diff --git a/cmd/releasekit/releasekit_delivery_workflow_test.go b/cmd/releasekit/releasekit_delivery_workflow_test.go index 54583e9..be0bdb5 100644 --- a/cmd/releasekit/releasekit_delivery_workflow_test.go +++ b/cmd/releasekit/releasekit_delivery_workflow_test.go @@ -47,17 +47,7 @@ func TestReleaseKitSourceDeliveryWorkflowFromTaggedCheckout(t *testing.T) { if err := os.WriteFile(notePath, []byte(note), 0o600); err != nil { t.Fatal(err) } - ignore, err := os.OpenFile(filepath.Join(repository, ".gitignore"), os.O_APPEND|os.O_WRONLY, 0) - if err != nil { - t.Fatal(err) - } - if _, err := ignore.WriteString("\n/.releasekit/\n"); err != nil { - t.Fatal(err) - } - if err := ignore.Close(); err != nil { - t.Fatal(err) - } - cliGitRun(t, repository, "add", "docs/releases/"+version+".md", ".gitignore") + cliGitRun(t, repository, "add", "docs/releases/"+version+".md") cliGitRun(t, repository, "commit", "-m", "prepare source-only delivery") state := &deliveryServerState{product: "ReleaseKit"} @@ -78,16 +68,11 @@ func TestReleaseKitSourceDeliveryWorkflowFromTaggedCheckout(t *testing.T) { cliGitRun(t, repository, "remote", "set-url", "origin", remote) cliGitRun(t, repository, "push", "origin", "HEAD:refs/heads/main", "refs/tags/"+version) - tool := filepath.Join(repository, ".releasekit", "bin", "releasekit") template, err := os.ReadFile(filepath.Join(root, "examples", "woodpecker", "releasekit-source-delivery.yml")) if err != nil { t.Fatal(err) } - buildBlock, err := extractWoodpeckerCommand(string(template), "build-tool") - if err != nil { - t.Fatal(err) - } - deliveryBlock, err := extractWoodpeckerCommand(string(template), "deliver-page") + block, err := extractWoodpeckerCommand(string(template), "deliver") if err != nil { t.Fatal(err) } @@ -96,19 +81,14 @@ func TestReleaseKitSourceDeliveryWorkflowFromTaggedCheckout(t *testing.T) { "CI_COMMIT_SHA": state.commit, "CI_COMMIT_TAG": version, "RELEASEKIT_GITEA_TOKEN": "fixture-token", + "GOTOOLCHAIN": "local", "GOPROXY": "off", "GOSUMDB": "off", } - runWoodpeckerBlock(t, strings.ReplaceAll(buildBlock, "$${", "${"), repository, env) - if got := cliGitOutput(t, repository, "rev-parse", "HEAD"); got != state.commit { - t.Fatalf("template checkout = %s, want %s", got, state.commit) - } + command := strings.ReplaceAll(block, "$${", "${") state.remoteCommit = strings.Repeat("f", 40) - failure := exec.Command(tool, "deliver", "--repo", repository, version) - failure.Dir = repository - failure.Env = append(os.Environ(), "RELEASEKIT_GITEA_TOKEN=fixture-token") - if output, err := failure.CombinedOutput(); err == nil { + if output, err := executeWoodpeckerBlock(command, repository, env); err == nil { t.Fatalf("delivery with a mismatched hosted tag succeeded: %s", output) } if state.releaseCreates != 0 || cliGitOutput(t, repository, "rev-parse", "refs/tags/"+version+"^{commit}") != state.commit { @@ -117,7 +97,7 @@ func TestReleaseKitSourceDeliveryWorkflowFromTaggedCheckout(t *testing.T) { state.remoteCommit = "" for attempt := 0; attempt < 2; attempt++ { - output := runWoodpeckerBlock(t, strings.ReplaceAll(deliveryBlock, "$${", "${"), repository, env) + output := runWoodpeckerBlock(t, command, repository, env) if !strings.Contains(output, "state complete") { t.Fatalf("delivery attempt %d output = %q", attempt+1, output) } diff --git a/cmd/releasekit/woodpecker_test.go b/cmd/releasekit/woodpecker_test.go index 8af7d85..9f70baf 100644 --- a/cmd/releasekit/woodpecker_test.go +++ b/cmd/releasekit/woodpecker_test.go @@ -1,6 +1,7 @@ package main import ( + "archive/zip" "errors" "fmt" "os" @@ -145,6 +146,135 @@ func TestWoodpeckerTagDeliveryTemplate(t *testing.T) { }) } +func TestWoodpeckerTagDeliveryUsesTaggedModuleVersion(t *testing.T) { + root := cliRepositoryRoot(t) + contents, err := os.ReadFile(filepath.Join(root, "examples", "woodpecker", "tag-delivery.yml")) + if err != nil { + t.Fatal(err) + } + block, err := extractWoodpeckerCommand(string(contents), "deliver") + if err != nil { + t.Fatal(err) + } + command := strings.ReplaceAll(block, "$${", "${") + proxy := writeReleaseKitModuleProxy(t) + + t.Run("older tag retry uses its committed module selection", func(t *testing.T) { + goDir, goLog := installGoBootstrapShim(t) + repository := newCLIRepository(t) + const module = "gitea.maximumdirect.net/eric/ReleaseKit" + writeRepositoryFile(t, repository, "go.mod", "module example.com/cli-candidate\n\ngo 1.25.0\n\nrequire "+module+" v0.2.197\n") + cliGitRun(t, repository, "add", "go.mod") + cliGitRun(t, repository, "commit", "-m", "select the earlier ReleaseKit version") + oldCommit := cliGitOutput(t, repository, "rev-parse", "HEAD") + cliGitRun(t, repository, "tag", "v1.2.3") + writeRepositoryFile(t, repository, "go.mod", "module example.com/cli-candidate\n\ngo 1.25.0\n\nrequire "+module+" v0.2.198\n") + cliGitRun(t, repository, "add", "go.mod") + cliGitRun(t, repository, "commit", "-m", "upgrade the default branch tool") + cliGitRun(t, repository, "init", "--bare", filepath.Join(filepath.Dir(repository), "origin.git")) + remote := filepath.Join(filepath.Dir(repository), "origin.git") + cliGitRun(t, repository, "remote", "add", "origin", remote) + cliGitRun(t, repository, "push", "origin", "refs/heads/main", "refs/tags/v1.2.3") + cliGitRun(t, repository, "checkout", "--detach", oldCommit) + if mainModule := cliGitOutput(t, repository, "show", "main:go.mod"); !strings.Contains(mainModule, module+" v0.2.198") { + t.Fatalf("default branch module = %q, want upgraded tool selection", mainModule) + } + moduleBefore := readWoodpeckerFixtureFile(t, filepath.Join(repository, "go.mod")) + commit := oldCommit + env := templateEnvironment(repository, commit, goDir, goLog) + env["GOPROXY"] = "file://" + filepath.ToSlash(proxy) + sumLog := filepath.Join(t.TempDir(), "tool-only-sums") + env["RELEASEKIT_SUM_LOG"] = sumLog + output := runWoodpeckerBlock(t, command, repository, env) + if !strings.Contains(output, "tool-version=v0.2.197") || !strings.Contains(output, "helper=tool-only dependency") || strings.Contains(output, "tool-version=v0.2.198") { + t.Fatalf("retry selected the wrong tool version: %q", output) + } + assertModuleBootstrap(t, repository, goLog, moduleBefore) + assertToolOnlyChecksums(t, sumLog) + }) + + t.Run("versioned replacement selects its declared source", func(t *testing.T) { + goDir, goLog := installGoBootstrapShim(t) + repository := newCLIRepository(t) + const module = "gitea.maximumdirect.net/eric/ReleaseKit" + goMod := "module example.com/cli-candidate\n\ngo 1.25.0\n\nrequire " + module + " v0.2.197\n\nreplace " + module + " v0.2.197 => " + module + " v0.2.198\n" + writeRepositoryFile(t, repository, "go.mod", goMod) + cliGitRun(t, repository, "add", "go.mod") + cliGitRun(t, repository, "commit", "-m", "select a versioned tool replacement") + commit := pushTaggedCLIRepository(t, repository) + moduleBefore := readWoodpeckerFixtureFile(t, filepath.Join(repository, "go.mod")) + env := templateEnvironment(repository, commit, goDir, goLog) + env["GOPROXY"] = "file://" + filepath.ToSlash(proxy) + sumLog := filepath.Join(t.TempDir(), "tool-only-sums") + env["RELEASEKIT_SUM_LOG"] = sumLog + output := runWoodpeckerBlock(t, command, repository, env) + if !strings.Contains(output, "tool-version=v0.2.198") || !strings.Contains(output, "helper=tool-only dependency") { + t.Fatalf("versioned replacement was not selected: %q", output) + } + assertModuleBootstrap(t, repository, goLog, moduleBefore) + assertToolOnlyChecksums(t, sumLog) + }) +} + +func TestWoodpeckerSourceDeliveryTemplateSelfBootstraps(t *testing.T) { + root := cliRepositoryRoot(t) + contents, err := os.ReadFile(filepath.Join(root, "examples", "woodpecker", "releasekit-source-delivery.yml")) + if err != nil { + t.Fatal(err) + } + block, err := extractWoodpeckerCommand(string(contents), "deliver") + if err != nil { + t.Fatal(err) + } + command := strings.ReplaceAll(block, "$${", "${") + repository := newCLIRepository(t) + writeRepositoryFile(t, repository, "cmd/releasekit/main.go", "package main\n\nimport (\"fmt\"; \"os\")\n\nfunc main() {\n\tfmt.Println(\"self-bootstrap\")\n\tfmt.Printf(\"tool-args=%q\\n\", os.Args[1:])\n}\n") + cliGitRun(t, repository, "add", "cmd/releasekit/main.go") + cliGitRun(t, repository, "commit", "-m", "add the self-bootstrapped command") + commit := pushTaggedCLIRepository(t, repository) + goDir, goLog := installGoBootstrapShim(t) + env := templateEnvironment(repository, commit, goDir, goLog) + output := runWoodpeckerBlock(t, command, repository, env) + if !strings.Contains(output, "self-bootstrap") || !strings.Contains(output, `"`+commit+`"`) { + t.Fatalf("self-bootstrap output = %q", output) + } + if !strings.Contains(output, `"ci-deliver"`) || !strings.Contains(output, `"`+repository+`"`) || !strings.Contains(output, `"v1.2.3"`) { + t.Fatalf("self-bootstrapped command arguments = %q", output) + } + log := readWoodpeckerFixtureFile(t, goLog) + if !strings.Contains(log, "GOWORK=off GOTOOLCHAIN=local") || !strings.Contains(log, "build -mod=readonly -o ") || !strings.Contains(log, "./cmd/releasekit") { + t.Fatalf("self-bootstrap Go command = %q", log) + } + var executable string + for index, field := range strings.Fields(log) { + if field == "-o" && index+1 < len(strings.Fields(log)) { + executable = strings.Fields(log)[index+1] + break + } + } + if executable == "" { + t.Fatalf("self-bootstrap output path missing from Go command: %q", log) + } + if relative, err := filepath.Rel(repository, executable); err != nil || relative == "." || (!strings.HasPrefix(relative, ".."+string(filepath.Separator)) && relative != "..") { + t.Fatalf("self-bootstrap executable %q is not outside source %q: relative=%q error=%v", executable, repository, relative, err) + } + if status := cliGitOutput(t, repository, "status", "--porcelain", "--untracked-files=all"); status != "" { + t.Fatalf("self-bootstrap changed the tagged source: %q", status) + } + if branch := cliGitOutput(t, repository, "rev-parse", "--abbrev-ref", "HEAD"); branch != "HEAD" { + t.Fatalf("self-bootstrap checkout branch = %q, want detached HEAD", branch) + } + entries, err := os.ReadDir(filepath.Dir(repository)) + if err != nil { + t.Fatal(err) + } + for _, entry := range entries { + if strings.HasPrefix(entry.Name(), ".releasekit-source.") { + t.Fatalf("self-bootstrap temporary directory remains: %s", entry.Name()) + } + } +} + func cliRepositoryRoot(t *testing.T) string { t.Helper() _, sourceFile, _, ok := runtime.Caller(0) @@ -195,7 +325,16 @@ func installGoBootstrapShim(t *testing.T) (string, string) { goLog := filepath.Join(shimDir, "go-commands") shim := `#!/bin/sh printf 'cwd=%s GOWORK=%s GOTOOLCHAIN=%s %s\n' "$PWD" "$GOWORK" "$GOTOOLCHAIN" "$*" >> "$RELEASEKIT_GO_LOG" -exec "$RELEASEKIT_REAL_GO" "$@" +"$RELEASEKIT_REAL_GO" "$@" +status=$? +if [ "$status" -eq 0 ] && [ "$1" = "build" ] && [ -n "${RELEASEKIT_SUM_LOG:-}" ]; then + modfile= + for argument in "$@"; do + case "$argument" in -modfile=*) modfile=${argument#-modfile=} ;; esac + done + cp "${modfile%.mod}.sum" "$RELEASEKIT_SUM_LOG" +fi +exit "$status" ` if err := os.WriteFile(filepath.Join(shimDir, "go"), []byte(shim), 0o700); err != nil { t.Fatal(err) @@ -299,6 +438,80 @@ func selectReleaseKitModule(t *testing.T, repository, releaseKitSource string, i cliGitRun(t, repository, "commit", "-m", "select ReleaseKit from module configuration") } +func writeReleaseKitModuleProxy(t *testing.T) string { + t.Helper() + const module = "gitea.maximumdirect.net/eric/ReleaseKit" + const toolDependency = "example.com/releasekit-helper" + proxyDir := t.TempDir() + toolModule := "module " + module + "\n\ngo 1.25.0\n\nrequire " + toolDependency + " v1.0.0\n" + for _, version := range []string{"v0.2.197", "v0.2.198"} { + files := map[string]string{ + "cmd/releasekit/main.go": "package main\n\nimport (\"fmt\"; \"os\"; tooldep \"example.com/releasekit-helper\")\n\nfunc main() {\n\tfmt.Println(\"tool-version=" + version + " helper=\" + tooldep.Version)\n\tfmt.Printf(\"tool-args=%q\\n\", os.Args[1:])\n}\n", + } + writeModuleProxyVersion(t, proxyDir, module, version, toolModule, files) + } + writeModuleProxyVersion(t, proxyDir, toolDependency, "v1.0.0", "module "+toolDependency+"\n\ngo 1.25.0\n", map[string]string{"tooldep.go": "package tooldep\n\nconst Version = \"tool-only dependency\"\n"}) + return proxyDir +} + +func writeModuleProxyVersion(t *testing.T, proxyDir, module, version, moduleFile string, files map[string]string) { + t.Helper() + var escaped strings.Builder + for _, character := range module { + if character >= 'A' && character <= 'Z' { + escaped.WriteByte('!') + character += 'a' - 'A' + } + escaped.WriteRune(character) + } + moduleVersions := filepath.Join(proxyDir, filepath.FromSlash(escaped.String()), "@v") + if err := os.MkdirAll(moduleVersions, 0o700); err != nil { + t.Fatal(err) + } + if _, err := os.Stat(filepath.Join(moduleVersions, "list")); os.IsNotExist(err) { + if err := os.WriteFile(filepath.Join(moduleVersions, "list"), []byte(version+"\n"), 0o600); err != nil { + t.Fatal(err) + } + } else { + listPath := filepath.Join(moduleVersions, "list") + list := readWoodpeckerFixtureFile(t, listPath) + if !strings.Contains(list, version+"\n") { + if err := os.WriteFile(listPath, []byte(list+version+"\n"), 0o600); err != nil { + t.Fatal(err) + } + } + } + if err := os.WriteFile(filepath.Join(moduleVersions, version+".mod"), []byte(moduleFile), 0o600); err != nil { + t.Fatal(err) + } + info := fmt.Sprintf("{\"Version\":%q,\"Time\":\"2026-10-01T00:00:00Z\"}\n", version) + if err := os.WriteFile(filepath.Join(moduleVersions, version+".info"), []byte(info), 0o600); err != nil { + t.Fatal(err) + } + archive, err := os.Create(filepath.Join(moduleVersions, version+".zip")) + if err != nil { + t.Fatal(err) + } + writer := zip.NewWriter(archive) + prefix := module + "@" + version + "/" + files["go.mod"] = moduleFile + for name, contents := range files { + file, err := writer.Create(prefix + name) + if err != nil { + t.Fatal(err) + } + if _, err := file.Write([]byte(contents)); err != nil { + t.Fatal(err) + } + } + if err := writer.Close(); err != nil { + t.Fatal(err) + } + if err := archive.Close(); err != nil { + t.Fatal(err) + } +} + func assertModuleBootstrap(t *testing.T, repository, goLog, moduleBefore string, sumBefore ...string) { t.Helper() assertGoSelection(t, repository, goLog) @@ -371,6 +584,14 @@ func assertNoCommands(t *testing.T, goLog string) { } } +func assertToolOnlyChecksums(t *testing.T, path string) { + t.Helper() + sums := readWoodpeckerFixtureFile(t, path) + if !strings.Contains(sums, "example.com/releasekit-helper v1.0.0 h1:") { + t.Fatalf("temporary module sum file omitted the tool-only dependency checksum: %q", sums) + } +} + func readWoodpeckerFixtureFile(t *testing.T, path string) string { t.Helper() contents, err := os.ReadFile(path) diff --git a/docs/cli.md b/docs/cli.md index 8095be5..4a0d28f 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -220,3 +220,13 @@ preparation failures return status 1; usage, profile, credential, and invalid version errors return status 2. Final delivery uses the same statuses as `deliver`: 0 for complete, 1 for known failure, conflict, or partial delivery, and 3 for uncertain remote state. + +The maintained module-based tag workflow verifies and checks out the event tag +before resolving the consumer module. It builds the ReleaseKit version already +selected by that tagged `go.mod`, using temporary alternate module files outside +the source checkout. This tool bootstrap is separate from `ci-deliver`'s +application dependency preparation for binary profiles. ReleaseKit's own +source workflow self-bootstraps `./cmd/releasekit`; consumers without a declared +ReleaseKit module dependency can use the separately installed-tool command. +See the [delivery guide](consumers/delivery.md#woodpecker-tag-workflows) for +compatible Go environments, retries, and preparation details. diff --git a/docs/config.md b/docs/config.md index 7d5e14b..199bbd5 100644 --- a/docs/config.md +++ b/docs/config.md @@ -181,6 +181,14 @@ dependencies. This preparation may access the network and may update independent of Woodpecker environment variable names. See the [CLI contract](cli.md) for the invocation and exit statuses. +This application dependency preparation is separate from bootstrapping the +ReleaseKit executable. The maintained module-based tag workflow verifies the +tag first, then uses temporary copies of that tag's module files to build the +ReleaseKit version selected by the consumer's `go.mod`. The +[delivery guide](consumers/delivery.md#woodpecker-tag-workflows) describes that +bootstrap, the self-bootstrapped source workflow, and the separately installed +tool option. + ReleaseKit's source-only profile is [releasekit.json](../releasekit.json). See the [source-only profile](../examples/releasekit.json) and diff --git a/docs/consumers/delivery.md b/docs/consumers/delivery.md index 305cc8a..7c745e7 100644 --- a/docs/consumers/delivery.md +++ b/docs/consumers/delivery.md @@ -150,28 +150,44 @@ exit codes, and transport behavior. Use the maintained [tag-delivery workflow](../../examples/woodpecker/tag-delivery.yml) in the consumer repository's `.woodpecker/` directory. Set `GO_IMAGE` to an -image with Go 1.25 or newer, Git, and a Go version compatible with the consumer -module. Replace `RELEASEKIT_VERSION` with an immutable ReleaseKit revision or -published version. The example selects the initial `v0.1.0` baseline; update -that pin when adopting a later release. Configure the Woodpecker repository secret -`releasekit_gitea_token`; the template maps it to `RELEASEKIT_GITEA_TOKEN` only -for the delivery step. The repository needs a `delivery` profile. Page-only and -binary behavior both use this template; the profile's build target list selects -whether binaries are built and delivered. +image with Go 1.25 or newer, Git, and a local Go version compatible with the +consumer module. It uses `GOTOOLCHAIN=local`; Go does not download a different +toolchain. The tag's `go.mod` must select ReleaseKit as a module dependency. +Configure the Woodpecker repository secret `releasekit_gitea_token`; the +template maps it to `RELEASEKIT_GITEA_TOKEN` for delivery. The repository needs +a `delivery` profile. Page-only and binary behavior both use this template; the +profile's build target list selects whether application binaries are built and +delivered. -The command runs versioned source directly with `go run`, passing -`CI_WORKSPACE`, `CI_COMMIT_SHA`, and `CI_COMMIT_TAG` as command arguments. -`ci-deliver` fetches and verifies the event tag against its commit before -checking out the commit detached. It then loads the profile from that source, -validates the destination and token, and downloads application dependencies -only for profiles with build targets. The facade builds and delivers those -targets with the note page. Source-only profiles skip dependency downloads and -binary work. The workflow does not repeat checkout guards or maintain a -separate build/receipt/delivery sequence. +Before running Go, the template fetches the explicit event tag, compares its +peeled commit with the provider's full commit ID without regard to hex case, +checks out that commit detached, and confirms the source tree is clean. It then +copies the tagged `go.mod` and any `go.sum` to a temporary directory outside +the checkout. `go list -m` confirms that the module selects ReleaseKit; Go uses +an alternate `-modfile` to prepare dependencies and build the selected +`cmd/releasekit` package without changing the tagged module files. The template +invokes that executable with `CI_WORKSPACE`, `CI_COMMIT_SHA`, and +`CI_COMMIT_TAG`. A cleanup trap removes the temporary metadata and executable. + +Tool bootstrap and application artifact preparation are separate. The bootstrap +resolves and builds ReleaseKit from the tagged consumer's module configuration. +Afterward, `ci-deliver` loads the profile and downloads application dependencies +only when build targets are selected; it builds and delivers those targets with +the note page. Source-only profiles skip application dependency downloads and +binary work. Dependency preparation for binaries may use the network and may +update an uncommitted checksum file in the checkout. A retry still uses the +same immutable tag and that tag's module settings, even after the default branch +selects a newer ReleaseKit version. + +If the application does not declare ReleaseKit as a module dependency, install +the executable separately and invoke the same `ci-deliver` command. For +ReleaseKit's own source workflow, the maintained +[source-delivery template](../../examples/woodpecker/releasekit-source-delivery.yml) +checks out the tag and self-bootstraps `./cmd/releasekit` into temporary storage. The normal Go module and build caches are sufficient. Cache mounts may be added as deployment configuration when they help the runner; they are not needed by -the template. A locally retained executable can be installed with +the template. A separately installed executable can be installed with `go install gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit@PIN`, then run with the same arguments: @@ -179,9 +195,10 @@ with the same arguments: releasekit ci-deliver --repo "$CI_WORKSPACE" --commit "$CI_COMMIT_SHA" "$CI_COMMIT_TAG" ``` -Upgrade by changing the selected immutable -`RELEASEKIT_VERSION` (or the `@PIN` used by `go install`) and validating the -workflow against a tag fixture. The command and profile behavior are documented +The module-based workflow follows the ReleaseKit version selected by the tagged +consumer's `go.mod`, so retries use that source's original selection. Update the +consumer module configuration when adopting a newer ReleaseKit version and +verify it with a tag fixture. The command and profile behavior are documented in the [CLI contract](../cli.md) and [configuration reference](../config.md). Woodpecker runs delivery after the tag event, independently from the source diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 62d68b9..aee350b 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -547,6 +547,15 @@ and propagation of its exit status. Existing delivery tests own hosted behavior. ## Stage 13: Verify upgrades, retries, and bootstrap variants +**Completion:** Complete. The tag workflow is exercised against a local module +proxy with two tool versions, an older tagged configuration after the default +branch upgrades, and a versioned replacement. Missing tool-only dependency +checksums are prepared in temporary module metadata. The ReleaseKit source +workflow verifies its tag before self-bootstrap and writes the executable +outside the checkout. Delivery, configuration, and CLI guidance now distinguish +tool bootstrap from application dependency preparation and describe +immutable-source retries and installed-tool use. + **Outcome:** Tool selection follows the triggering release across supported uses. 1. Extend Stage 12's bootstrap fixtures with a local module proxy for two tool diff --git a/examples/woodpecker/releasekit-source-delivery.yml b/examples/woodpecker/releasekit-source-delivery.yml index 9e85691..039f1f0 100644 --- a/examples/woodpecker/releasekit-source-delivery.yml +++ b/examples/woodpecker/releasekit-source-delivery.yml @@ -3,27 +3,33 @@ when: ref: refs/tags/v* steps: - - name: build-tool - image: golang:1.25.0-bookworm - commands: - - | - test -n "$${CI_COMMIT_TAG}" - test -n "$${CI_COMMIT_SHA}" - git fetch origin "refs/tags/$${CI_COMMIT_TAG}:refs/tags/$${CI_COMMIT_TAG}" - test "$(git rev-parse "refs/tags/$${CI_COMMIT_TAG}^{commit}")" = "$${CI_COMMIT_SHA}" - git checkout --detach "$${CI_COMMIT_SHA}" - test "$(git rev-parse HEAD)" = "$${CI_COMMIT_SHA}" - mkdir -p "$${CI_WORKSPACE}/.releasekit/bin" - GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off go build -o "$${CI_WORKSPACE}/.releasekit/bin/releasekit" ./cmd/releasekit - - - name: deliver-page + - name: deliver image: golang:1.25.0-bookworm environment: RELEASEKIT_GITEA_TOKEN: from_secret: releasekit_gitea_token commands: - | - "$${CI_WORKSPACE}/.releasekit/bin/releasekit" ci-deliver \ + set -eu + test -n "$${CI_COMMIT_TAG}" + test -n "$${CI_COMMIT_SHA}" + git check-ref-format "refs/tags/$${CI_COMMIT_TAG}" + git fetch origin "refs/tags/$${CI_COMMIT_TAG}:refs/tags/$${CI_COMMIT_TAG}" + tag_commit=$(git rev-parse "refs/tags/$${CI_COMMIT_TAG}^{commit}") + expected_commit=$(printf '%s' "$${CI_COMMIT_SHA}" | tr '[:upper:]' '[:lower:]') + test "$(printf '%s' "$${tag_commit}" | tr '[:upper:]' '[:lower:]')" = "$${expected_commit}" + git checkout --detach "$${tag_commit}" + test "$(git rev-parse HEAD | tr '[:upper:]' '[:lower:]')" = "$${expected_commit}" + test -z "$(git status --porcelain --untracked-files=all)" + + bootstrap_dir=$(mktemp -d "$${CI_WORKSPACE%/*}/.releasekit-source.XXXXXX") + trap 'rm -rf "$${bootstrap_dir}"' EXIT + export GOWORK=off + export GOTOOLCHAIN=local + export GOPROXY=off + export GOSUMDB=off + go build -mod=readonly -o "$${bootstrap_dir}/releasekit" ./cmd/releasekit + "$${bootstrap_dir}/releasekit" ci-deliver \ --repo "$${CI_WORKSPACE}" \ --commit "$${CI_COMMIT_SHA}" \ "$${CI_COMMIT_TAG}"