Use the tagged module selection for CI delivery
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- GO_IMAGE: golang:1.25.5
|
- GO_IMAGE: golang:1.25.5
|
||||||
RELEASEKIT_VERSION: v0.1.0
|
|
||||||
|
|
||||||
when:
|
when:
|
||||||
- event: tag
|
- event: tag
|
||||||
@@ -15,7 +14,36 @@ steps:
|
|||||||
from_secret: GITEA_RELEASE_TOKEN
|
from_secret: GITEA_RELEASE_TOKEN
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
go run gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit@${RELEASEKIT_VERSION} 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-bootstrap.XXXXXX")
|
||||||
|
trap 'rm -rf "$${bootstrap_dir}"' EXIT
|
||||||
|
cp go.mod "$${bootstrap_dir}/go.mod"
|
||||||
|
if [ -f go.sum ]; then
|
||||||
|
cp go.sum "$${bootstrap_dir}/go.sum"
|
||||||
|
else
|
||||||
|
: > "$${bootstrap_dir}/go.sum"
|
||||||
|
fi
|
||||||
|
cp "$${bootstrap_dir}/go.mod" "$${bootstrap_dir}/releasekit.mod"
|
||||||
|
cp "$${bootstrap_dir}/go.sum" "$${bootstrap_dir}/releasekit.sum"
|
||||||
|
|
||||||
|
export GOWORK=off
|
||||||
|
export GOTOOLCHAIN=local
|
||||||
|
releasekit_module=gitea.maximumdirect.net/eric/ReleaseKit
|
||||||
|
go list -mod=mod -modfile="$${bootstrap_dir}/releasekit.mod" -m "$${releasekit_module}"
|
||||||
|
go build -mod=mod -modfile="$${bootstrap_dir}/releasekit.mod" \
|
||||||
|
-o "$${bootstrap_dir}/releasekit" "$${releasekit_module}/cmd/releasekit"
|
||||||
|
"$${bootstrap_dir}/releasekit" ci-deliver \
|
||||||
--repo "$${CI_WORKSPACE}" \
|
--repo "$${CI_WORKSPACE}" \
|
||||||
--commit "$${CI_COMMIT_SHA}" \
|
--commit "$${CI_COMMIT_SHA}" \
|
||||||
"$${CI_COMMIT_TAG}"
|
"$${CI_COMMIT_TAG}"
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ GOWORK=off GOTOOLCHAIN=local go mod download
|
|||||||
```
|
```
|
||||||
|
|
||||||
If dependency preparation changes `go.mod` or `go.sum`, include those changes in
|
If dependency preparation changes `go.mod` or `go.sum`, include those changes in
|
||||||
the committed candidate. The release wrapper separately bootstraps the pinned
|
the committed candidate. The release wrapper separately builds the ReleaseKit
|
||||||
ReleaseKit command into ignored `bin/releasekit`; that build is tool preparation,
|
version selected by `go.mod` into ignored `bin/releasekit`. It uses temporary
|
||||||
not candidate validation.
|
copies of `go.mod` and `go.sum` for tool dependency preparation; that build is
|
||||||
|
tool preparation, not candidate validation.
|
||||||
|
|
||||||
## Prepare and publish the candidate
|
## Prepare and publish the candidate
|
||||||
|
|
||||||
@@ -71,29 +72,36 @@ profile, or version input error; and `3` means the push outcome remains unknown.
|
|||||||
|
|
||||||
## Binary delivery
|
## Binary delivery
|
||||||
|
|
||||||
Woodpecker runs the pinned ReleaseKit `ci-deliver` command for tags under
|
Woodpecker runs ReleaseKit's `ci-deliver` command for tags under
|
||||||
`refs/tags/v*`. It verifies the triggering commit against the tag, prepares the
|
`refs/tags/v*`. The tagged `go.mod` selects the ReleaseKit version, and
|
||||||
tagged source, and uses `releasekit.json` to build and deliver:
|
`releasekit.json` supplies Audita's build and delivery settings:
|
||||||
|
|
||||||
- `audita-<version>-linux-amd64`
|
- `audita-<version>-linux-amd64`
|
||||||
- `audita-<version>-linux-arm64`
|
- `audita-<version>-linux-arm64`
|
||||||
- `SHA256SUMS`
|
- `SHA256SUMS`
|
||||||
|
|
||||||
The workflow invokes:
|
Before running Go, the workflow fetches and verifies the event tag, checks it
|
||||||
|
out detached, and confirms the source tree is clean. It copies the tagged
|
||||||
|
`go.mod` and `go.sum` into temporary module metadata outside the checkout. Go
|
||||||
|
confirms that this configuration selects ReleaseKit, then builds its CLI using
|
||||||
|
an alternate `-modfile`. The bootstrap executable and temporary files remain
|
||||||
|
outside the tagged source. The workflow runs with the existing Go 1.25.5 image,
|
||||||
|
`GOWORK=off`, and `GOTOOLCHAIN=local`.
|
||||||
|
|
||||||
|
The resulting executable receives:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go run gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit@${RELEASEKIT_VERSION} ci-deliver \
|
"$${bootstrap_dir}/releasekit" ci-deliver \
|
||||||
--repo "$${CI_WORKSPACE}" \
|
--repo "$${CI_WORKSPACE}" \
|
||||||
--commit "$${CI_COMMIT_SHA}" \
|
--commit "$${CI_COMMIT_SHA}" \
|
||||||
"$${CI_COMMIT_TAG}"
|
"$${CI_COMMIT_TAG}"
|
||||||
```
|
```
|
||||||
|
|
||||||
Woodpecker sets `RELEASEKIT_VERSION` to `v0.1.0` for this workflow.
|
The workflow maps the existing `GITEA_RELEASE_TOKEN` secret to
|
||||||
|
`RELEASEKIT_GITEA_TOKEN`. The complete checked-in release note, including its
|
||||||
The complete checked-in release note, including its licensing footer, supplies
|
licensing footer, supplies the hosted release body. Delivery runs independently
|
||||||
the hosted release body. The workflow maps the existing `GITEA_RELEASE_TOKEN`
|
after source publication; its status and logs report delivery progress or
|
||||||
secret to `RELEASEKIT_GITEA_TOKEN`. Delivery runs independently after source
|
failure.
|
||||||
publication; its status and logs report delivery progress or failure.
|
|
||||||
|
|
||||||
ReleaseKit handles content-aware retries and retained-artifact recovery. See
|
ReleaseKit handles content-aware retries and retained-artifact recovery. See
|
||||||
the pinned [publication recovery guide](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.1.0/docs/consumers/publish.md)
|
the pinned [publication recovery guide](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.1.0/docs/consumers/publish.md)
|
||||||
|
|||||||
@@ -3,7 +3,23 @@ set -euo pipefail
|
|||||||
|
|
||||||
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||||
cd "$script_dir/.."
|
cd "$script_dir/.."
|
||||||
|
|
||||||
|
releasekit_module=gitea.maximumdirect.net/eric/ReleaseKit
|
||||||
|
bootstrap_dir=$(mktemp -d "${TMPDIR:-/tmp}/releasekit-bootstrap.XXXXXX")
|
||||||
|
trap 'rm -rf "$bootstrap_dir"' EXIT
|
||||||
|
cp go.mod "$bootstrap_dir/go.mod"
|
||||||
|
if [[ -f go.sum ]]; then
|
||||||
|
cp go.sum "$bootstrap_dir/go.sum"
|
||||||
|
else
|
||||||
|
: > "$bootstrap_dir/go.sum"
|
||||||
|
fi
|
||||||
|
cp "$bootstrap_dir/go.mod" "$bootstrap_dir/releasekit.mod"
|
||||||
|
cp "$bootstrap_dir/go.sum" "$bootstrap_dir/releasekit.sum"
|
||||||
|
|
||||||
mkdir -p bin
|
mkdir -p bin
|
||||||
GOWORK=off GOTOOLCHAIN=local go build -mod=readonly \
|
GOWORK=off GOTOOLCHAIN=local go list -mod=mod -modfile="$bootstrap_dir/releasekit.mod" -m "$releasekit_module" >/dev/null
|
||||||
-o ./bin/releasekit gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit
|
GOWORK=off GOTOOLCHAIN=local go build -mod=mod -modfile="$bootstrap_dir/releasekit.mod" \
|
||||||
|
-o ./bin/releasekit "$releasekit_module/cmd/releasekit"
|
||||||
|
rm -rf "$bootstrap_dir"
|
||||||
|
trap - EXIT
|
||||||
exec ./bin/releasekit "$@"
|
exec ./bin/releasekit "$@"
|
||||||
|
|||||||
@@ -11,33 +11,55 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const goStub = `#!/usr/bin/env bash
|
const goStub = `#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
printf '%s\0' "$PWD" "GOWORK=$GOWORK" "GOTOOLCHAIN=$GOTOOLCHAIN" "$@" > "$RELEASEKIT_BUILD_LOG"
|
case ${1:?missing Go command} in
|
||||||
if [[ ${RELEASEKIT_BOOTSTRAP_STATUS:-0} != 0 ]]; then
|
list)
|
||||||
exit "$RELEASEKIT_BOOTSTRAP_STATUS"
|
log=$RELEASEKIT_LIST_LOG
|
||||||
fi
|
printf '%s\0' "$PWD" "GOWORK=$GOWORK" "GOTOOLCHAIN=$GOTOOLCHAIN" "$@" > "$log"
|
||||||
output=
|
modfile=
|
||||||
while (($#)); do
|
for argument in "$@"; do
|
||||||
case $1 in
|
case $argument in -modfile=*) modfile=${argument#-modfile=} ;; esac
|
||||||
-o)
|
done
|
||||||
|
[[ -n $modfile && -f $modfile && -f ${modfile%.mod}.sum ]]
|
||||||
|
cp "$modfile" "$RELEASEKIT_MODULE_LOG"
|
||||||
|
cp "${modfile%.mod}.sum" "$RELEASEKIT_SUM_LOG"
|
||||||
|
if [[ ${RELEASEKIT_BOOTSTRAP_STATUS:-0} != 0 ]]; then
|
||||||
|
exit "$RELEASEKIT_BOOTSTRAP_STATUS"
|
||||||
|
fi
|
||||||
|
printf '%s\n' v0.1.0
|
||||||
|
;;
|
||||||
|
build)
|
||||||
|
printf '%s\0' "$PWD" "GOWORK=$GOWORK" "GOTOOLCHAIN=$GOTOOLCHAIN" "$@" > "$RELEASEKIT_BUILD_LOG"
|
||||||
|
if [[ ${RELEASEKIT_BOOTSTRAP_STATUS:-0} != 0 ]]; then
|
||||||
|
exit "$RELEASEKIT_BOOTSTRAP_STATUS"
|
||||||
|
fi
|
||||||
|
output=
|
||||||
|
while (($#)); do
|
||||||
|
case $1 in
|
||||||
|
-o)
|
||||||
|
shift
|
||||||
|
output=${1:?missing output path}
|
||||||
|
;;
|
||||||
|
-o=*) output=${1#-o=} ;;
|
||||||
|
esac
|
||||||
shift
|
shift
|
||||||
output=${1:?missing output path}
|
done
|
||||||
;;
|
[[ -n $output ]]
|
||||||
-o=*) output=${1#-o=} ;;
|
cat > "$output" <<'RELEASEKIT_TOOL'
|
||||||
esac
|
|
||||||
shift
|
|
||||||
done
|
|
||||||
[[ -n $output ]]
|
|
||||||
cat > "$output" <<'RELEASEKIT_TOOL'
|
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
printf '%s\0' "$PWD" "$@" > "$RELEASEKIT_ARGS_LOG"
|
printf '%s\0' "$PWD" "$@" > "$RELEASEKIT_ARGS_LOG"
|
||||||
exit "${RELEASEKIT_TOOL_STATUS:-0}"
|
exit "${RELEASEKIT_TOOL_STATUS:-0}"
|
||||||
RELEASEKIT_TOOL
|
RELEASEKIT_TOOL
|
||||||
chmod +x "$output"
|
chmod +x "$output"
|
||||||
|
;;
|
||||||
|
*) exit 64 ;;
|
||||||
|
esac
|
||||||
`
|
`
|
||||||
|
|
||||||
func TestReleaseWrapperForwardsNativeArgumentsAndStatus(t *testing.T) {
|
func TestReleaseWrapperForwardsNativeArgumentsAndStatus(t *testing.T) {
|
||||||
@@ -50,6 +72,9 @@ func TestReleaseWrapperForwardsNativeArgumentsAndStatus(t *testing.T) {
|
|||||||
cmd.Env = layout.environment(map[string]string{
|
cmd.Env = layout.environment(map[string]string{
|
||||||
"RELEASEKIT_ARGS_LOG": layout.argsLog,
|
"RELEASEKIT_ARGS_LOG": layout.argsLog,
|
||||||
"RELEASEKIT_BUILD_LOG": layout.buildLog,
|
"RELEASEKIT_BUILD_LOG": layout.buildLog,
|
||||||
|
"RELEASEKIT_LIST_LOG": layout.listLog,
|
||||||
|
"RELEASEKIT_MODULE_LOG": layout.moduleLog,
|
||||||
|
"RELEASEKIT_SUM_LOG": layout.sumLog,
|
||||||
"RELEASEKIT_TOOL_STATUS": strconv.Itoa(toolStatus),
|
"RELEASEKIT_TOOL_STATUS": strconv.Itoa(toolStatus),
|
||||||
})
|
})
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
@@ -60,17 +85,49 @@ func TestReleaseWrapperForwardsNativeArgumentsAndStatus(t *testing.T) {
|
|||||||
t.Fatalf("unexpected output: %q", output)
|
t.Fatalf("unexpected output: %q", output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listInvocation := readNULArguments(t, layout.listLog)
|
||||||
buildInvocation := readNULArguments(t, layout.buildLog)
|
buildInvocation := readNULArguments(t, layout.buildLog)
|
||||||
if len(buildInvocation) < 4 || buildInvocation[0] != layout.repo {
|
for name, invocation := range map[string][]string{"list": listInvocation, "build": buildInvocation} {
|
||||||
t.Fatalf("build invocation=%q, want repository %q", buildInvocation, layout.repo)
|
if len(invocation) < 4 || invocation[0] != layout.repo {
|
||||||
}
|
t.Fatalf("%s invocation=%q, want repository %q", name, invocation, layout.repo)
|
||||||
if buildInvocation[1] != "GOWORK=off" || buildInvocation[2] != "GOTOOLCHAIN=local" {
|
}
|
||||||
t.Fatalf("build environment=%q", buildInvocation[1:3])
|
if invocation[1] != "GOWORK=off" || invocation[2] != "GOTOOLCHAIN=local" {
|
||||||
|
t.Fatalf("%s environment=%q", name, invocation[1:3])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
listArgs := listInvocation[3:]
|
||||||
buildArgs := buildInvocation[3:]
|
buildArgs := buildInvocation[3:]
|
||||||
if buildArgs[0] != "build" || !slices.Contains(buildArgs, "-mod=readonly") || !slices.Contains(buildArgs, "gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit") {
|
modfile := argumentValue(listArgs, "-modfile=")
|
||||||
|
if listArgs[0] != "list" || !slices.Contains(listArgs, "-mod=mod") || !slices.Contains(listArgs, "-m") || !slices.Contains(listArgs, "gitea.maximumdirect.net/eric/ReleaseKit") {
|
||||||
|
t.Fatalf("unexpected module-selection arguments: %q", listArgs)
|
||||||
|
}
|
||||||
|
if modfile == "" || argumentValue(buildArgs, "-modfile=") != modfile || !slices.Contains(buildArgs, "-mod=mod") {
|
||||||
|
t.Fatalf("list/build did not share alternate module metadata: list=%q build=%q", listArgs, buildArgs)
|
||||||
|
}
|
||||||
|
if buildArgs[0] != "build" || !slices.Contains(buildArgs, "gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit") {
|
||||||
t.Fatalf("unexpected build arguments: %q", buildArgs)
|
t.Fatalf("unexpected build arguments: %q", buildArgs)
|
||||||
}
|
}
|
||||||
|
if relative, err := filepath.Rel(layout.repo, modfile); err != nil || relative == "." || (!strings.HasPrefix(relative, ".."+string(filepath.Separator)) && relative != "..") {
|
||||||
|
t.Fatalf("alternate module file %q is not outside repository %q: relative=%q error=%v", modfile, layout.repo, relative, err)
|
||||||
|
}
|
||||||
|
if module := readText(t, layout.moduleLog); !strings.Contains(module, "gitea.maximumdirect.net/eric/ReleaseKit v0.1.0") {
|
||||||
|
t.Fatalf("temporary module file does not retain selected ReleaseKit version: %q", module)
|
||||||
|
}
|
||||||
|
if sum := readText(t, layout.sumLog); !strings.Contains(sum, "fixture checksum") {
|
||||||
|
t.Fatalf("temporary sum file did not copy source checksums: %q", sum)
|
||||||
|
}
|
||||||
|
if sourceModule := readText(t, filepath.Join(layout.repo, "go.mod")); !strings.Contains(sourceModule, "gitea.maximumdirect.net/eric/ReleaseKit v0.1.0") {
|
||||||
|
t.Fatalf("source go.mod changed: %q", sourceModule)
|
||||||
|
}
|
||||||
|
if sourceSum := readText(t, filepath.Join(layout.repo, "go.sum")); sourceSum != "fixture checksum\n" {
|
||||||
|
t.Fatalf("source go.sum changed: %q", sourceSum)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(modfile); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("temporary module file remains after wrapper: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(filepath.Dir(modfile), "releasekit.sum")); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("temporary sum file remains after wrapper: %v", err)
|
||||||
|
}
|
||||||
if info, err := os.Stat(filepath.Join(layout.repo, "bin", "releasekit")); err != nil || !info.Mode().IsRegular() {
|
if info, err := os.Stat(filepath.Join(layout.repo, "bin", "releasekit")); err != nil || !info.Mode().IsRegular() {
|
||||||
t.Fatalf("ReleaseKit executable was not built in bin: %v", err)
|
t.Fatalf("ReleaseKit executable was not built in bin: %v", err)
|
||||||
}
|
}
|
||||||
@@ -84,6 +141,86 @@ func TestReleaseWrapperForwardsNativeArgumentsAndStatus(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeliveryWorkflowUsesModuleSelectedReleaseKit(t *testing.T) {
|
||||||
|
data, err := os.ReadFile("../.woodpecker/release.yml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var workflow struct {
|
||||||
|
Matrix struct {
|
||||||
|
Include []map[string]string `yaml:"include"`
|
||||||
|
} `yaml:"matrix"`
|
||||||
|
When []struct {
|
||||||
|
Event string `yaml:"event"`
|
||||||
|
Ref string `yaml:"ref"`
|
||||||
|
} `yaml:"when"`
|
||||||
|
Steps []struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Image string `yaml:"image"`
|
||||||
|
Environment map[string]struct {
|
||||||
|
FromSecret string `yaml:"from_secret"`
|
||||||
|
} `yaml:"environment"`
|
||||||
|
Commands []string `yaml:"commands"`
|
||||||
|
} `yaml:"steps"`
|
||||||
|
}
|
||||||
|
if err := yaml.Unmarshal(data, &workflow); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(workflow.Matrix.Include) != 1 || workflow.Matrix.Include[0]["GO_IMAGE"] != "golang:1.25.5" {
|
||||||
|
t.Fatalf("workflow Go image = %v, want existing Go 1.25.5 image", workflow.Matrix.Include)
|
||||||
|
}
|
||||||
|
if len(workflow.When) != 1 || workflow.When[0].Event != "tag" || workflow.When[0].Ref != "refs/tags/v*" {
|
||||||
|
t.Fatalf("workflow trigger = %+v", workflow.When)
|
||||||
|
}
|
||||||
|
if len(workflow.Steps) != 1 || workflow.Steps[0].Name != "deliver" || workflow.Steps[0].Image != "${GO_IMAGE}" {
|
||||||
|
t.Fatalf("workflow delivery step = %+v", workflow.Steps)
|
||||||
|
}
|
||||||
|
if secret := workflow.Steps[0].Environment["RELEASEKIT_GITEA_TOKEN"].FromSecret; secret != "GITEA_RELEASE_TOKEN" {
|
||||||
|
t.Fatalf("delivery secret = %q, want existing GITEA_RELEASE_TOKEN", secret)
|
||||||
|
}
|
||||||
|
if len(workflow.Steps[0].Commands) != 1 {
|
||||||
|
t.Fatalf("workflow commands = %q, want one bootstrap/delivery command", workflow.Steps[0].Commands)
|
||||||
|
}
|
||||||
|
command := workflow.Steps[0].Commands[0]
|
||||||
|
for _, fragment := range []string{
|
||||||
|
"git fetch origin \"refs/tags/$${CI_COMMIT_TAG}:refs/tags/$${CI_COMMIT_TAG}\"",
|
||||||
|
"git checkout --detach \"$${tag_commit}\"",
|
||||||
|
"git status --porcelain --untracked-files=all",
|
||||||
|
"mktemp -d \"$${CI_WORKSPACE%/*}/.releasekit-bootstrap.XXXXXX\"",
|
||||||
|
"trap 'rm -rf \"$${bootstrap_dir}\"' EXIT",
|
||||||
|
"cp go.mod \"$${bootstrap_dir}/go.mod\"",
|
||||||
|
"cp go.sum \"$${bootstrap_dir}/go.sum\"",
|
||||||
|
"export GOWORK=off",
|
||||||
|
"export GOTOOLCHAIN=local",
|
||||||
|
"releasekit_module=gitea.maximumdirect.net/eric/ReleaseKit",
|
||||||
|
"go list -mod=mod -modfile=",
|
||||||
|
"-m \"$${releasekit_module}\"",
|
||||||
|
"go build -mod=mod -modfile=",
|
||||||
|
"-o \"$${bootstrap_dir}/releasekit\" \"$${releasekit_module}/cmd/releasekit\"",
|
||||||
|
"\"$${bootstrap_dir}/releasekit\" ci-deliver",
|
||||||
|
"--repo \"$${CI_WORKSPACE}\"",
|
||||||
|
"--commit \"$${CI_COMMIT_SHA}\"",
|
||||||
|
"\"$${CI_COMMIT_TAG}\"",
|
||||||
|
} {
|
||||||
|
if !strings.Contains(command, fragment) {
|
||||||
|
t.Fatalf("workflow command is missing %q: %s", fragment, command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Index(command, "git checkout --detach") > strings.Index(command, "go list -mod=mod") || strings.Index(command, "git status --porcelain") > strings.Index(command, "go list -mod=mod") {
|
||||||
|
t.Fatalf("workflow resolves the tool before verifying the tagged source: %s", command)
|
||||||
|
}
|
||||||
|
if strings.Contains(command, "RELEASEKIT_VERSION") || strings.Contains(command, "cmd/releasekit@") {
|
||||||
|
t.Fatalf("workflow retains a separate tool version: %s", command)
|
||||||
|
}
|
||||||
|
goMod, err := os.ReadFile("../go.mod")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(goMod), "gitea.maximumdirect.net/eric/ReleaseKit v0.1.0") {
|
||||||
|
t.Fatalf("go.mod does not retain selected supported ReleaseKit version: %s", goMod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestReleaseWrapperStopsWhenBootstrapFails(t *testing.T) {
|
func TestReleaseWrapperStopsWhenBootstrapFails(t *testing.T) {
|
||||||
layout := newReleaseTestLayout(t)
|
layout := newReleaseTestLayout(t)
|
||||||
cmd := exec.Command("bash", layout.wrapper, "publish", "v4.2.0")
|
cmd := exec.Command("bash", layout.wrapper, "publish", "v4.2.0")
|
||||||
@@ -91,6 +228,9 @@ func TestReleaseWrapperStopsWhenBootstrapFails(t *testing.T) {
|
|||||||
cmd.Env = layout.environment(map[string]string{
|
cmd.Env = layout.environment(map[string]string{
|
||||||
"RELEASEKIT_ARGS_LOG": layout.argsLog,
|
"RELEASEKIT_ARGS_LOG": layout.argsLog,
|
||||||
"RELEASEKIT_BUILD_LOG": layout.buildLog,
|
"RELEASEKIT_BUILD_LOG": layout.buildLog,
|
||||||
|
"RELEASEKIT_LIST_LOG": layout.listLog,
|
||||||
|
"RELEASEKIT_MODULE_LOG": layout.moduleLog,
|
||||||
|
"RELEASEKIT_SUM_LOG": layout.sumLog,
|
||||||
"RELEASEKIT_BOOTSTRAP_STATUS": "42",
|
"RELEASEKIT_BOOTSTRAP_STATUS": "42",
|
||||||
})
|
})
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
@@ -100,6 +240,17 @@ func TestReleaseWrapperStopsWhenBootstrapFails(t *testing.T) {
|
|||||||
if _, err := os.Stat(layout.argsLog); !os.IsNotExist(err) {
|
if _, err := os.Stat(layout.argsLog); !os.IsNotExist(err) {
|
||||||
t.Fatalf("ReleaseKit executed after failed bootstrap; stat error=%v", err)
|
t.Fatalf("ReleaseKit executed after failed bootstrap; stat error=%v", err)
|
||||||
}
|
}
|
||||||
|
if _, err := os.Stat(layout.buildLog); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("ReleaseKit build ran after module selection failed; stat error=%v", err)
|
||||||
|
}
|
||||||
|
listInvocation := readNULArguments(t, layout.listLog)
|
||||||
|
modfile := argumentValue(listInvocation[3:], "-modfile=")
|
||||||
|
if modfile == "" {
|
||||||
|
t.Fatalf("module selection did not use temporary metadata: %q", listInvocation)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(filepath.Dir(modfile)); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("temporary bootstrap directory remains after failed selection: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReleaseKitProfileDeclaresProductAndDeliverySettings(t *testing.T) {
|
func TestReleaseKitProfileDeclaresProductAndDeliverySettings(t *testing.T) {
|
||||||
@@ -176,23 +327,29 @@ func TestReleaseKitTargetMembership(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type releaseTestLayout struct {
|
type releaseTestLayout struct {
|
||||||
repo string
|
repo string
|
||||||
caller string
|
caller string
|
||||||
wrapper string
|
wrapper string
|
||||||
argsLog string
|
argsLog string
|
||||||
buildLog string
|
buildLog string
|
||||||
path string
|
listLog string
|
||||||
|
moduleLog string
|
||||||
|
sumLog string
|
||||||
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newReleaseTestLayout(t *testing.T) releaseTestLayout {
|
func newReleaseTestLayout(t *testing.T) releaseTestLayout {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
layout := releaseTestLayout{
|
layout := releaseTestLayout{
|
||||||
repo: filepath.Join(root, "repository"),
|
repo: filepath.Join(root, "repository"),
|
||||||
caller: filepath.Join(root, "caller"),
|
caller: filepath.Join(root, "caller"),
|
||||||
argsLog: filepath.Join(root, "tool-arguments"),
|
argsLog: filepath.Join(root, "tool-arguments"),
|
||||||
buildLog: filepath.Join(root, "build-arguments"),
|
buildLog: filepath.Join(root, "build-arguments"),
|
||||||
path: filepath.Join(root, "bin"),
|
listLog: filepath.Join(root, "list-arguments"),
|
||||||
|
moduleLog: filepath.Join(root, "temporary-go.mod"),
|
||||||
|
sumLog: filepath.Join(root, "temporary-go.sum"),
|
||||||
|
path: filepath.Join(root, "bin"),
|
||||||
}
|
}
|
||||||
if err := os.MkdirAll(filepath.Join(layout.repo, "scripts"), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Join(layout.repo, "scripts"), 0755); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -210,9 +367,29 @@ func newReleaseTestLayout(t *testing.T) releaseTestLayout {
|
|||||||
layout.wrapper = filepath.Join(layout.repo, "scripts", "release.sh")
|
layout.wrapper = filepath.Join(layout.repo, "scripts", "release.sh")
|
||||||
writeReleaseTestFile(t, layout.wrapper, wrapper, 0755)
|
writeReleaseTestFile(t, layout.wrapper, wrapper, 0755)
|
||||||
writeReleaseTestFile(t, filepath.Join(layout.path, "go"), []byte(goStub), 0755)
|
writeReleaseTestFile(t, filepath.Join(layout.path, "go"), []byte(goStub), 0755)
|
||||||
|
writeReleaseTestFile(t, filepath.Join(layout.repo, "go.mod"), []byte("module example.com/audita-fixture\n\ngo 1.25.5\n\nrequire gitea.maximumdirect.net/eric/ReleaseKit v0.1.0\n"), 0644)
|
||||||
|
writeReleaseTestFile(t, filepath.Join(layout.repo, "go.sum"), []byte("fixture checksum\n"), 0644)
|
||||||
return layout
|
return layout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readText(t *testing.T, path string) string {
|
||||||
|
t.Helper()
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func argumentValue(arguments []string, prefix string) string {
|
||||||
|
for _, argument := range arguments {
|
||||||
|
if strings.HasPrefix(argument, prefix) {
|
||||||
|
return strings.TrimPrefix(argument, prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (layout releaseTestLayout) environment(values map[string]string) []string {
|
func (layout releaseTestLayout) environment(values map[string]string) []string {
|
||||||
filtered := make([]string, 0, len(os.Environ())+len(values)+1)
|
filtered := make([]string, 0, len(os.Environ())+len(values)+1)
|
||||||
for _, value := range os.Environ() {
|
for _, value := range os.Environ() {
|
||||||
|
|||||||
Reference in New Issue
Block a user