You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Extend the backstage-template-execution workflow to chain multiple Backstage scaffolder templates in one Dapr workflow instance, with three target shapes:
pr — current behavior. Backstage opens a PR; we poll runs filtered by head branch; optional auto-merge.
dispatch — Backstage calls github:actions:dispatch. No PR, no merge. Run identification: time-based filter on event=workflow_dispatch + created>=<dispatchedAt> (strategy "a"). Upgrade to a correlation_id workflow input (strategy "b") if races bite.
Data hand-off
Stage 1 → 2 (VM IP):pr-vm-deploy.yaml exposes vm_ip as a job output. New activity FetchGitHubJobOutputs reads it; resolver replaces ${stages.vm.vm_ip} in stage 2's values.
Stage 2 → 3 (kubeconfig path): deterministic from vm_name — no runtime fetch, just a literal secrets/kubeconfigs/<name>.yaml in stage 3's values.
Code changes (main.go, ~150 LOC)
Replace top-level templateRef/values/watch with Stages []Stage.
Lift current scaffolder→poll→watch→merge body into runStage(...).
Outer loop over stages; export map keyed <stageName>.<key>.
Hard-cut input shape (no compat layer for the old single-template format).
External changes
stuttgart-things/stuttgart-things — pr-vm-deploy.yaml: upload stage-outputs artifact containing outputs.json with vm_ip (and any other stage-2 inputs). Original plan said "job output", corrected — see comment on this issue.
Plan
0. Baseline real run: single-stage create-terraform-vm with current code (no refactor) to validate tokens/network/PR-watch/merge end-to-end. Done 2026-04-25, ~28 min, PR #2092 merged in stuttgart-things/stuttgart-things.
1. Refactor main.go to stages: [...] + watch.kind discriminator, strategy (a) for dispatch correlation. Done — commit 15b6d63 on feat/multi-stage-backstage-chain.
1b. Re-run case (a) against the refactored code. Skipped full E2E in favor of unit tests (4 pass: interpolation, type preservation, nested resolveValues, no-exports passthrough) + live worker boot + structural smoke. Burning a real VM cycle for this would have been wasteful.
2. Add stage-outputs artifact upload to pr-vm-deploy.yaml (separate PR in stuttgart-things/stuttgart-things); implement FetchStageExports activity to download + parse it. Done — stuttgart-things/blueprints v1.83.0 writes outputs.json; stuttgart-things/stuttgart-things workflow change merged; activity verified end-to-end on 2026-04-26 ({"vm_ips":["10.31.101.109"]} retrieved + injected into stage 2).
3. Verify case (b) VM + ansible. Framework parts validated end-to-end on 2026-04-26 (instance run-1777182898): blueprint outputs.json, artifact upload + download, FetchStageExports, type-aware ${stages.vm.vm_ips} list passthrough into Backstage's array-typed targets, stage 1 PR auto-merge (#2096), stage 2 PR open + GH run watch + failure detected (no auto-merge on conclusion=failure). Underlying ansible playbook fails at kind create cluster with permission denied on /var/run/docker.sock — newly-added docker group not picked up by same SSH session. Out of scope for this issue (lives in stuttgart-things/ansible roles); 219/220 ansible tasks succeeded.
4. Verify case (c) — flux-bootstrap with kind: dispatch watch (the only framework piece still untested). Will use action: check against an existing cluster to keep it lab-light.
5. If concurrency becomes a problem: upgrade dispatch correlation to strategy (b) (workflow-input correlation_id + templated run-name).
Out of scope
Replacing the in-repo SOPS kubeconfig pattern with a runtime kubeconfig fetch.
Per-stage retry/backoff policy (today: stop on first failure).
Fixing the kind create cluster permission failure surfaced in step 3 — it's a stuttgart-things ansible playbook ordering bug (docker install adds user to docker group, but the same SSH session doesn't see it). Track separately.
Goal
Extend the
backstage-template-executionworkflow to chain multiple Backstage scaffolder templates in one Dapr workflow instance, with three target shapes:create-terraform-vmonlycreate-terraform-vm→ansible-provisioningcreate-terraform-vm→ansible-provisioning→flux-bootstrapA single Go workflow handles all three; the shape is selected by the input file (
stages: [...]).Design
Input schema
{ "backstageURL": "...", "stages": [ { "name": "vm", "templateRef": "...", "values": {...}, "watch": { "kind": "pr", "workflowFile": "pr-vm-deploy.yaml", "branch": "...", "merge": {"enabled": true} }, "exports": { "vm_ip": "fromGithubJobOutput:deploy.vm_ip" } }, { "name": "ansible", "templateRef": "...", "values": { "targets": ["${stages.vm.vm_ip}"], ... }, "watch": { "kind": "pr", "workflowFile": "pr-ansible.yaml", "branch": "...", "merge": {"enabled": true} } }, { "name": "flux", "templateRef": "...", "values": { "kube_config": "secrets/kubeconfigs/<name>.yaml", ... }, "watch": { "kind": "dispatch", "workflowFile": "dispatch-flux-bootstrap.yaml" } } ] }Trigger kinds
pr— current behavior. Backstage opens a PR; we poll runs filtered by head branch; optional auto-merge.dispatch— Backstage callsgithub:actions:dispatch. No PR, no merge. Run identification: time-based filter onevent=workflow_dispatch+created>=<dispatchedAt>(strategy "a"). Upgrade to acorrelation_idworkflow input (strategy "b") if races bite.Data hand-off
pr-vm-deploy.yamlexposesvm_ipas a job output. New activityFetchGitHubJobOutputsreads it; resolver replaces${stages.vm.vm_ip}in stage 2'svalues.vm_name— no runtime fetch, just a literalsecrets/kubeconfigs/<name>.yamlin stage 3'svalues.Code changes (
main.go, ~150 LOC)templateRef/values/watchwithStages []Stage.runStage(...).<stageName>.<key>.resolveValues(values, exports)— recursive${stages.X.Y}replacement.FetchGitHubJobOutputsactivity (gated onexports != nil).watch.kinddiscriminator:prkeeps existing logic;dispatchuses time-window filter and skips merge.External changes
stuttgart-things/stuttgart-things—pr-vm-deploy.yaml: uploadstage-outputsartifact containingoutputs.jsonwithvm_ip(and any other stage-2 inputs). Original plan said "job output", corrected — see comment on this issue.Plan
create-terraform-vmwith current code (no refactor) to validate tokens/network/PR-watch/merge end-to-end. Done 2026-04-25, ~28 min, PR #2092 merged in stuttgart-things/stuttgart-things.main.gotostages: [...]+watch.kinddiscriminator, strategy (a) for dispatch correlation. Done — commit 15b6d63 onfeat/multi-stage-backstage-chain.stage-outputsartifact upload topr-vm-deploy.yaml(separate PR instuttgart-things/stuttgart-things); implementFetchStageExportsactivity to download + parse it. Done —stuttgart-things/blueprintsv1.83.0 writesoutputs.json;stuttgart-things/stuttgart-thingsworkflow change merged; activity verified end-to-end on 2026-04-26 ({"vm_ips":["10.31.101.109"]}retrieved + injected into stage 2).${stages.vm.vm_ips}list passthrough into Backstage's array-typedtargets, stage 1 PR auto-merge (#2096), stage 2 PR open + GH run watch + failure detected (no auto-merge on conclusion=failure). Underlying ansible playbook fails atkind create clusterwithpermission denied on /var/run/docker.sock— newly-added docker group not picked up by same SSH session. Out of scope for this issue (lives in stuttgart-things/ansible roles); 219/220 ansible tasks succeeded.kind: dispatchwatch (the only framework piece still untested). Will useaction: checkagainst an existing cluster to keep it lab-light.correlation_id+ templatedrun-name).Out of scope
kind create clusterpermission failure surfaced in step 3 — it's a stuttgart-things ansible playbook ordering bug (docker install adds user todockergroup, but the same SSH session doesn't see it). Track separately.