Commit fd5e7ad395d7

Vincent Demeester <vincent@sbr.pm>
2020-05-10 13:54:39
bus: add a small tool to schedule builds.sr.ht builds…
… used in sakhalin to schedule nightly builds. Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent da55f31
Changed files (4)
machines/sakhalin.nixos.nix
@@ -104,6 +104,7 @@ with import ../assets/machines.nix; {
       OnFailure = "status-email-root@%n.service";
     };
   };
+  environment.etc."secrets/srht-token".text = "${tokten_srht}";
   # builds.sr.ht: daily builds
   systemd.services.builds-srht = {
     description = "Daily builds.sr.ht";
@@ -119,11 +120,7 @@ with import ../assets/machines.nix; {
       OnFailure = "status-email-root@%n.service";
     };
 
-    path = with pkgs; [ httpie ];
-    script = ''
-      manifest=$(cat /etc/nixos/.builds/nixos.yml)
-      http POST https://builds.sr.ht/api/jobs manifest="${manifest}" Authorization:"token ${token_srht}"
-    '';
+    script = "${pkgs.my.bus}/bin/bus";
 
     startAt = "daily";
   };
pkgs/default.nix
@@ -6,6 +6,7 @@ rec {
   tmux-tpm = pkgs.callPackage ./tmux-tpm { };
   vrsync = pkgs.callPackage ./vrsync { };
   vde-thinkpad = pkgs.callPackage ./vde-thinkpad { };
+  bus = pkgs.callPackage ../tools/bus { };
 
   # Mine
   ape = pkgs.callPackage ./ape { };
tools/bus/default.nix
@@ -0,0 +1,19 @@
+{ stdenv, lib, go }:
+
+stdenv.mkDerivation rec {
+  name = "bus";
+  src = ./.;
+
+  phases = "buildPhase installPhase";
+  buildInputs = [ go ];
+
+  buildPhase = ''
+    HOME=$(pwd)
+    cp $src/main.go .
+    go build -o bus main.go
+  '';
+  installPhase = ''
+    mkdir $out
+    install -D bus $out/bin/bus
+  '';
+}
tools/bus/main.go
@@ -0,0 +1,94 @@
+// bus is a smal CLI tool that triggers home builds on build.sr.ht.
+// It is designed to be run in a cron or systemd timer.
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+var (
+	branch = flag.String("branch", "master", "branch to schedule")
+	secret = flag.String("secret", "/etc/secrets/srht-token", "path to find sr.ht secret")
+	builds = flag.String("builds", "/etc/nixos/.builds", "path to find builds manifests")
+)
+
+// 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"`
+}
+
+func triggerBuild(token, name, manifest, branch string) {
+	build := Build{
+		Manifest: manifest,
+		Note:     fmt.Sprintf("Nightly build of '%s' on '%s'", name, branch),
+		Tags: []string{
+			// my branch names tend to contain slashes, which are not valid
+			// identifiers in sourcehut.
+			"home", "nightly", strings.ReplaceAll(branch, "/", "_"), name,
+		},
+	}
+
+	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 {
+		fmt.Fprintf(os.Stderr, "failed to create an HTTP request: %s", err)
+		os.Exit(1)
+	}
+
+	req.Header.Add("Authorization", "token "+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.
+		fmt.Fprintf(os.Stderr, "failed to send build.sr.ht request: %s", err)
+		return
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != 200 {
+		respBody, _ := ioutil.ReadAll(resp.Body)
+		fmt.Fprintf(os.Stderr, "received non-success response from builds.sr.ht: %s (%v)", respBody, resp.Status)
+		os.Exit(1)
+	} else {
+		fmt.Fprintf(os.Stdout, "triggered builds.sr.ht job for branch '%s'\n", branch)
+	}
+}
+
+func main() {
+	flag.Parse()
+	token, err := ioutil.ReadFile(*secret)
+	if err != nil {
+		fmt.Fprint(os.Stderr, "sourcehut token could not be read.")
+		os.Exit(1)
+	}
+	files, err := ioutil.ReadDir(*builds)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "cannot list builds manifest from %s.", *builds)
+		os.Exit(1)
+	}
+	fmt.Fprintf(os.Stdout, "triggering builds for %v\n", *branch)
+	for _, f := range files {
+		path := filepath.Join(*builds, f.Name())
+		manifest, err := ioutil.ReadFile(path)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "cannot read manifest %s.", path)
+			os.Exit(1)
+		}
+		triggerBuild(string(token), f.Name(), string(manifest), *branch)
+	}
+}