Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion e2e/harness/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,16 @@ const actionLocalizeSedExpr = `s|stablekernel/cascade/\.github/actions/\([^@]*\)
// gaining a second `./` prefix.
const usesLocalizeSedExpr = `s|uses: \([^./@][^/@]*\.yaml\)|uses: ./\1|g`

// unlocalizedActionRefPattern is the extended-regex the post-localize verify
// step greps for. It matches only the action ref the sed actually rewrites,
// `stablekernel/cascade/.github/actions/`, so a verbatim cross-repo callback
// such as `stablekernel/cascade-example-artifact-a/.github/workflows/...@ref`
// is not mistaken for an un-localized ref. The trailing `/.github/actions/`
// anchor is what distinguishes the localizable action ref (slash after
// `cascade`) from the distinct `cascade-example-artifact-a` repo (hyphen after
// `cascade`).
const unlocalizedActionRefPattern = `stablekernel/cascade/\.github/actions/`

func (h *Harness) localizeWorkflows(ctx context.Context) error {
const maxAttempts = 3
const retryDelay = 200 * time.Millisecond
Expand All @@ -883,9 +893,18 @@ func (h *Harness) localizeWorkflows(ctx context.Context) error {
}
// grep -l exits 0 on match (= un-localized ref still present, which we
// treat as a failure to localize). Negation gives us 0 = clean.
//
// The pattern is anchored to the exact ref the sed rewrites,
// `stablekernel/cascade/.github/actions/`, rather than a bare
// `stablekernel/cascade` substring. A bare substring also matches a
// cross-repo reusable-workflow callback such as
// `stablekernel/cascade-example-artifact-a/.github/workflows/...@ref`
// (scenario 21), which is an intentional verbatim ref the sed leaves
// untouched. Treating that as un-localized made verify fail deterministically
// for every scenario that wires a cross-repo callback.
verify := []string{
"bash", "-c",
"cd /tmp/repo && ! grep -l 'stablekernel/cascade' .github/workflows/*.yaml",
"cd /tmp/repo && ! grep -lE '" + unlocalizedActionRefPattern + "' .github/workflows/*.yaml",
}

var lastErr error
Expand Down
68 changes: 68 additions & 0 deletions e2e/harness/localize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,74 @@ func runSed(t *testing.T, expr, in string) string {
return string(out)
}

// grepMatches reports whether `grep -lE <pattern>` finds the pattern in in,
// exercising the exact extended-regex the post-localize verify runs inside the
// act container. grep exits 0 on a match (un-localized ref present) and 1 on no
// match (clean); any other exit is a hard failure.
func grepMatches(t *testing.T, pattern, in string) bool {
t.Helper()
grep, err := exec.LookPath("grep")
if err != nil {
t.Skipf("grep not available: %v", err)
}
cmd := exec.Command(grep, "-lE", pattern)
cmd.Stdin = strings.NewReader(in)
err = cmd.Run()
if err == nil {
return true
}
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
return false
}
t.Fatalf("grep -lE %q failed: %v", pattern, err)
return false
}

// TestUnlocalizedActionRefPattern_IgnoresCrossRepoCallback locks in that the
// post-localize verify only flags the action ref the sed rewrites and never the
// distinct cross-repo reusable-workflow callback repo. A bare `stablekernel/cascade`
// substring also matched `stablekernel/cascade-example-artifact-a/...@ref`
// (scenario 21), so every scenario wiring a cross-repo callback failed staging
// with `localize verify found un-localized refs`.
func TestUnlocalizedActionRefPattern_IgnoresCrossRepoCallback(t *testing.T) {
tests := []struct {
name string
in string
wantMatch bool
}{
{
name: "un-localized action ref is flagged",
in: " uses: stablekernel/cascade/.github/actions/setup-cli@v0.1.0\n",
wantMatch: true,
},
{
name: "localized action ref is clean",
in: " uses: ./.github/actions/setup-cli\n",
wantMatch: false,
},
{
name: "cross-repo callback to a sibling repo is not flagged",
in: " uses: stablekernel/cascade-example-artifact-a/.github/workflows/build-shared.yaml@main\n",
wantMatch: false,
},
{
name: "cross-repo reusable workflow in the cascade repo itself is not flagged",
in: " uses: stablekernel/cascade/.github/workflows/x.yaml@v1\n",
wantMatch: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := grepMatches(t, unlocalizedActionRefPattern, tt.in)
if got != tt.wantMatch {
t.Errorf("grep -lE %q over %q: got match=%v, want %v",
unlocalizedActionRefPattern, tt.in, got, tt.wantMatch)
}
})
}
}

// TestUsesLocalizeSedExpr_Idempotent_LeavesQualifiedPathsUnchanged verifies the
// reusable-workflow localizer prefixes a bare ref with `./` but never adds a
// second `./` to an already-qualified path or mangles a cross-repo `@ref`.
Expand Down
Loading