Validate CI module tags before delivery preparation
This commit is contained in:
60
cmd/releasekit/ci_identity_test.go
Normal file
60
cmd/releasekit/ci_identity_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"go.maximumdirect.net/releasekit/cmd/releasekit/internal/cli"
|
||||
)
|
||||
|
||||
func TestCIDeliveryRejectsModuleTagMismatchBeforePreparation(t *testing.T) {
|
||||
for _, binary := range []bool{false, true} {
|
||||
name := "page-only"
|
||||
if binary {
|
||||
name = "binary"
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
repository := newDeliveryBuildRepository(t)
|
||||
writeRepositoryFile(t, repository, "child/go.mod", "module example.com/tagged-app/child\n\ngo 1.26.0\n")
|
||||
writeRepositoryFile(t, repository, "child/docs/releases/v1.2.3.md", "# Example Product v1.2.3\n\nChild release.\n")
|
||||
cliGitRun(t, repository, "add", "child")
|
||||
cliGitRun(t, repository, "commit", "-m", "add child module")
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Errorf("mismatched module tag reached forge: %s", r.URL.Path)
|
||||
http.Error(w, "unexpected request", http.StatusInternalServerError)
|
||||
}))
|
||||
defer server.Close()
|
||||
settings := cli.Settings{
|
||||
Product: "Example Product", ModuleDir: "child",
|
||||
Delivery: &cli.DeliverySettings{Forge: "gitea", BaseURL: server.URL, Owner: "owner", Repository: "example"},
|
||||
}
|
||||
if binary {
|
||||
settings.Build = &cli.BuildSettings{MainPackage: ".", BinaryName: "helper", Targets: []string{"linux/amd64"}}
|
||||
}
|
||||
writeDeliveryProfile(t, repository, settings)
|
||||
commit := pushTaggedCLIRepository(t, repository)
|
||||
t.Setenv("RELEASEKIT_GITEA_TOKEN", "fixture-token")
|
||||
shimDir := t.TempDir()
|
||||
marker := filepath.Join(shimDir, "go-invoked")
|
||||
if err := os.WriteFile(filepath.Join(shimDir, "go"), []byte("#!/bin/sh\n: > \"$RELEASEKIT_GO_MARKER\"\nexit 99\n"), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("RELEASEKIT_GO_MARKER", marker)
|
||||
t.Setenv("PATH", shimDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
var stdout, stderr bytes.Buffer
|
||||
status := run(context.Background(), []string{"ci-deliver", "--repo", repository, "--prepared", "--commit", commit, "--expected-module", "child", "v1.2.3"}, &stdout, &stderr)
|
||||
if status != 1 || !strings.Contains(stderr.String(), `expected delivery tag "v1.2.3"`) || !strings.Contains(stderr.String(), `selected module tag "child/v1.2.3"`) {
|
||||
t.Fatalf("status=%d stderr=%q, want module tag mismatch", status, stderr.String())
|
||||
}
|
||||
if _, err := os.Stat(marker); !os.IsNotExist(err) {
|
||||
t.Fatalf("dependency/build work ran before tag comparison: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -291,6 +291,15 @@ func runCIDelivery(ctx context.Context, invocation cli.Invocation, stdout, stder
|
||||
fmt.Fprintf(stderr, "profile module selection: %v\n", err)
|
||||
return 2
|
||||
}
|
||||
identity, err := releaseidentity.FromCommitted(ctx, invocation.Repository, settings.ModuleDir, invocation.Version, invocation.Commit)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "resolve CI release identity: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
if invocation.ExpectedTag != identity.Tag {
|
||||
fmt.Fprintf(stderr, "expected delivery tag %q does not match selected module tag %q\n", invocation.ExpectedTag, identity.Tag)
|
||||
return 1
|
||||
}
|
||||
destination, status := deliveryDestination(invocation.Repository, settings, stderr)
|
||||
if status != 0 {
|
||||
return status
|
||||
|
||||
@@ -244,9 +244,11 @@ verifies the tag, HEAD, and clean worktree. With `--prepared`, it performs those
|
||||
checks read-only against the existing checkout and does not fetch or check out.
|
||||
In either mode, it loads the profile from the verified source. `--expected-module`
|
||||
optionally checks that the profile selects the module chosen by a launcher;
|
||||
module selection itself remains profile-owned. The command validates explicit
|
||||
forge selection and resolves the configured token. For binary profiles, it
|
||||
performs a read-only repository preflight before downloading application
|
||||
module selection itself remains profile-owned. The command compares the selected
|
||||
module's derived release tag with the full event tag before forge access or
|
||||
dependency preparation, then validates explicit forge selection and resolves
|
||||
the configured token. For binary profiles, it performs a read-only repository
|
||||
preflight before downloading application
|
||||
dependencies; delivery performs a fresh check after dependency preparation.
|
||||
Source-only profiles rely on delivery's automatic preflight and skip dependency
|
||||
downloads and binary builds.
|
||||
|
||||
@@ -197,7 +197,15 @@ cleanup; this finding does not require removing plans now.
|
||||
|
||||
## 5. Bind CI release identity before online preflight and preparation — P2
|
||||
|
||||
Status: open.
|
||||
Status: complete.
|
||||
|
||||
**Completed:** CI now derives the selected module identity from the verified
|
||||
event commit and checks the complete tag before credential loading, forge
|
||||
requests, or dependency preparation. The optional launcher-module guard and
|
||||
delivery's later source/preflight verification retain their responsibilities.
|
||||
A focused page-only/binary regression covers a matching launcher selection
|
||||
with a mismatched derived tag and verifies no forge or Go-tool work. That test
|
||||
and existing valid CI/preflight/managed-delivery tests passed.
|
||||
|
||||
**Evidence:** [CI adapter](../../cmd/releasekit/main.go), especially
|
||||
`runCIDelivery` and `checkExpectedModule`, and the existing identity/tag checks
|
||||
|
||||
Reference in New Issue
Block a user