main.go (3651B)
1 // bus is a smal CLI tool that triggers home builds on build.sr.ht. 2 // It is designed to be run in a cron or systemd timer. 3 package main 4 5 import ( 6 "bytes" 7 "encoding/json" 8 "flag" 9 "fmt" 10 "io/ioutil" 11 "net/http" 12 "os" 13 "path/filepath" 14 "strings" 15 16 yaml "gopkg.in/yaml.v2" 17 ) 18 19 var ( 20 branch = flag.String("branch", "master", "branch to schedule") 21 secret = flag.String("secret", "/etc/secrets/srht-token", "path to find sr.ht secret") 22 builds = flag.String("builds", "/etc/nixos/.builds", "path to find builds manifests") 23 ) 24 25 // Represents a builds.sr.ht build object as described on 26 // https://man.sr.ht/builds.sr.ht/api.md 27 type Build struct { 28 Manifest string `json:"manifest"` 29 Note string `json:"note"` 30 Tags []string `json:"tags"` 31 } 32 33 // Represents a build trigger object as described on <the docs for 34 // this are currently down> 35 type Trigger struct { 36 Action string `json:"action"` 37 Condition string `json:"condition"` 38 To string `json:"to"` 39 } 40 41 // Represents a build manifest for sourcehut. 42 type Manifest struct { 43 Image string `json:"image"` 44 Sources []string `json:"sources"` 45 Secrets []string `json:"secrets"` 46 Tasks [](map[string]string) `json:"tasks"` 47 Triggers []Trigger `json:"triggers"` 48 } 49 50 func triggerBuild(token, name, manifest, branch string) { 51 build := Build{ 52 Manifest: manifest, 53 Note: fmt.Sprintf("Nightly build of '%s' on '%s'", name, branch), 54 Tags: []string{ 55 // my branch names tend to contain slashes, which are not valid 56 // identifiers in sourcehut. 57 "home", "nightly", strings.ReplaceAll(branch, "/", "_"), name, 58 }, 59 } 60 61 body, _ := json.Marshal(build) 62 reader := ioutil.NopCloser(bytes.NewReader(body)) 63 64 req, err := http.NewRequest("POST", "https://builds.sr.ht/api/jobs", reader) 65 if err != nil { 66 fmt.Fprintf(os.Stderr, "failed to create an HTTP request: %s", err) 67 os.Exit(1) 68 } 69 70 req.Header.Add("Authorization", "token "+token) 71 req.Header.Add("Content-Type", "application/json") 72 73 resp, err := http.DefaultClient.Do(req) 74 if err != nil { 75 // This might indicate a temporary error on the sourcehut side, do 76 // not fail the whole program. 77 fmt.Fprintf(os.Stderr, "failed to send build.sr.ht request: %s", err) 78 return 79 } 80 defer resp.Body.Close() 81 82 if resp.StatusCode != 200 { 83 respBody, _ := ioutil.ReadAll(resp.Body) 84 fmt.Fprintf(os.Stderr, "received non-success response from builds.sr.ht: %s (%v)", respBody, resp.Status) 85 os.Exit(1) 86 } else { 87 fmt.Fprintf(os.Stdout, "triggered builds.sr.ht job for branch '%s'\n", branch) 88 } 89 } 90 91 func prepareManifest(manifest []byte) ([]byte, error) { 92 var m Manifest 93 if err := yaml.Unmarshal(manifest, &m); err != nil { 94 return manifest, err 95 } 96 m.Sources = append(m.Sources, "https://git.sr.ht/~vdemeester/home") 97 return yaml.Marshal(m) 98 } 99 100 func main() { 101 flag.Parse() 102 token, err := ioutil.ReadFile(*secret) 103 if err != nil { 104 fmt.Fprint(os.Stderr, "sourcehut token could not be read.") 105 os.Exit(1) 106 } 107 files, err := ioutil.ReadDir(*builds) 108 if err != nil { 109 fmt.Fprintf(os.Stderr, "cannot list builds manifest from %s: %v.\n", *builds, err) 110 os.Exit(1) 111 } 112 fmt.Fprintf(os.Stdout, "triggering builds for %v\n", *branch) 113 for _, f := range files { 114 path := filepath.Join(*builds, f.Name()) 115 manifest, err := ioutil.ReadFile(path) 116 if err != nil { 117 fmt.Fprintf(os.Stderr, "cannot read manifest %s: %v.\n", path, err) 118 os.Exit(1) 119 } 120 manifest, err = prepareManifest(manifest) 121 if err != nil { 122 fmt.Fprintf(os.Stderr, "failed to prepare manifest %s: %v.\n", path, err) 123 os.Exit(1) 124 } 125 triggerBuild(string(token), f.Name(), string(manifest), *branch) 126 } 127 }