This commit is contained in:
Iris Lightshard 2023-09-01 22:42:39 -06:00
parent 2bb6d24546
commit 734eab3cfd
Signed by: Iris Lightshard
GPG key ID: F54E0D40695271D4
3 changed files with 129 additions and 130 deletions

View file

@ -1,26 +1,26 @@
package action package action
import ( import (
"fmt" "fmt"
"os" "os"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type Action struct { type Action struct {
Build struct { Build struct {
Cmd string `yaml:"cmd"` Cmd string `yaml:"cmd"`
} `yaml:"build,omitempty"` } `yaml:"build,omitempty"`
Deploy struct { Deploy struct {
Hosts []string `yaml:"hosts"` Hosts []string `yaml:"hosts"`
Artifacts map[string][]string `yaml:"artifacts"` Artifacts map[string][]string `yaml:"artifacts"`
Before map[string]string `yaml:"before,omitempty"` Before map[string]string `yaml:"before,omitempty"`
After map[string]string `yaml:"after,omitempty"` After map[string]string `yaml:"after,omitempty"`
} `yaml:"deploy,omitempty"` } `yaml:"deploy,omitempty"`
} }
func Read(filename string) (*Action, error) { func Read(filename string) (*Action, error) {
b, err := os.ReadFile(filename) b, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, fmt.Errorf("reading action: %w", err) return nil, fmt.Errorf("reading action: %w", err)
} }

195
main.go
View file

@ -1,136 +1,135 @@
package main package main
import ( import (
"encoding/json"
"fmt"
"html/template"
"net/http"
"os"
"os/exec"
"path/filepath"
"encoding/json" "hacklab.nilfm.cc/quartzgun/renderer"
"fmt" "hacklab.nilfm.cc/quartzgun/router"
"html/template" . "hacklab.nilfm.cc/quartzgun/util"
"os"
"os/exec"
"net/http"
"path/filepath"
"hacklab.nilfm.cc/quartzgun/renderer" "forge.lightcrystal.systems/lightcrystal/memnarch/action"
"hacklab.nilfm.cc/quartzgun/router" "forge.lightcrystal.systems/lightcrystal/memnarch/webhook"
. "hacklab.nilfm.cc/quartzgun/util"
"forge.lightcrystal.systems/lightcrystal/memnarch/action"
"forge.lightcrystal.systems/lightcrystal/memnarch/webhook"
) )
func decode(next http.Handler) http.Handler { func decode(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
data := make(map[string]interface{}) data := make(map[string]interface{})
err := json.NewDecoder(req.Body).Decode(&data) err := json.NewDecoder(req.Body).Decode(&data)
if err == nil { if err == nil {
AddContextValue(req, "data", data) AddContextValue(req, "data", data)
} else { } else {
AddContextValue(req, "data", err.Error()) AddContextValue(req, "data", err.Error())
} }
next.ServeHTTP(w, req) next.ServeHTTP(w, req)
}) })
} }
func runJob(secret string, next http.Handler) http.Handler { func runJob(secret string, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// validate signature // validate signature
_, err := webhook.Verify([]byte(secret), req) _, err := webhook.Verify([]byte(secret), req)
if err != nil { if err != nil {
w.WriteHeader(422) w.WriteHeader(422)
return return
} }
// get repo from data // get repo from data
data := req.Context().Value("data").(map[string]interface{}) data := req.Context().Value("data").(map[string]interface{})
if data != nil { if data != nil {
w.WriteHeader(500) w.WriteHeader(500)
return return
} }
repoUrl := data["repository"].(map[string]interface{})["clone_url"].(string) repoUrl := data["repository"].(map[string]interface{})["clone_url"].(string)
owner := data["owner"].(map[string]interface{})["login"].(string) owner := data["owner"].(map[string]interface{})["login"].(string)
repo := data["repository"].(map[string]interface{})["name"].(string) repo := data["repository"].(map[string]interface{})["name"].(string)
obj := data["head_commit"].(map[string]interface{})["id"].(string) obj := data["head_commit"].(map[string]interface{})["id"].(string)
// create working dir // create working dir
workingDir := filepath.Join("working", owner, repo, obj) workingDir := filepath.Join("working", owner, repo, obj)
if (os.MkdirAll(workingDir, 0750) != nil) { if os.MkdirAll(workingDir, 0750) != nil {
w.WriteHeader(500) w.WriteHeader(500)
return return
} }
// from this point on we can tell the client they succeeded // from this point on we can tell the client they succeeded
// so we run the rest in a goroutine... // so we run the rest in a goroutine...
go func() { go func() {
// cd and checkout repo // cd and checkout repo
clone := exec.Command("git", "clone", repoUrl) clone := exec.Command("git", "clone", repoUrl)
clone.Dir = workingDir clone.Dir = workingDir
err := clone.Run() err := clone.Run()
if err != nil { if err != nil {
// clone error - log it and quit // clone error - log it and quit
return return
} }
// read memnarch action file // read memnarch action file
urlParams := req.Context().Value("params").(map[string]string) urlParams := req.Context().Value("params").(map[string]string)
jobName := urlParams["job"] jobName := urlParams["job"]
jobFile := filepath.Join(workingDir, repo, jobName + ".yml") jobFile := filepath.Join(workingDir, repo, jobName+".yml")
a, err := action.Read(jobFile) a, err := action.Read(jobFile)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
} }
// decode and perform action // decode and perform action
// build // build
buildCmd := exec.Command(a.Build.Cmd) buildCmd := exec.Command(a.Build.Cmd)
buildCmd.Dir = filepath.Join(workingDir, repo) buildCmd.Dir = filepath.Join(workingDir, repo)
// pre-deploy // pre-deploy
// deploy // deploy
// post-deploy // post-deploy
}() }()
AddContextValue(req, "data", "job submitted") AddContextValue(req, "data", "job submitted")
next.ServeHTTP(w, req) next.ServeHTTP(w, req)
}) })
} }
func seeJobsForRepo(next http.Handler) http.Handler { func seeJobsForRepo(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
}) })
} }
func seeJobsForObject(next http.Handler) http.Handler { func seeJobsForObject(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
}) })
} }
func run(args []string) error { func run(args []string) error {
secret := args[1] secret := args[1]
rtr := &router.Router{ rtr := &router.Router{
Fallback: *template.Must(template.ParseFiles("templates/error.html")), Fallback: *template.Must(template.ParseFiles("templates/error.html")),
} }
rtr.Post("/echo", decode(renderer.JSON("data"))) rtr.Post("/echo", decode(renderer.JSON("data")))
rtr.Post(`/do/(?P<job>\S+)`, decode(runJob(secret, renderer.JSON("data")))) rtr.Post(`/do/(?P<job>\S+)`, decode(runJob(secret, renderer.JSON("data"))))
rtr.Get(`/status/(?P<owner>[^/]+)/(?P<repo>\S+)`, seeJobsForRepo(renderer.JSON("data"))) rtr.Get(`/status/(?P<owner>[^/]+)/(?P<repo>\S+)`, seeJobsForRepo(renderer.JSON("data")))
rtr.Get(`/status/(?P<owner>[^/]+)/(?P<repo>[^/]+)/(?P<object>\S+)`, seeJobsForObject(renderer.JSON("data"))) rtr.Get(`/status/(?P<owner>[^/]+)/(?P<repo>[^/]+)/(?P<object>\S+)`, seeJobsForObject(renderer.JSON("data")))
http.ListenAndServe(":9999", rtr); http.ListenAndServe(":9999", rtr)
return nil return nil
} }
func main () { func main() {
err := run(os.Args) err := run(os.Args)
if err == nil { if err == nil {
os.Exit(0) os.Exit(0)
} else { } else {
fmt.Println(err.Error()) fmt.Println(err.Error())
os.Exit(1) os.Exit(1)
} }
} }

View file

@ -13,7 +13,7 @@ import (
type Hook struct { type Hook struct {
Signature string Signature string
Payload []byte Payload []byte
} }
const signaturePrefix = "" const signaturePrefix = ""