package main import ( "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" "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) }) } 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) }) } func seeJobsForRepo(next http.Handler) http.Handler { 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) { }) } func run(args []string) error { 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 } func main () { err := run(os.Args) if err == nil { os.Exit(0) } else { fmt.Println(err.Error()) os.Exit(1) } }