2023-08-26 05:30:39 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2023-09-02 04:42:39 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"html/template"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
2023-08-26 05:30:39 +00:00
|
|
|
|
2023-09-02 04:42:39 +00:00
|
|
|
"hacklab.nilfm.cc/quartzgun/renderer"
|
|
|
|
"hacklab.nilfm.cc/quartzgun/router"
|
|
|
|
. "hacklab.nilfm.cc/quartzgun/util"
|
2023-08-26 05:30:39 +00:00
|
|
|
|
2023-09-02 04:42:39 +00:00
|
|
|
"forge.lightcrystal.systems/lightcrystal/memnarch/action"
|
|
|
|
"forge.lightcrystal.systems/lightcrystal/memnarch/webhook"
|
2023-09-17 05:23:59 +00:00
|
|
|
host "forge.lightcrystal.systems/lightcrystal/memnarch/hosts"
|
2023-08-26 05:30:39 +00:00
|
|
|
)
|
|
|
|
|
2023-08-28 06:09:46 +00:00
|
|
|
func decode(next http.Handler) http.Handler {
|
2023-09-02 04:42:39 +00:00
|
|
|
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)
|
|
|
|
})
|
2023-08-28 06:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func runJob(secret string, next http.Handler) http.Handler {
|
2023-09-02 04:42:39 +00:00
|
|
|
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)
|
2023-09-17 05:23:59 +00:00
|
|
|
buildOutput, err := buildCmd.CombinedOutput()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(buildOutput)
|
|
|
|
// log that build failed, with the complete output
|
|
|
|
}
|
2023-09-02 04:42:39 +00:00
|
|
|
|
2023-09-17 05:23:59 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-09-02 04:42:39 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
AddContextValue(req, "data", "job submitted")
|
|
|
|
next.ServeHTTP(w, req)
|
|
|
|
})
|
2023-08-28 06:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func seeJobsForRepo(next http.Handler) http.Handler {
|
2023-09-02 04:42:39 +00:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
|
|
})
|
2023-08-28 06:09:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func seeJobsForObject(next http.Handler) http.Handler {
|
2023-09-02 04:42:39 +00:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
|
|
})
|
2023-08-26 05:30:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func run(args []string) error {
|
2023-09-02 04:42:39 +00:00
|
|
|
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<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>[^/]+)/(?P<object>\S+)`, seeJobsForObject(renderer.JSON("data")))
|
|
|
|
|
|
|
|
http.ListenAndServe(":9999", rtr)
|
|
|
|
return nil
|
2023-08-26 05:30:39 +00:00
|
|
|
}
|
|
|
|
|
2023-09-02 04:42:39 +00:00
|
|
|
func main() {
|
|
|
|
err := run(os.Args)
|
|
|
|
if err == nil {
|
|
|
|
os.Exit(0)
|
|
|
|
} else {
|
|
|
|
fmt.Println(err.Error())
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|