Skip to content
quarry Artifact [8ec4465d80]
Login

Artifact [8ec4465d80]

Artifact 8ec4465d80c1fd93d22e208e607906b173b9c13e96d3ead21b6135ed4dc163e1:


// quarry's own CI worker (RFC 0009, dogfooded): each task checks out
// the check-in under test in a fresh clone and runs one Go toolchain
// step, streaming its output to the engine's log lane. Deliberately
// boring — three task types, one helper, no state. Anything worth
// knowing is either in the run's logs (what the tools said) or on this
// process's stdout (what the worker did, one line per task).
//
// Its own module: the log-lane SDK (dagnats#624) is newer than the
// dagnats release quarry's main module pins, and a worker is a
// deployment artifact, not part of the forge.
package main

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strings"
	"time"

	"github.com/danmestas/dagnats/worker"
	"github.com/nats-io/nats.go"
)

var (
	natsURL = flag.String("nats", nats.DefaultURL, "engine NATS address")
	zitBin  = flag.String("zit", "zit", "zit binary the worker shells (pin one so a zit upgrade cannot blind CI mid-flight)")
	workdir = flag.String("work", filepath.Join(os.TempDir(), "quarry-ci-worker"), "scratch directory for clones")
	keep    = flag.Bool("keep", false, "keep a failed step's tree for debugging")
)

func main() {
	flag.Parse()
	if err := os.MkdirAll(*workdir, 0o755); err != nil {
		log.Fatal(err)
	}
	nc, err := nats.Connect(*natsURL)
	if err != nil {
		log.Fatalf("connect %s: %v", *natsURL, err)
	}
	defer nc.Close()
	w := worker.NewWorker(nc)
	step(w, "quarry-test", []string{"go", "test", "./..."}, false)
	step(w, "quarry-build", []string{"go", "build", "./..."}, false)
	// gofmt -l exits 0 even when files need formatting; its OUTPUT is
	// the verdict, so this step fails on any.
	step(w, "quarry-fmt", []string{"gofmt", "-l", "cmd", "internal"}, true)
	log.Printf("quarry-ci-worker ready · nats %s · work %s · zit %s", *natsURL, *workdir, *zitBin)
	w.Start()
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt)
	<-sig
	w.Stop()
}

// step registers one task type: clone, open the check-in, run argv.
func step(w *worker.Worker, task string, argv []string, failOnOutput bool) {
	worker.HandleTyped(w, task, func(ctx worker.TaskContext, in map[string]any) (string, error) {
		repo, _ := in["repo"].(string)
		checkin, _ := in["checkin"].(string)
		clone, _ := in["cloneUrl"].(string)
		if clone == "" || checkin == "" {
			return "", fmt.Errorf("input missing cloneUrl or checkin")
		}
		start := time.Now()
		out, errw := ctx.LogOut(), ctx.LogErr()

		dir, err := os.MkdirTemp(*workdir, task+"-")
		if err != nil {
			return "", err
		}
		failed := false
		defer func() {
			if failed && *keep {
				log.Printf("%s: kept %s", task, dir)
				return
			}
			os.RemoveAll(dir)
		}()
		fail := func(err error) (string, error) {
			failed = true
			log.Printf("%s %s @ %.12s: FAIL in %s: %v", task, repo, checkin, time.Since(start).Round(time.Second), err)
			return "", err
		}

		run := func(extra io.Writer, name string, args ...string) error {
			fmt.Fprintf(out, "$ %s %s\n", name, strings.Join(args, " "))
			cmd := exec.Command(name, args...)
			cmd.Dir = dir
			cmd.Stdout, cmd.Stderr = out, errw
			if extra != nil {
				cmd.Stdout = io.MultiWriter(out, extra)
			}
			return cmd.Run()
		}

		if err := run(nil, *zitBin, "clone", clone, "."); err != nil {
			return fail(fmt.Errorf("clone: %w", err))
		}
		// zit 0.2.0 opens the tree during clone; moving to the check-in
		// under test is then an update. Older zits still want the open.
		if err := run(nil, *zitBin, "update", checkin); err != nil {
			if oerr := run(nil, *zitBin, "open", checkin); oerr != nil {
				return fail(fmt.Errorf("open %s: %w", checkin, err))
			}
		}

		var caught bytes.Buffer
		var sink io.Writer
		if failOnOutput {
			sink = &caught
		}
		if err := run(sink, argv[0], argv[1:]...); err != nil {
			return fail(err)
		}
		if failOnOutput && strings.TrimSpace(caught.String()) != "" {
			n := len(strings.Fields(caught.String()))
			fmt.Fprintf(errw, "%d file(s) need gofmt\n", n)
			return fail(fmt.Errorf("%d file(s) need gofmt", n))
		}
		log.Printf("%s %s @ %.12s: ok in %s", task, repo, checkin, time.Since(start).Round(time.Second))
		return "ok", nil
	})
}