all repos — memnarch @ c28267af045fccf922a7eb484b8b6844019305ab

featherweight orchestrator

main.go (raw)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
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<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
}

func main() {
	err := run(os.Args)
	if err == nil {
		os.Exit(0)
	} else {
		fmt.Println(err.Error())
		os.Exit(1)
	}
}