Azure DevOps Pipelines Migration (to GitHub Actions or AWS CodePipeline)
Objective
Convert the CI/CD definition an Azure DevOps repository does carry - the YAML pipeline
files - into the target platform, and report precisely what the pipeline consumes that lives
outside the repository (variable groups, service connections, environment approvals,
agent pools), because those are exactly the pieces that make a converted pipeline fail on
first run in ways that look like conversion bugs.
One definition, two targets
The target is resolved in Phase 0 from additionalPlanContext:
target: value |
Output |
github-actions (default) |
One workflow file per pipeline under cicd-migration/github-actions/ |
codepipeline |
A CloudFormation template for the pipeline plus one CodeBuild buildspec per job under cicd-migration/codepipeline/ |
The discovery, inventory and classification phases are identical; only the emission phase
differs. Mapping tables: references/01-tasks-and-variables.md (shared catalog + GitHub
Actions target), references/02-codepipeline-target.md (CodePipeline emission rules).
Scope
MECHANICAL - converted
| Azure Pipelines construct |
GitHub Actions |
CodePipeline / CodeBuild |
trigger (branches/paths) |
on.push.branches / paths |
pipeline source trigger + EventBridge note |
pr |
on.pull_request |
report note (PR builds are a CodeBuild webhook filter) |
schedules (cron) |
on.schedule |
EventBridge Scheduler rule in the template |
stages / dependsOn |
jobs + needs |
pipeline Stages in declared order |
jobs / steps |
jobs.<id>.steps |
one CodeBuild project + buildspec per job |
script / Bash@3 / pwsh |
run with the right shell |
buildspec commands |
pool.vmImage |
runs-on (ubuntu/windows/macos map) |
CodeBuild image + Type (macOS unsupported: report) |
| Common task catalog (UseDotNet@2, DotNetCoreCLI@2, NodeTool@0, UsePythonVersion@0, Docker@2, ArchiveFiles@2, CopyFiles@2, PublishBuildArtifacts@1, DownloadBuildArtifacts@0, PublishTestResults@2) |
setup-* actions / upload-download-artifact / run equivalents |
buildspec phases + artifacts section |
Inline variables |
env |
buildspec env.variables |
Predefined variables ($(Build.SourcesDirectory), $(Build.BuildId), $(Build.SourceBranchName), ...) |
${{ github.workspace }}, ${{ github.run_id }}, ${{ github.ref_name }}, ... (full table in reference 01) |
CODEBUILD_SRC_DIR, CODEBUILD_BUILD_ID, ... |
condition: (succeeded/failed/always, eq/ne on variables) |
if: expression |
buildspec guard or stage condition, with a report note when semantics narrow |
strategy.matrix |
strategy.matrix |
one project per matrix leg + report note |
timeoutInMinutes / continueOnError |
timeout-minutes / continue-on-error |
project TimeoutInMinutes / report note |
SCAFFOLD - emitted incomplete, clearly marked
- Variable groups (
variables: - group:). The VALUES live in the Azure DevOps Library
and cannot be read from the repository. Emit the reference as secrets.<NAME> /
Secrets Manager reference with TODO(migration) per variable group, and a report entry
listing every consuming job. Never invent a value.
- Service connections (AzureRM, AWS, container registries). Emit the OIDC skeleton
(
aws-actions/configure-aws-credentials with role-to-assume: TODO(migration), or the
CodeBuild service role placeholder) plus a report entry naming the connection.
- Environments + approvals. GitHub
environment: emitted on the job; the protection
rules are repository SETTINGS, not YAML - report entry with the exact settings to create.
CodePipeline: a manual approval action inserted where the environment gate was.
- Deployment jobs (
deployment: with runOnce/rolling/canary). Converted as a plain job
plus a report entry: neither target has native in-YAML deployment strategies.
- Pipeline templates (
extends, template: includes with parameters). GitHub target:
reusable workflow (workflow_call) skeleton with the parameter names carried over.
REPORT-ONLY - never transformed
| Construct |
Why |
| Classic (GUI) pipelines |
Not in the repository; the report gives the export guidance |
Marketplace tasks with no cataloged equivalent (e.g. SnykSecurityScan@1) |
Vendor actions exist but version/config parity is a decision - named per occurrence with the closest candidate |
| Task groups, gates, manual intervention tasks |
Live in Azure DevOps, not in YAML |
self-hosted agent pools |
Runner/fleet provisioning is infrastructure, not conversion |
| Azure Artifacts feeds |
Feed migration (CodeArtifact / GitHub Packages) is its own project |
| Boards/work-item integration, service hooks |
Platform features outside the pipeline |
Any task: not in the catalog |
Degrade to report-only with the raw inputs quoted - never guess an action name |
Constraints
- Additive, and that includes files at the repository root. Originals untouched; ALL
output under
cicd-migration/. MIGRATION_REPORT.md goes at the repository ROOT -
the only file allowed there. GitHub Actions workflows only run from .github/workflows/,
and that is exactly why they are NOT emitted there: writing into .github/ mutates the
source tree and can collide with existing workflows. The report instructs the operator to
move them - a safe artifact plus an instruction beats a mutation.
- Never invent a secret value, a role ARN, an account id, a registry URL or a
subscription id.
TODO(migration) + report entry, every time.
- Every emitted workflow/buildspec names its source (comment header
migrated-from: azure-pipelines.yml#<stage>/<job>).
- The manual-action section of the report is named exactly
## Manual Action Items.
- No em dash (U+2014) anywhere in emitted output - use a hyphen.
- Stay on the current branch; do not commit.
Workflow
Phase 0 Read additionalPlanContext: target (github-actions | codepipeline, default
github-actions), plus optional default_branch and aws_region (codepipeline).
Phase 1 Discovery. Find every pipeline YAML (azure-pipelines*.yml, *.yml with the
trigger/stages/jobs top-level shape, template files referenced by them).
Inventory EVERY construct present AND the external dependencies consumed
(variable groups, service connections, environments, pools). Classify
MECHANICAL / SCAFFOLD / REPORT-ONLY.
Phase 2 Convert the MECHANICAL set for the resolved target.
Phase 3 Emit scaffolds (variable-group references, OIDC skeleton, environments,
reusable-workflow skeletons for templates).
Phase 4 Validate: YAML parses; GitHub target - every job has runs-on and at least one
step, every uses: pins a major version, zero $(...) Azure macro syntax survives;
CodePipeline target - the CloudFormation template passes a structural lint
(Resources present, every stage has actions) and every buildspec has phases.
Phase 5 MIGRATION_REPORT.md at the root: full inventory (present and absent), external
dependency table (variable groups, connections, environments), per-construct
classification, Manual Action Items, TODO(migration) cross-reference.
Exit Criteria
- Every pipeline YAML and template in the repository appears in the inventory with a
classification per construct.
- Originals byte-identical. All output under
cicd-migration/; MIGRATION_REPORT.md at
the repository root; no other file created or modified at the root.
- Zero Azure Pipelines macro syntax (
$(Var)) and zero task: keys survive in emitted
output - each is either converted or quoted inside a report entry.
- Every variable group, service connection and environment consumed by the pipeline has a
report entry and, where referenced in output, a
TODO(migration).
- Emitted YAML parses. GitHub target: every job has
runs-on; every uses: carries a
version. CodePipeline target: template lints, every buildspec has phases.
- Every
TODO(migration) has an entry in Manual Action Items, and vice versa.
- The report states which target was resolved and why (explicit vs default).
Non-Goals
- Migrating repository hosting, branch policies, Boards, Artifacts feeds or wikis.
- Provisioning runners, CodeBuild fleets or IAM roles - scaffolds and instructions only.
- Classic (GUI) pipeline conversion - export guidance in the report.
- Executing the migration or registering anything in GitHub/AWS.
1---2name: azure-devops-pipelines-migration3description: Transforms Azure DevOps Pipelines (azure-pipelines.yml and template files) into the target CI/CD chosen at run time - GitHub Actions workflows, or AWS CodePipeline with CodeBuild buildspecs. Converts triggers, stages, jobs, steps, matrices, conditions and the common task catalog; scaffolds what depends on values that live in Azure DevOps (variable groups, service connections, environments); and reports every construct with no counterpart (classic GUI pipelines, task groups, gates, Boards integration). Trigger: Azure DevOps migration, azure-pipelines.yml, ADO to GitHub Actions, Azure Pipelines to CodePipeline, VSTS, TFS pipelines, CI/CD migration.4---56# Azure DevOps Pipelines Migration (to GitHub Actions or AWS CodePipeline)78## Objective910Convert the CI/CD definition an Azure DevOps repository **does** carry - the YAML pipeline11files - into the target platform, and report precisely what the pipeline consumes that lives12**outside the repository** (variable groups, service connections, environment approvals,13agent pools), because those are exactly the pieces that make a converted pipeline fail on14first run in ways that look like conversion bugs.1516## One definition, two targets1718The target is resolved in Phase 0 from `additionalPlanContext`:1920| `target:` value | Output |21|---|---|22| `github-actions` (default) | One workflow file per pipeline under `cicd-migration/github-actions/` |23| `codepipeline` | A CloudFormation template for the pipeline plus one CodeBuild buildspec per job under `cicd-migration/codepipeline/` |2425The discovery, inventory and classification phases are identical; only the emission phase26differs. Mapping tables: `references/01-tasks-and-variables.md` (shared catalog + GitHub27Actions target), `references/02-codepipeline-target.md` (CodePipeline emission rules).2829## Scope3031### MECHANICAL - converted3233| Azure Pipelines construct | GitHub Actions | CodePipeline / CodeBuild |34|---|---|---|35| `trigger` (branches/paths) | `on.push.branches` / `paths` | pipeline source trigger + EventBridge note |36| `pr` | `on.pull_request` | report note (PR builds are a CodeBuild webhook filter) |37| `schedules` (cron) | `on.schedule` | EventBridge Scheduler rule in the template |38| `stages` / `dependsOn` | `jobs` + `needs` | pipeline `Stages` in declared order |39| `jobs` / `steps` | `jobs.<id>.steps` | one CodeBuild project + buildspec per job |40| `script` / `Bash@3` / `pwsh` | `run` with the right `shell` | buildspec `commands` |41| `pool.vmImage` | `runs-on` (ubuntu/windows/macos map) | CodeBuild image + `Type` (macOS unsupported: report) |42| Common task catalog (UseDotNet@2, DotNetCoreCLI@2, NodeTool@0, UsePythonVersion@0, Docker@2, ArchiveFiles@2, CopyFiles@2, PublishBuildArtifacts@1, DownloadBuildArtifacts@0, PublishTestResults@2) | setup-* actions / upload-download-artifact / run equivalents | buildspec phases + artifacts section |43| Inline `variables` | `env` | buildspec `env.variables` |44| Predefined variables (`$(Build.SourcesDirectory)`, `$(Build.BuildId)`, `$(Build.SourceBranchName)`, ...) | `${{ github.workspace }}`, `${{ github.run_id }}`, `${{ github.ref_name }}`, ... (full table in reference 01) | `CODEBUILD_SRC_DIR`, `CODEBUILD_BUILD_ID`, ... |45| `condition:` (succeeded/failed/always, `eq`/`ne` on variables) | `if:` expression | buildspec guard or stage condition, with a report note when semantics narrow |46| `strategy.matrix` | `strategy.matrix` | one project per matrix leg + report note |47| `timeoutInMinutes` / `continueOnError` | `timeout-minutes` / `continue-on-error` | project `TimeoutInMinutes` / report note |4849### SCAFFOLD - emitted incomplete, clearly marked50511. **Variable groups (`variables: - group:`).** The VALUES live in the Azure DevOps Library52 and cannot be read from the repository. Emit the reference as `secrets.<NAME>` /53 Secrets Manager reference with `TODO(migration)` per variable group, and a report entry54 listing every consuming job. Never invent a value.552. **Service connections** (AzureRM, AWS, container registries). Emit the OIDC skeleton56 (`aws-actions/configure-aws-credentials` with `role-to-assume: TODO(migration)`, or the57 CodeBuild service role placeholder) plus a report entry naming the connection.583. **Environments + approvals.** GitHub `environment:` emitted on the job; the protection59 rules are repository SETTINGS, not YAML - report entry with the exact settings to create.60 CodePipeline: a manual approval action inserted where the environment gate was.614. **Deployment jobs** (`deployment:` with runOnce/rolling/canary). Converted as a plain job62 plus a report entry: neither target has native in-YAML deployment strategies.635. **Pipeline templates** (`extends`, `template:` includes with parameters). GitHub target:64 reusable workflow (`workflow_call`) skeleton with the parameter names carried over.6566### REPORT-ONLY - never transformed6768| Construct | Why |69|---|---|70| Classic (GUI) pipelines | Not in the repository; the report gives the export guidance |71| Marketplace tasks with no cataloged equivalent (e.g. `SnykSecurityScan@1`) | Vendor actions exist but version/config parity is a decision - named per occurrence with the closest candidate |72| Task groups, gates, manual intervention tasks | Live in Azure DevOps, not in YAML |73| `self-hosted` agent pools | Runner/fleet provisioning is infrastructure, not conversion |74| Azure Artifacts feeds | Feed migration (CodeArtifact / GitHub Packages) is its own project |75| Boards/work-item integration, service hooks | Platform features outside the pipeline |76| Any `task:` not in the catalog | Degrade to report-only with the raw inputs quoted - never guess an action name |7778## Constraints7980- **Additive, and that includes files at the repository root.** Originals untouched; ALL81 output under `cicd-migration/`. **`MIGRATION_REPORT.md` goes at the repository ROOT** -82 the only file allowed there. GitHub Actions workflows only run from `.github/workflows/`,83 and that is exactly why they are NOT emitted there: writing into `.github/` mutates the84 source tree and can collide with existing workflows. The report instructs the operator to85 move them - a safe artifact plus an instruction beats a mutation.86- **Never invent a secret value, a role ARN, an account id, a registry URL or a87 subscription id.** `TODO(migration)` + report entry, every time.88- **Every emitted workflow/buildspec names its source** (comment header89 `migrated-from: azure-pipelines.yml#<stage>/<job>`).90- **The manual-action section of the report is named exactly `## Manual Action Items`.**91- **No em dash (U+2014) anywhere in emitted output** - use a hyphen.92- Stay on the current branch; do not commit.9394## Workflow9596```text97Phase 0 Read additionalPlanContext: target (github-actions | codepipeline, default98 github-actions), plus optional default_branch and aws_region (codepipeline).99Phase 1 Discovery. Find every pipeline YAML (azure-pipelines*.yml, *.yml with the100 trigger/stages/jobs top-level shape, template files referenced by them).101 Inventory EVERY construct present AND the external dependencies consumed102 (variable groups, service connections, environments, pools). Classify103 MECHANICAL / SCAFFOLD / REPORT-ONLY.104Phase 2 Convert the MECHANICAL set for the resolved target.105Phase 3 Emit scaffolds (variable-group references, OIDC skeleton, environments,106 reusable-workflow skeletons for templates).107Phase 4 Validate: YAML parses; GitHub target - every job has runs-on and at least one108 step, every uses: pins a major version, zero $(...) Azure macro syntax survives;109 CodePipeline target - the CloudFormation template passes a structural lint110 (Resources present, every stage has actions) and every buildspec has phases.111Phase 5 MIGRATION_REPORT.md at the root: full inventory (present and absent), external112 dependency table (variable groups, connections, environments), per-construct113 classification, Manual Action Items, TODO(migration) cross-reference.114```115116## Exit Criteria1171181. Every pipeline YAML and template in the repository appears in the inventory with a119 classification per construct.1202. Originals byte-identical. All output under `cicd-migration/`; `MIGRATION_REPORT.md` at121 the repository root; no other file created or modified at the root.1223. Zero Azure Pipelines macro syntax (`$(Var)`) and zero `task:` keys survive in emitted123 output - each is either converted or quoted inside a report entry.1244. Every variable group, service connection and environment consumed by the pipeline has a125 report entry and, where referenced in output, a `TODO(migration)`.1265. Emitted YAML parses. GitHub target: every job has `runs-on`; every `uses:` carries a127 version. CodePipeline target: template lints, every buildspec has `phases`.1286. Every `TODO(migration)` has an entry in Manual Action Items, and vice versa.1297. The report states which target was resolved and why (explicit vs default).130131## Non-Goals1321331. Migrating repository hosting, branch policies, Boards, Artifacts feeds or wikis.1342. Provisioning runners, CodeBuild fleets or IAM roles - scaffolds and instructions only.1353. Classic (GUI) pipeline conversion - export guidance in the report.1364. Executing the migration or registering anything in GitHub/AWS.