Chapter 35Lesson 02~300 minutes

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.

Dry runParity labArtifactsRunnersRollback

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.

Next lesson

GitHub Actions Importer, CI Migration, Compatibility, and Modernization: Configuration, Design Patterns, and Trade-Offs

Continue with the next lesson to build on the current concepts, evidence, security boundaries, and operational practices.

Knowledge check

Why does the lab preserve the deployment TODO?

What proves artifact parity in the mandatory lab?

Why prefer dry-run before migrate?

If target CI is faster but uses a different commit, is it a valid parity comparison?

What should you do when an old agent had hidden network/tool capability?

Official references and version notes

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.