package main import ( "encoding/json" "fmt" "html/template" "net/http" "os" "os/exec" "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" host "forge.lightcrystal.systems/lightcrystal/memnarch/hosts" ) 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) buildOutput, err := buildCmd.CombinedOutput() if err != nil { fmt.Println(buildOutput) // log that build failed, with the complete output } // for each host, we gotta get the key and then, thru ssh for name, addr := range a.Deploy.Hosts { h := host.Host{ Name: name, Addr: addr, } // pre-deploy for _, step := range a.Deploy.Before { err := h.Run(step...) if err != nil { // log error or recover } } // deploy artifacts for path, artifacts := range a.Deploy.Artifacts { for _, a := range artifacts { fmt.Println(path + " :: " + a) // use rsync to copy artifact to path on host // return an error if we get one } } // post-deploy for _, step := range a.Deploy.After { err := h.Run(step...) if err != nil { // log error or recover } } } }() 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) } }