diff --git a/action/action.go b/action/action.go index 161f070..a27667a 100644 --- a/action/action.go +++ b/action/action.go @@ -1,26 +1,26 @@ package action import ( - "fmt" - "os" + "fmt" + "os" - "gopkg.in/yaml.v3" + "gopkg.in/yaml.v3" ) type Action struct { - Build struct { - Cmd string `yaml:"cmd"` - } `yaml:"build,omitempty"` - Deploy struct { - Hosts []string `yaml:"hosts"` - Artifacts map[string][]string `yaml:"artifacts"` - Before map[string]string `yaml:"before,omitempty"` - After map[string]string `yaml:"after,omitempty"` - } `yaml:"deploy,omitempty"` + Build struct { + Cmd string `yaml:"cmd"` + } `yaml:"build,omitempty"` + Deploy struct { + Hosts []string `yaml:"hosts"` + Artifacts map[string][]string `yaml:"artifacts"` + Before map[string]string `yaml:"before,omitempty"` + After map[string]string `yaml:"after,omitempty"` + } `yaml:"deploy,omitempty"` } func Read(filename string) (*Action, error) { - b, err := os.ReadFile(filename) + b, err := os.ReadFile(filename) if err != nil { return nil, fmt.Errorf("reading action: %w", err) } @@ -31,4 +31,4 @@ func Read(filename string) (*Action, error) { } return &a, nil -} \ No newline at end of file +} diff --git a/main.go b/main.go index a8e673e..68626d8 100644 --- a/main.go +++ b/main.go @@ -1,136 +1,135 @@ package main import ( + "encoding/json" + "fmt" + "html/template" + "net/http" + "os" + "os/exec" + "path/filepath" - "encoding/json" - "fmt" - "html/template" - "os" - "os/exec" - "net/http" - "path/filepath" + "hacklab.nilfm.cc/quartzgun/renderer" + "hacklab.nilfm.cc/quartzgun/router" + . "hacklab.nilfm.cc/quartzgun/util" - "hacklab.nilfm.cc/quartzgun/renderer" - "hacklab.nilfm.cc/quartzgun/router" - . "hacklab.nilfm.cc/quartzgun/util" - - "forge.lightcrystal.systems/lightcrystal/memnarch/action" - "forge.lightcrystal.systems/lightcrystal/memnarch/webhook" + "forge.lightcrystal.systems/lightcrystal/memnarch/action" + "forge.lightcrystal.systems/lightcrystal/memnarch/webhook" ) func decode(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - data := make(map[string]interface{}) - - err := json.NewDecoder(req.Body).Decode(&data) - - if err == nil { - AddContextValue(req, "data", data) - } else { - AddContextValue(req, "data", err.Error()) - } - - next.ServeHTTP(w, req) - }) + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + data := make(map[string]interface{}) + + err := json.NewDecoder(req.Body).Decode(&data) + + if err == nil { + AddContextValue(req, "data", data) + } else { + AddContextValue(req, "data", err.Error()) + } + + next.ServeHTTP(w, req) + }) } func runJob(secret string, next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - // validate signature - _, err := webhook.Verify([]byte(secret), req) - if err != nil { - w.WriteHeader(422) - return - } - // get repo from data - data := req.Context().Value("data").(map[string]interface{}) - if data != nil { - w.WriteHeader(500) - return - } - repoUrl := data["repository"].(map[string]interface{})["clone_url"].(string) - owner := data["owner"].(map[string]interface{})["login"].(string) - repo := data["repository"].(map[string]interface{})["name"].(string) - obj := data["head_commit"].(map[string]interface{})["id"].(string) - - // create working dir - workingDir := filepath.Join("working", owner, repo, obj) - if (os.MkdirAll(workingDir, 0750) != nil) { - w.WriteHeader(500) - return - } - - // from this point on we can tell the client they succeeded - - // so we run the rest in a goroutine... - go func() { - // cd and checkout repo - clone := exec.Command("git", "clone", repoUrl) - clone.Dir = workingDir - - err := clone.Run() - if err != nil { - // clone error - log it and quit - return - } - // read memnarch action file - urlParams := req.Context().Value("params").(map[string]string) - jobName := urlParams["job"] - - jobFile := filepath.Join(workingDir, repo, jobName + ".yml") - a, err := action.Read(jobFile) - - if err != nil { - fmt.Println(err.Error()) - } - - // decode and perform action - // build - buildCmd := exec.Command(a.Build.Cmd) - buildCmd.Dir = filepath.Join(workingDir, repo) - - // pre-deploy - // deploy - // post-deploy - }() - - AddContextValue(req, "data", "job submitted") - next.ServeHTTP(w, req) - }) + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // validate signature + _, err := webhook.Verify([]byte(secret), req) + if err != nil { + w.WriteHeader(422) + return + } + // get repo from data + data := req.Context().Value("data").(map[string]interface{}) + if data != nil { + w.WriteHeader(500) + return + } + repoUrl := data["repository"].(map[string]interface{})["clone_url"].(string) + owner := data["owner"].(map[string]interface{})["login"].(string) + repo := data["repository"].(map[string]interface{})["name"].(string) + obj := data["head_commit"].(map[string]interface{})["id"].(string) + + // create working dir + workingDir := filepath.Join("working", owner, repo, obj) + if os.MkdirAll(workingDir, 0750) != nil { + w.WriteHeader(500) + return + } + + // from this point on we can tell the client they succeeded + + // so we run the rest in a goroutine... + go func() { + // cd and checkout repo + clone := exec.Command("git", "clone", repoUrl) + clone.Dir = workingDir + + err := clone.Run() + if err != nil { + // clone error - log it and quit + return + } + // read memnarch action file + urlParams := req.Context().Value("params").(map[string]string) + jobName := urlParams["job"] + + jobFile := filepath.Join(workingDir, repo, jobName+".yml") + a, err := action.Read(jobFile) + + if err != nil { + fmt.Println(err.Error()) + } + + // decode and perform action + // build + buildCmd := exec.Command(a.Build.Cmd) + buildCmd.Dir = filepath.Join(workingDir, repo) + + // pre-deploy + // deploy + // post-deploy + }() + + AddContextValue(req, "data", "job submitted") + next.ServeHTTP(w, req) + }) } 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 { - 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 { - secret := args[1] + secret := args[1] - rtr := &router.Router{ - Fallback: *template.Must(template.ParseFiles("templates/error.html")), - } - - rtr.Post("/echo", decode(renderer.JSON("data"))) - rtr.Post(`/do/(?P\S+)`, decode(runJob(secret, renderer.JSON("data")))) - rtr.Get(`/status/(?P[^/]+)/(?P\S+)`, seeJobsForRepo(renderer.JSON("data"))) - rtr.Get(`/status/(?P[^/]+)/(?P[^/]+)/(?P\S+)`, seeJobsForObject(renderer.JSON("data"))) - - http.ListenAndServe(":9999", rtr); - return nil + rtr := &router.Router{ + Fallback: *template.Must(template.ParseFiles("templates/error.html")), + } + + rtr.Post("/echo", decode(renderer.JSON("data"))) + rtr.Post(`/do/(?P\S+)`, decode(runJob(secret, renderer.JSON("data")))) + rtr.Get(`/status/(?P[^/]+)/(?P\S+)`, seeJobsForRepo(renderer.JSON("data"))) + rtr.Get(`/status/(?P[^/]+)/(?P[^/]+)/(?P\S+)`, seeJobsForObject(renderer.JSON("data"))) + + http.ListenAndServe(":9999", rtr) + return nil } -func main () { - err := run(os.Args) - if err == nil { - os.Exit(0) - } else { - fmt.Println(err.Error()) - os.Exit(1) - } -} \ No newline at end of file +func main() { + err := run(os.Args) + if err == nil { + os.Exit(0) + } else { + fmt.Println(err.Error()) + os.Exit(1) + } +} diff --git a/webhook/webhook.go b/webhook/webhook.go index 4bbd8a6..868c26c 100644 --- a/webhook/webhook.go +++ b/webhook/webhook.go @@ -13,11 +13,11 @@ import ( type Hook struct { Signature string - Payload []byte + Payload []byte } const signaturePrefix = "" -const signatureLength = len(signaturePrefix) + 64 +const signatureLength = len(signaturePrefix) + 64 func signBody(secret, body []byte) []byte { computed := hmac.New(sha256.New, secret) @@ -62,4 +62,4 @@ func Verify(secret []byte, req *http.Request) (hook *Hook, err error) { err = errors.New("Invalid signature") } return -} \ No newline at end of file +}