From 80c6680eda2afd2e9899f92d508d1b1907916756 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 13 Feb 2020 22:38:34 +0000 Subject: feat(ops/besadii): Refactored tool to trigger sourcehut builds Refactors //ops/sync-gcsr which was previously responsible for synchronising the git repository between GCSR and the git.tazj.in cgit instance to simply be responsible for triggering builds on sourcehut. This program is intended to run as a git post-update hook. Note: Not yet feature complete, as interpolation of concrete git values and also sourcehut secrets is missing. --- ops/besadii/main.go | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 ops/besadii/main.go (limited to 'ops/besadii/main.go') diff --git a/ops/besadii/main.go b/ops/besadii/main.go new file mode 100644 index 0000000000..e9feadd953 --- /dev/null +++ b/ops/besadii/main.go @@ -0,0 +1,119 @@ +// Copyright 2019 Google LLC. +// SPDX-License-Identifier: Apache-2.0 +// +// besadii is a small CLI tool that triggers depot builds on +// builds.sr.ht +// +// It is designed to run as a post-update git hook on the server +// hosting the depot. +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" +) + +// Represents a builds.sr.ht build object as described on +// https://man.sr.ht/builds.sr.ht/api.md +type Build struct { + Manifest string `json:"manifest"` + Note string `json:"note"` + Tags []string `json:"tags"` +} + +// Represents a build trigger object as described on +type Trigger struct { + Action string `json:"action"` + Condition string `json:"condition"` + To string `json:"to"` +} + +// Represents a build manifest for sourcehut. +type Manifest struct { + Image string `json:"image"` + Sources []string `json:"sources"` + Secrets []string `json:"secrets"` + Tasks [](map[string]string) `json:"tasks"` + Triggers []Trigger `json:"triggers"` +} + +func prepareManifest(commit string) string { + m := Manifest{ + Image: "nixos/latest", + Sources: []string{"https://git.camden.tazj.in/"}, + + // secret for cachix/tazjin + Secrets: []string{"f7f02546-4d95-44f7-a98e-d61fdded8b5b"}, + + Tasks: [](map[string]string){ + {"setup": `# sourcehut does not censor secrets in builds, hence this hack: +echo -n 'export CACHIX_SIGNING_KEY=' >> ~/.buildenv +cat ~/.cachix-tazjin >> ~/.buildenv +nix-env -iA third_party.cachix -f git.tazj.in +cachix use tazjin +cd git.tazj.in +git checkout ` + commit}, + + {"build": `cd git.tazj.in +nix-build ci-builds.nix > built-paths`}, + + {"cache": `cd git.tazj.in +cat built-paths | cachix push tazjin`}, + }, + + Triggers: []Trigger{ + Trigger{Action: "email", Condition: "failure", To: "mail@tazj.in"}, + }, + } + + j, _ := json.Marshal(m) + return string(j) +} + +// Trigger a build of a given branch & commit on builds.sr.ht +func triggerBuild(branch, commit string) { + build := Build{ + Manifest: prepareManifest(commit), + Note: fmt.Sprintf("Build of 'master' at '%s'", commit), + Tags: []string{ + "depot", branch, + }, + } + + body, _ := json.Marshal(build) + reader := ioutil.NopCloser(bytes.NewReader(body)) + + req, err := http.NewRequest("POST", "https://builds.sr.ht/api/jobs", reader) + if err != nil { + log.Fatalln("[ERROR] failed to create an HTTP request:", err) + } + + req.Header.Add("Authorization", fmt.Sprintf("token %s", os.Getenv("SRHT_TOKEN"))) + req.Header.Add("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + // This might indicate a temporary error on the SourceHut side, do + // not fail the whole program. + log.Println("failed to send builds.sr.ht request:", err) + return + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + respBody, err := ioutil.ReadAll(resp.Body) + log.Printf("received non-success response from builds.sr.ht: %s (%v)[%s]", respBody, resp.Status, err) + } else { + log.Println("triggered builds.sr.ht job for commit", commit) + } +} + +func main() { + triggerBuild("master", "c5806a44a728d5a46878f54de7b695321a38559c") +} -- cgit 1.4.1 From 0a34810e274bb2ca3dc8764733ec23b6b878d5bc Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 21 Feb 2020 22:06:56 +0000 Subject: chore(ops/besadii): Fail if sourcehut token is unset --- ops/besadii/main.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'ops/besadii/main.go') diff --git a/ops/besadii/main.go b/ops/besadii/main.go index e9feadd953..c417cb0a12 100644 --- a/ops/besadii/main.go +++ b/ops/besadii/main.go @@ -94,12 +94,17 @@ func triggerBuild(branch, commit string) { log.Fatalln("[ERROR] failed to create an HTTP request:", err) } - req.Header.Add("Authorization", fmt.Sprintf("token %s", os.Getenv("SRHT_TOKEN"))) + token := fmt.Sprintf("token %s", os.Getenv("SRHT_TOKEN")) + if token == "" { + log.Fatalln("[ERROR] sourcehut token is not set") + } + + req.Header.Add("Authorization", ) req.Header.Add("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { - // This might indicate a temporary error on the SourceHut side, do + // This might indicate a temporary error on the sourcehut side, do // not fail the whole program. log.Println("failed to send builds.sr.ht request:", err) return -- cgit 1.4.1 From 5058f3928a13e73a72a86e685519ba8be7ca8714 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 21 Feb 2020 22:31:57 +0000 Subject: feat(ops/besadii): Read sourcehut token from secrets file on disk --- ops/besadii/main.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'ops/besadii/main.go') diff --git a/ops/besadii/main.go b/ops/besadii/main.go index c417cb0a12..4bb5c59a1c 100644 --- a/ops/besadii/main.go +++ b/ops/besadii/main.go @@ -77,7 +77,7 @@ cat built-paths | cachix push tazjin`}, } // Trigger a build of a given branch & commit on builds.sr.ht -func triggerBuild(branch, commit string) { +func triggerBuild(token, branch, commit string) { build := Build{ Manifest: prepareManifest(commit), Note: fmt.Sprintf("Build of 'master' at '%s'", commit), @@ -94,12 +94,7 @@ func triggerBuild(branch, commit string) { log.Fatalln("[ERROR] failed to create an HTTP request:", err) } - token := fmt.Sprintf("token %s", os.Getenv("SRHT_TOKEN")) - if token == "" { - log.Fatalln("[ERROR] sourcehut token is not set") - } - - req.Header.Add("Authorization", ) + req.Header.Add("Authorization", token) req.Header.Add("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) @@ -121,4 +116,9 @@ func triggerBuild(branch, commit string) { func main() { triggerBuild("master", "c5806a44a728d5a46878f54de7b695321a38559c") + token, err := ioutil.ReadFile("/etc/secrets/srht-token") + if err != nil { + log.Fatalln("[ERROR] sourcehot token could not be read") + } + } -- cgit 1.4.1 From dcbe3d1f9b0c1a4720bd2b9170d302c8ba672f83 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 21 Feb 2020 22:32:23 +0000 Subject: feat(ops/besadii): Use post-receive hook input to trigger builds Parses the input passed to besadii from git to extract ref updates and trigger builds. --- ops/besadii/main.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) (limited to 'ops/besadii/main.go') diff --git a/ops/besadii/main.go b/ops/besadii/main.go index 4bb5c59a1c..b09e1b929c 100644 --- a/ops/besadii/main.go +++ b/ops/besadii/main.go @@ -9,6 +9,7 @@ package main import ( + "bufio" "bytes" "encoding/json" "fmt" @@ -16,8 +17,18 @@ import ( "log" "net/http" "os" + "strings" ) +// Represents an updated reference as passed to besadii by git +// +// https://git-scm.com/docs/githooks#pre-receive +type refUpdate struct { + name string + old string + new string +} + // Represents a builds.sr.ht build object as described on // https://man.sr.ht/builds.sr.ht/api.md type Build struct { @@ -110,15 +121,50 @@ func triggerBuild(token, branch, commit string) { respBody, err := ioutil.ReadAll(resp.Body) log.Printf("received non-success response from builds.sr.ht: %s (%v)[%s]", respBody, resp.Status, err) } else { - log.Println("triggered builds.sr.ht job for commit", commit) + log.Printf("triggered builds.sr.ht job for branch '%s' at commit '%s'", branch, commit) } } +func parseRefUpdates() ([]refUpdate, error) { + var updates []refUpdate + + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + line := scanner.Text() + fragments := strings.Split(line, " ") + + if len(fragments) != 3 { + return nil, fmt.Errorf("invalid ref update: '%s'", line) + } + + updates = append(updates, refUpdate{ + old: fragments[0], + new: fragments[1], + name: fragments[2], + }) + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return updates, nil +} + func main() { - triggerBuild("master", "c5806a44a728d5a46878f54de7b695321a38559c") token, err := ioutil.ReadFile("/etc/secrets/srht-token") if err != nil { log.Fatalln("[ERROR] sourcehot token could not be read") } + updates, err := parseRefUpdates() + if err != nil { + log.Fatalln("[ERROR] could not parse updated refs:", err) + } + + log.Printf("triggering builds for %v refs", len(updates)) + + for _, update := range updates { + triggerBuild(string(token), update.name, update.new) + } } -- cgit 1.4.1 From 59d02771b52c36af62c2bdce034648600c338eda Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 21 Feb 2020 22:46:34 +0000 Subject: refactor(ops/besadii): Log to syslog instead of stdout --- ops/besadii/main.go | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) (limited to 'ops/besadii/main.go') diff --git a/ops/besadii/main.go b/ops/besadii/main.go index b09e1b929c..34b33f9cac 100644 --- a/ops/besadii/main.go +++ b/ops/besadii/main.go @@ -14,7 +14,7 @@ import ( "encoding/json" "fmt" "io/ioutil" - "log" + "log/syslog" "net/http" "os" "strings" @@ -88,7 +88,7 @@ cat built-paths | cachix push tazjin`}, } // Trigger a build of a given branch & commit on builds.sr.ht -func triggerBuild(token, branch, commit string) { +func triggerBuild(log *syslog.Writer, token, branch, commit string) { build := Build{ Manifest: prepareManifest(commit), Note: fmt.Sprintf("Build of 'master' at '%s'", commit), @@ -102,7 +102,8 @@ func triggerBuild(token, branch, commit string) { req, err := http.NewRequest("POST", "https://builds.sr.ht/api/jobs", reader) if err != nil { - log.Fatalln("[ERROR] failed to create an HTTP request:", err) + log.Err(fmt.Sprintf("failed to create an HTTP request: %s", err)) + os.Exit(1) } req.Header.Add("Authorization", token) @@ -112,16 +113,16 @@ func triggerBuild(token, branch, commit string) { if err != nil { // This might indicate a temporary error on the sourcehut side, do // not fail the whole program. - log.Println("failed to send builds.sr.ht request:", err) + log.Err(fmt.Sprintf("failed to send builds.sr.ht request:", err)) return } defer resp.Body.Close() if resp.StatusCode != 200 { respBody, err := ioutil.ReadAll(resp.Body) - log.Printf("received non-success response from builds.sr.ht: %s (%v)[%s]", respBody, resp.Status, err) + log.Err(fmt.Sprintf("received non-success response from builds.sr.ht: %s (%v)[%s]", respBody, resp.Status, err)) } else { - log.Printf("triggered builds.sr.ht job for branch '%s' at commit '%s'", branch, commit) + fmt.Fprintf(log, "triggered builds.sr.ht job for branch '%s' at commit '%s'", branch, commit) } } @@ -152,19 +153,27 @@ func parseRefUpdates() ([]refUpdate, error) { } func main() { + log, err := syslog.New(syslog.LOG_INFO|syslog.LOG_USER, "besadii") + if err != nil { + fmt.Printf("failed to open syslog: %s\n", err) + os.Exit(1) + } + token, err := ioutil.ReadFile("/etc/secrets/srht-token") if err != nil { - log.Fatalln("[ERROR] sourcehot token could not be read") + log.Alert("sourcehot token could not be read") + os.Exit(1) } updates, err := parseRefUpdates() if err != nil { - log.Fatalln("[ERROR] could not parse updated refs:", err) + log.Err(fmt.Sprintf("could not parse updated refs:", err)) + os.Exit(1) } - log.Printf("triggering builds for %v refs", len(updates)) + fmt.Fprintf(log, "triggering builds for %v refs", len(updates)) for _, update := range updates { - triggerBuild(string(token), update.name, update.new) + triggerBuild(log, string(token), update.name, update.new) } } -- cgit 1.4.1 From 8377fd48f51df43b2c2376b5be34d1d63e72185d Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 21 Feb 2020 22:51:40 +0000 Subject: fix(ops/besadii): Send auth token in correct format --- ops/besadii/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ops/besadii/main.go') diff --git a/ops/besadii/main.go b/ops/besadii/main.go index 34b33f9cac..438dd91f5e 100644 --- a/ops/besadii/main.go +++ b/ops/besadii/main.go @@ -106,7 +106,7 @@ func triggerBuild(log *syslog.Writer, token, branch, commit string) { os.Exit(1) } - req.Header.Add("Authorization", token) + req.Header.Add("Authorization", "token " + token) req.Header.Add("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) -- cgit 1.4.1 From 21b76cb0238319c7fd58d7522fd42ca8c314ff8b Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 21 Feb 2020 22:57:34 +0000 Subject: feat(ops/besadii): Run 'git update-server-info' at startup Since besadii is effectively the entire post-receive hook, it also needs to do the entire job of the hook. --- ops/besadii/main.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'ops/besadii/main.go') diff --git a/ops/besadii/main.go b/ops/besadii/main.go index 438dd91f5e..36e0b6e59f 100644 --- a/ops/besadii/main.go +++ b/ops/besadii/main.go @@ -17,9 +17,12 @@ import ( "log/syslog" "net/http" "os" + "os/exec" "strings" ) +var gitBin = "git" + // Represents an updated reference as passed to besadii by git // // https://git-scm.com/docs/githooks#pre-receive @@ -106,7 +109,7 @@ func triggerBuild(log *syslog.Writer, token, branch, commit string) { os.Exit(1) } - req.Header.Add("Authorization", "token " + token) + req.Header.Add("Authorization", "token "+token) req.Header.Add("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) @@ -159,6 +162,15 @@ func main() { os.Exit(1) } + // Before triggering builds, it is important that git + // update-server-info is run so that cgit correctly serves the + // repository. + err := exec.Command(gitBin, "update-server-info").Run() + if err != nil { + log.Alert("failed to run 'git update-server-info' for depot!") + os.Exit() + } + token, err := ioutil.ReadFile("/etc/secrets/srht-token") if err != nil { log.Alert("sourcehot token could not be read") -- cgit 1.4.1 From c689df0dc721b766dfc4c0820238646a9e3fe815 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 21 Feb 2020 23:01:42 +0000 Subject: fix(ops/besadii): Replace slashes in branch names Submitting a build with a branch containing a slash (which is common for my branches) returns this error: Invalid tag name, tags must use lowercase alphanumeric characters, underscores, dashes, or dots This commit replaces all slashes with underscores to work around that. --- ops/besadii/main.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'ops/besadii/main.go') diff --git a/ops/besadii/main.go b/ops/besadii/main.go index 36e0b6e59f..0241eb8449 100644 --- a/ops/besadii/main.go +++ b/ops/besadii/main.go @@ -96,7 +96,9 @@ func triggerBuild(log *syslog.Writer, token, branch, commit string) { Manifest: prepareManifest(commit), Note: fmt.Sprintf("Build of 'master' at '%s'", commit), Tags: []string{ - "depot", branch, + // my branch names tend to contain slashes, which are not valid + // identifiers in sourcehut. + "depot", strings.ReplaceAll(branch, "/", "_"), }, } @@ -122,8 +124,8 @@ func triggerBuild(log *syslog.Writer, token, branch, commit string) { defer resp.Body.Close() if resp.StatusCode != 200 { - respBody, err := ioutil.ReadAll(resp.Body) - log.Err(fmt.Sprintf("received non-success response from builds.sr.ht: %s (%v)[%s]", respBody, resp.Status, err)) + respBody, _ := ioutil.ReadAll(resp.Body) + log.Err(fmt.Sprintf("received non-success response from builds.sr.ht: %s (%v)", respBody, resp.Status)) } else { fmt.Fprintf(log, "triggered builds.sr.ht job for branch '%s' at commit '%s'", branch, commit) } @@ -165,10 +167,10 @@ func main() { // Before triggering builds, it is important that git // update-server-info is run so that cgit correctly serves the // repository. - err := exec.Command(gitBin, "update-server-info").Run() + err = exec.Command(gitBin, "update-server-info").Run() if err != nil { log.Alert("failed to run 'git update-server-info' for depot!") - os.Exit() + os.Exit(1) } token, err := ioutil.ReadFile("/etc/secrets/srht-token") -- cgit 1.4.1 From 5ed68f0f6b192cffb1bd4d1790360583d08018bb Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Fri, 21 Feb 2020 23:16:28 +0000 Subject: fix(ops/besadii): Only trigger builds for branches --- ops/besadii/main.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'ops/besadii/main.go') diff --git a/ops/besadii/main.go b/ops/besadii/main.go index 0241eb8449..460eddf265 100644 --- a/ops/besadii/main.go +++ b/ops/besadii/main.go @@ -143,10 +143,14 @@ func parseRefUpdates() ([]refUpdate, error) { return nil, fmt.Errorf("invalid ref update: '%s'", line) } + if !strings.HasPrefix(fragments[2], "refs/heads/") { + continue + } + updates = append(updates, refUpdate{ old: fragments[0], new: fragments[1], - name: fragments[2], + name: strings.TrimPrefix(fragments[2], "refs/heads/"), }) } -- cgit 1.4.1