feat: dockerized migration pipeline with per-project payloads - #16
Draft
hllvc wants to merge 4 commits into
Draft
Conversation
- pull workspace state from the TFC API instead of per-workspace init
- write migration-summary.{md,json}; replace workspace.tmpl with summary.tmpl
- map TFC vcs settings to SG VCSTriggers (push, PR speculative, file filters)
- add SGDefault*/workspaceOverrides for vcs auth, source kind and triggers
Validate generated payloads against schema/sg-payload.schema.json via yajsv.
- sg-migrate.sh + Dockerfile run the full pipeline inside a container - scripts/migrate.sh: init/apply/enrich/convert/validate/import/triggers/all - merge TFC variable sets, parallel convert/import, register VCS triggers - move convert_hcl_to_json.sh into scripts/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Turns the migrator from a hand-driven
terraform applyplus manual editing into an end-to-end, dockerized pipeline: extract from TFC/TFE, enrich with Variable Sets, convert HCL, validate against a schema, and bulk-import into StackGuardian with one command. The transformer itself gains per-project output, a migration summary, API-based state export, VCS trigger mapping, and per-workspace overrides.Motivation & Context
The previous flow produced a single
sg-payload.jsonkeyed by raw TFC project IDs, required the user to pre-create workflow groups and look up their IDs, exported state by runningterraform initper workspace (slow, and prone to the plugin-cache concurrency bug), silently dropped information the user needed to act on (sensitive variables, unpinned Terraform versions), and depended on the host having the right Terraform,jq,hcl2jsonandsg-cliversions. This PR removes those manual steps and makes the run reproducible across Linux, macOS and Windows.Changes Made
Transformer (
transformer/terraform-cloud/)sg-payload.<project>.jsonper TFC project via a newtfe_projectsdata source;WorkflowGroup.nameis now the human-readabletfc-<project>instead of the project ID.migration-summary.md/.json(rendered fromsummary.tmpl, replacingworkspace.tmpl) listing skipped sensitive variables, Terraform-version fallbacks, renamed workspaces, and state-export failures.null_resourcethat pulls each workspace's state directly from the TFC API (current-state-version-> hosted download URL) withcurl/jq. Idempotent by workspace name/id,forceStateRefreshre-pulls, and per-workspace failures go tostate-export-failures.loginstead of aborting the apply. The token is read at runtime from theterraform logincreds file orTFE_TOKEN, so it never lands in TF state.VCSTriggersblock per workflow, gated bySGDefaultEnableVCSTriggers.terraform/envvariables (TFC never returns their values) and record them in the summary.terraform_versiontoTERRAFORM-<version>; anything else (latest, constraints) falls back to the newSGDefaultTerraformVersion.ResourceNameto SG's charset and 100-char limit with deterministic collision suffixes; any actual rename is reported.tfHostname,SGDefaultTerraformVersion,SGDefaultEnableVCSTriggers,forceStateRefresh, and a typedworkspaceOverridesmap (integrations, approvers, runners, version, extra env vars, triggers) that takes precedence overSGDefault*per workspace. Requires Terraform>= 1.3foroptional().example_payload.jsoncandterraform.tfvars.examplefor the new shape and variables.Orchestration and tooling
sg-migrate.sh(host entrypoint) runsscripts/migrate.shinside the newDockerfileimage, bind-mounting the repo and forwardingSG_API_TOKEN,SG_ORG,SG_BASE_URL,TFE_TOKENandTF_TOKEN_*. Falls back to native when--native/--local,SG_NATIVE=1,clean, or no Docker.scripts/migrate.shimplementsinit|apply|enrich|convert|validate|import|triggers|all|clean. Import resolves each project'stfc-<project>group, shows anexists/createplan, prompts (skip with-y), creates missing groups via the SG API, imports withsg-cliunder retry/backoff, then registersVCSTriggersin a second pass through the webhooks endpoint because bulk create drops them (triggersruns that pass on its own,--no-vcs-triggersskips it). Convert and import run in parallel;.sg/workflow-groups.json(or--mapping) overrides target groups per project, and--export-dirrelocates the output directory.scripts/tools.shprovides PATH-or-cache tool resolution with pinned downloads ofjq,hcl2json,yajsvand the Gosg-cliinto.sg/cached/, plus retry and colored logging helpers. The image bundles the same tools (yajsvbuilt from source for arm64).scripts/enrich_variable_sets.shmerges TFC Variable Set variables into the payloads via the TFC API, resolving global/project/workspace scope with TFC precedence and reporting sensitive vars and key conflicts.schema/sg-payload.schema.json(draft-07, derived from the SG OpenAPIWorkflowschema) andscripts/validate_payload.shusingyajsv.convert_hcl_to_json.shintoscripts/and rework it to run per payload file with an atomic in-place rewrite..gitignoreadds.sg/(tool cache and group map); new.dockerignorekeeps.git,.sg, exports, tfvars and Terraform state out of the image context.Docs
README.md: new "Quick start (orchestrated)" section,TFE_TOKENas the recommended auth path, per-file convert/validate/import steps, Go-binarysg-cliinstructions, and a "Notes and limitations" section.Testing
./sg-migrate.sh allagainst a TFC org with multiple projects produces one payload per project, a summary, and state files; import creates the missingtfc-<project>groups and workflows.--nativeon a host without Docker downloads tools into.sg/cached/and behaves identically.applywithoutforceStateRefreshdoes not re-download state; with it set, all states are re-pulled.latestTerraform version, and Variable Sets at each scope show up correctly inmigration-summary.mdand the payloads../scripts/validate_payload.sh export/sg-payload.*.jsonpasses on generated payloads.terraform fmt -checkon the transformer module.Risks & Edge Cases
sg-payload.jsonbecamesg-payload.<project>.jsonper project, andWorkflowGroup.nameistfc-<project>rather than the TFC project ID. Anyone scripting around the old file name needs to adjust.--no-create-groupsrestores the old requirement that groups pre-exist.curl/jqbeing on PATH at apply time; the orchestrator and image guarantee this, a bareterraform applydoes not.remoteexecution mode workspaces may have no or stale state in TFC; the summary flags them.Deployment Notes
SG_API_TOKEN,SG_ORG, optionalSG_BASE_URL(non-prod targets),TFE_TOKEN(recommended overterraform login), and tuning knobsSG_RETRIES,SG_RETRY_BASE,SG_TF_PARALLELISM,SG_CONCURRENCY,SG_NATIVE,SG_IMAGE,SG_EXPORT_DIR,SG_WFGROUP_MAP,SG_CACHE_DIR.stackguardian/migrator:localon first run (--buildto rebuild); nothing is pushed to a registry.