GitHub Actions Importer, CI Migration, Compatibility, and Modernization: Guided Hands-On Workflow
Use a disposable source-pipeline fixture and generated GitHub Actions candidate to compare triggers, jobs, runner assumptions, secrets, artifacts and deployment behavior on the same synthetic revision.
Learning objectives
- Create a disposable source/target migration fixture.
- Run source and target behavior on the same revision.
- Compare artifact digests and preserve unsupported deployment behavior.
- Use audit/dry-run concepts without requiring live credentials.
- Assemble a reproducible migration evidence packet.
1. Guided lab: migrate one synthetic pipeline without a live legacy server
Create a disposable repository fixture that represents a small
source CI pipeline. The mandatory path does not require credentials
or the Importer container: a checked-in “Importer-like” candidate
and audit report faithfully model the review workflow. If you have
an authorized source system, you may optionally replace the fixture
with a real gh actions-importer dry-run.
2. Preflight and repository layout
- Python 3.11+ and Git are enough for the mandatory path.
- Use only synthetic values; no real tokens or enterprise URLs.
- The source and target simulations both operate on the same local commit.
-
The deployment step writes only beneath
tmp/target. - Delete the entire temporary directory at cleanup.
mkdir -p gha-migration-lab/{source,target,evidence,tmp/target}
cd gha-migration-lab
git init
git config user.email learner@example.invalid
git config user.name "Migration Learner"
printf 'hello migration
' > app.txt
git add app.txt && git commit -m "synthetic source"
git rev-parse HEAD | tee evidence/source-sha.txt
3. Record the source pipeline contract
version: synthetic-ci-v1
triggers:
push: [main]
pull_request: true
agent:
os: linux
tools: [python3]
variables:
BUILD_MODE: release
secrets:
- DEPLOY_TOKEN
stages:
- name: build
commands: [python3 source/build.py]
artifact: dist/app.txt
- name: test
needs: build
commands: [python3 source/test.py]
- name: deploy
needs: test
when: branch == main
manual_approval: true
unsupported_task: legacy-deploy-plugin
The synthetic pipeline intentionally contains one unsupported deployment plugin. That item is the lab’s test of discipline: the generated target is not allowed to quietly invent equivalent behavior.
cat > source/pipeline.yml <<'YAML'
# paste the source pipeline shown above
YAML
sha256sum source/pipeline.yml | tee evidence/source-pipeline.sha256
4. Implement deterministic source evidence
from pathlib import Path
import hashlib, json
root=Path('.')
sha=(root/'evidence/source-sha.txt').read_text().strip()
out=root/'source-out'; out.mkdir(exist_ok=True)
artifact=out/'app.txt'; artifact.write_bytes((root/'app.txt').read_bytes().upper())
report={'sha':sha,'build':'success','test':'success','artifact_sha256':hashlib.sha256(artifact.read_bytes()).hexdigest(),'deploy':'blocked-awaiting-manual-approval','unsupported':['legacy-deploy-plugin']}
(root/'evidence/source-result.json').write_text(json.dumps(report,indent=2))
print(json.dumps(report,indent=2))
5. Inspect the generated candidate before running it
name: migrated-ci
on:
push:
branches: [main]
pull_request:
permissions: {}
jobs:
build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- run: python3 target/build.py
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: app-${{ github.sha }}
path: target-out/app.txt
test:
needs: build
runs-on: ubuntu-24.04
steps:
- run: echo "run target tests"
deploy:
if: github.ref == 'refs/heads/main'
needs: test
environment: migration-lab
runs-on: ubuntu-24.04
permissions: {}
steps:
# TODO(actions-importer): map legacy-deploy-plugin manually.
- run: echo "deployment intentionally not implemented"
Review the candidate line by line. The push and pull-request triggers exist. The runner is explicit. Permissions are empty by default. The build artifact is revision-scoped. The deployment is bound to an environment, but the unsupported legacy plugin remains a TODO. That is correct: conversion should not fabricate trust semantics it cannot prove.
6. Run source and target simulations on the same revision
from pathlib import Path
import hashlib, json
root=Path('.')
sha=(root/'evidence/source-sha.txt').read_text().strip()
out=root/'target-out'; out.mkdir(exist_ok=True)
artifact=out/'app.txt'; artifact.write_bytes((root/'app.txt').read_bytes().upper())
report={'sha':sha,'build':'success','test':'success','artifact_sha256':hashlib.sha256(artifact.read_bytes()).hexdigest(),'deploy':'blocked-pending-mapped-gate','todo':['legacy-deploy-plugin']}
(root/'evidence/target-result.json').write_text(json.dumps(report,indent=2))
print(json.dumps(report,indent=2))
python3 - <<'PY'
import json
s=json.load(open('evidence/source-result.json')); t=json.load(open('evidence/target-result.json'))
assert s['sha']==t['sha']
assert s['artifact_sha256']==t['artifact_sha256']
assert s['build']==t['build']==s['test']==t['test']=='success'
print('core parity: PASS; deployment intentionally blocked')
PY
7. Optional live Importer path
If Docker, GitHub CLI, an authorized source CI account and a
disposable target repository are available, install the official
extension and inspect its version before use. Keep credentials out
of shell history and generated files. Start with
audit or dry-run, not
migrate, because migrate opens a pull
request and therefore mutates repository state.
gh extension install github/gh-actions-importer
gh actions-importer version
gh actions-importer dry-run -h
8. Build the parity evidence packet
printf '%s
' "candidate reviewed; TODO preserved" > evidence/review.txt
sha256sum source-out/app.txt target-out/app.txt | tee evidence/artifacts.sha256
git rev-parse HEAD > evidence/revision.txt
find evidence -maxdepth 1 -type f -print -exec sha256sum {} \;
A reviewer should be able to reconstruct the source revision, source configuration, generated candidate, unsupported item, artifact digest comparison and deployment delta without the original engineer’s memory.
9. Challenge: choose the layer, not a command
Suppose the source pipeline depended on an agent-only certificate
store and the generated workflow passes unit tests on
ubuntu-24.04 but cannot reach the integration endpoint.
Do not immediately choose a self-hosted runner. First classify the
dependency: public CA/tool installation, private-network
requirement, or credential trust boundary. Address the actual layer
and prove it with a read-only connectivity or certificate test.
10. Cleanup and transition
cd .. && rm -rf gha-migration-lab
Lesson 3 compares migration strategies and shows when a compatibility shim is a useful temporary bridge versus permanent technical debt.
Knowledge check
Why does the lab preserve the deployment TODO?
Because unsupported behavior must remain visible until it is mapped or intentionally redesigned and verified.
What proves artifact parity in the mandatory lab?
The source and target artifacts are produced from the same revision and compared by SHA-256 digest.
Why prefer dry-run before migrate?
Dry-run keeps conversion local; migrate creates a pull request and therefore changes repository state.
If target CI is faster but uses a different commit, is it a valid parity comparison?
No. Performance or correctness comparisons require equivalent immutable inputs.
What should you do when an old agent had hidden network/tool capability?
Inventory the actual dependency, choose the narrowest runner/network design that satisfies it, and verify it explicitly.
Official references and version notes
- Automating migration with GitHub Actions Importer — Current Importer commands, prerequisites, supported platforms and review warning.
- GitHub Actions Importer reference — Supplemental settings and migration-reference material.
- Custom transformers — Current custom transformer model for unsupported tasks, runners and variables.
- Migrating to GitHub Actions — Automated and manual migration guidance.
- Secure use reference — Trust-boundary and least-privilege guidance applicable during cutover.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.