mirror of
https://github.com/juanfont/headscale.git
synced 2026-09-26 18:24:54 +09:00
f1186d8e00
gateFlake judges the formatting check right after the lock moves, but the fmt area runs last, so the new toolchain's formatting had not been applied yet and the flake area dropped itself every run.
176 lines
5.1 KiB
Go
176 lines
5.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// generatedPaths is every file the generators are allowed to touch. Anything
|
|
// outside it means a generator reached further than expected, and the area is
|
|
// dropped rather than committed.
|
|
var generatedPaths = map[string]bool{
|
|
"hscontrol/types/types_clone.go": true,
|
|
"hscontrol/types/types_view.go": true,
|
|
"hscontrol/capver/capver_generated.go": true,
|
|
"hscontrol/capver/capver_test_data.go": true,
|
|
"gen/client/v1/client.gen.go": true,
|
|
"gen/client/v2/client.gen.go": true,
|
|
".github/workflows/test-integration.yaml": true,
|
|
}
|
|
|
|
// oapiPin finds the oapi-codegen version the Makefile pins, so the clients are
|
|
// regenerated with the same tool a developer running `make client` would get.
|
|
var oapiPin = regexp.MustCompile(`github\.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@(v[\w.\-]+)`)
|
|
|
|
var (
|
|
errNoOapiPin = errors.New("no oapi-codegen pin found in Makefile")
|
|
errStrayWrite = errors.New("generator wrote outside the generated set")
|
|
)
|
|
|
|
func oapiVersion(r *repo) (string, error) {
|
|
mk, err := r.readFile("Makefile")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
m := oapiPin.FindStringSubmatch(mk)
|
|
if m == nil {
|
|
return "", errNoOapiPin
|
|
}
|
|
|
|
return m[1], nil
|
|
}
|
|
|
|
// generateClients repeats the Makefile's client recipe. The bot does not shell
|
|
// out to make, but check-generated.yml still runs the real target and diffs, so
|
|
// any drift between the two surfaces on the bot's own pull request.
|
|
func generateClients(ctx context.Context, r *repo) error {
|
|
version, err := oapiVersion(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tmp, err := os.CreateTemp("", "headscale-openapi-3.0.*.yaml")
|
|
if err != nil {
|
|
return fmt.Errorf("creating temporary spec: %w", err)
|
|
}
|
|
|
|
spec := tmp.Name()
|
|
|
|
tmp.Close()
|
|
defer os.Remove(spec)
|
|
|
|
tool := "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@" + version
|
|
|
|
for _, step := range [][]string{
|
|
{"go", "run", "./cmd/gen-openapi", "-downgrade", spec},
|
|
{"go", "run", tool, "-generate", "types,client", "-package", "clientv1", "-o", "gen/client/v1/client.gen.go", spec},
|
|
{"go", "run", "./cmd/gen-openapi", "-api", "v2", "-downgrade", spec},
|
|
{"go", "run", tool, "-generate", "types,client", "-package", "clientv2", "-o", "gen/client/v2/client.gen.go", spec},
|
|
} {
|
|
if _, err := r.nixRun(ctx, step...); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// applyGenerate refreshes every checked-in generated file. Most of the churn
|
|
// here is not caused by this repository at all: the capability-version table is
|
|
// scraped from tailscale's published tags, so it goes stale on an untouched
|
|
// tree the moment upstream ships a release.
|
|
func applyGenerate(ctx context.Context, r *repo) (change, error) {
|
|
if _, err := r.nixRun(ctx, "go", "generate", "./..."); err != nil {
|
|
return change{}, err
|
|
}
|
|
|
|
if err := generateClients(ctx, r); err != nil {
|
|
return change{}, err
|
|
}
|
|
|
|
// go generate ./... skips dot-directories, so the integration matrix
|
|
// generator has to be invoked from inside .github/workflows.
|
|
if _, err := r.nixRunIn(ctx, ".github/workflows", "go", "generate"); err != nil {
|
|
return change{}, err
|
|
}
|
|
|
|
touched, err := changedFiles(ctx, r)
|
|
if err != nil {
|
|
return change{}, err
|
|
}
|
|
|
|
if len(touched) == 0 {
|
|
return change{Empty: true}, nil
|
|
}
|
|
|
|
return change{
|
|
Summary: strings.Join(touched, ", "),
|
|
Detail: touched,
|
|
}, nil
|
|
}
|
|
|
|
// gateGenerate refuses a generator run that reached outside the known set.
|
|
func gateGenerate(ctx context.Context, r *repo) error {
|
|
touched, err := changedFiles(ctx, r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, f := range touched {
|
|
if !generatedPaths[f] {
|
|
return fmt.Errorf("%w: %s", errStrayWrite, f)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// applyFormat re-runs everything that decides whether the tree is acceptable to
|
|
// CI, and commits whatever it rewrites. Two reasons it cannot be skipped:
|
|
// nixpkgs picks which prettier and gofumpt the tree is formatted with, so a
|
|
// lock bump reformats files no area touched; and the bot rewrites workflow YAML
|
|
// and Dockerfiles, where a stray trailing space or an unformatted .nix fails a
|
|
// hook rather than the compiler.
|
|
//
|
|
// The first hook pass is expected to fail, because a hook that rewrites a file
|
|
// reports failure. The second pass is the real verdict.
|
|
func applyFormat(ctx context.Context, r *repo) (change, error) {
|
|
if err := runFormatters(ctx, r); err != nil { //nolint:noinlineerr
|
|
return change{}, err
|
|
}
|
|
|
|
touched, err := changedFiles(ctx, r)
|
|
if err != nil {
|
|
return change{}, err
|
|
}
|
|
|
|
if len(touched) == 0 {
|
|
return change{Empty: true}, nil
|
|
}
|
|
|
|
return change{
|
|
Summary: fmt.Sprintf("%d file(s) reformatted", len(touched)),
|
|
Detail: touched,
|
|
}, nil
|
|
}
|
|
|
|
// runFormatters brings the tree up to whatever the current toolchain considers
|
|
// formatted. The first hook pass is expected to fail, because a hook that
|
|
// rewrites a file reports failure; the second pass is the verdict.
|
|
func runFormatters(ctx context.Context, r *repo) error {
|
|
if _, err := r.nixRun(ctx, "make", "fmt"); err != nil { //nolint:noinlineerr
|
|
return err
|
|
}
|
|
|
|
_, _ = r.nixRun(ctx, "prek", "run", "--all-files")
|
|
|
|
_, err := r.nixRun(ctx, "prek", "run", "--all-files")
|
|
|
|
return err
|
|
}
|