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
14 changes: 14 additions & 0 deletions e2e/harness/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,9 +344,23 @@ func generateStubWorkflow(name, scenarioTag string) string {
if scenarioTag != "" {
displayName = fmt.Sprintf("%s [scenario-%s]", name, scenarioTag)
}
// Declare the inputs a real promote/orchestrate deploy callback accepts so the
// generator discovers them and threads the matching preflight outputs through
// each deploy's with: block. image_tag in particular drives the
// source_image_tag passthrough in the generated promote workflow.
return fmt.Sprintf(`name: %s
on:
workflow_call:
inputs:
environment:
required: false
type: string
sha:
required: false
type: string
image_tag:
required: false
type: string
jobs:
%s:
runs-on: ubuntu-latest
Expand Down
36 changes: 36 additions & 0 deletions e2e/scenarios/18-promote-source-image-tag.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: "Promote threads source image tag to deploys"
description: |
Verifies the generated promote.yaml wires the source image tag from the
preflight job into each deploy that accepts an image_tag input. The preflight
job's outputs block must declare source_image_tag, and a deploy whose reusable
workflow declares an image_tag input must receive
image_tag: ${{ needs.preflight.outputs.source_image_tag }} in its with: block.
Without the output declaration the reference resolves to an empty string on
real GitHub and the deploy receives a blank image tag.

This is a generator-output verification scenario. Assertion runs on the staged
repo after StageRepoFromConfig generates workflows, before any run.

config:
trunk_branch: main
environments: [dev, test, prod]
deploys:
- name: app
workflow: .github/workflows/deploy.yaml
triggers: ["src/**"]

steps:
- name: "Initial commit generates workflows; promote threads source_image_tag to deploy-app"
action: commit
commit:
message: "feat: add app source"
files:
src/app.go: |
package main
func main() {}
expect:
workflow_files:
- path: ".github/workflows/promote.yaml"
contains:
- "source_image_tag: ${{ steps.preflight.outputs.source_image_tag }}"
- "image_tag: ${{ needs.preflight.outputs.source_image_tag }}"
1 change: 1 addition & 0 deletions internal/generate/promote.go
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,7 @@ func (g *PromoteGenerator) writePreflightJob(sb *strings.Builder) {
sb.WriteString(" target_env: ${{ steps.preflight.outputs.target_env }}\n")
sb.WriteString(" source_sha: ${{ steps.preflight.outputs.source_sha }}\n")
sb.WriteString(" source_version: ${{ steps.preflight.outputs.source_version }}\n")
sb.WriteString(" source_image_tag: ${{ steps.preflight.outputs.source_image_tag }}\n")
sb.WriteString(" changelog_base_sha: ${{ steps.preflight.outputs.changelog_base_sha }}\n")
sb.WriteString(" rollback_sha: ${{ steps.preflight.outputs.rollback_sha }}\n")
sb.WriteString(" rollback_on_failure: ${{ steps.preflight.outputs.rollback_on_failure }}\n")
Expand Down
20 changes: 20 additions & 0 deletions internal/generate/promote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,26 @@ func TestPromoteGenerator_PublishOnProd(t *testing.T) {
assert.Contains(t, content, "rc_version")
}

// TestPromoteGenerator_PreflightDeclaresSourceImageTag verifies the preflight job
// declares a source_image_tag output. Deploy jobs reference
// needs.preflight.outputs.source_image_tag for the image_tag input, so the
// preflight outputs block must declare it or the reference resolves to an empty
// string on real GitHub (and actionlint flags an undefined property).
func TestPromoteGenerator_PreflightDeclaresSourceImageTag(t *testing.T) {
cfg := &config.TrunkConfig{
TrunkBranch: "main",
Environments: []string{"dev", "test", "prod"},
}

gen := NewPromoteGenerator(cfg, "")
content, err := gen.Generate()
require.NoError(t, err)

assert.Contains(t, content,
"source_image_tag: ${{ steps.preflight.outputs.source_image_tag }}",
"preflight outputs block must declare source_image_tag so deploy jobs resolve a non-empty image_tag")
}

func TestPromoteGenerator_DryRunSupport(t *testing.T) {
cfg := &config.TrunkConfig{
TrunkBranch: "main",
Expand Down
3 changes: 3 additions & 0 deletions internal/promote/command_preflight.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ func writePreflightGHAOutput(result *PreflightResult) error {
w.Set("target_env", result.TargetEnv)
w.Set("source_sha", result.SourceSHA)
w.Set("source_version", result.SourceVersion)
// The promoted artifact version is the canonical image tag for the promotion.
// Deploy jobs consume this as their image_tag input.
w.Set("source_image_tag", result.SourceVersion)
w.Set("changelog_base_sha", result.ChangelogBaseSHA)
w.Set("rollback_sha", result.RollbackSHA)

Expand Down
4 changes: 4 additions & 0 deletions internal/promote/command_preflight_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@ func TestPreflightCommand_GHAOutput(t *testing.T) {
require.NoError(t, err)
require.Contains(t, string(output), "source_env=")
require.Contains(t, string(output), "target_env=")
// Deploy jobs consume source_image_tag for their image_tag input. The promoted
// artifact version is the canonical image tag, so it must mirror source_version.
require.Contains(t, string(output), "source_image_tag=v1.0.0-rc.1",
"preflight must emit source_image_tag set to the source version")
}

// TestPreflightCommand_AllowBreaking tests --allow-breaking flag.
Expand Down
Loading