Module 5 · CI/CD and Containers
Pipelines as code
Read a CI/CD pipeline defined in YAML (triggers, jobs, steps, dependencies and secrets), load it as data, and map how Tallybook's old GitHub Actions workflow actually ran.
About 15 minutes
The problem
Tallybook's old deployment process was defined in one file, .github/workflows/deploy.yml. Pipelines defined as code have the same advantages as infrastructure as code: they're versioned, reviewed, and can be read by people and programs. They also have the same risk: a few wrong lines can deploy untested code to production.
Before you can review a pipeline, you need to read its YAML fluently, and know a couple of traps.
The concept
YAML
A text format of keys, values and nested blocks, where indentation is structure. Lists start with -.
A GitHub Actions workflow
| Key | Meaning |
|---|---|
on | the trigger: which events start the workflow (a push, a pull request, a schedule) |
permissions | what the workflow's automatic token may do in the repository |
jobs | named jobs; each runs on a fresh machine (runs-on) |
needs | job dependencies: deploy with needs: test waits for test to pass |
steps | each step either uses a published action or runs a command |
${{ secrets.NAME }} | a secret stored in the repository settings |
environment | a deployment target that can require approval |
Without needs, jobs run in parallel.
A YAML trap
In YAML 1.1, which many libraries (including Python's PyYAML) follow, the bare word on means true. So loading a workflow in Python gives a key True, not "on". GitHub reads it correctly; your scripts must handle it.
Example
The old workflow:
from urllib.request import urlopen
import yaml
with urlopen("https://academy.cloudtechanalytics.com/datasets/cicd/deploy.yml") as f:
text = f.read().decode("utf-8")
print(text)name: deploy
on:
push:
branches: ["**"]
permissions: write-all
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install
- run: npm test
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: quickship-dev/ssh-deploy-action@main
with:
host: ${{ secrets.PROD_HOST }}
key: ${{ secrets.DEPLOY_KEY }}
- run: echo "Deploying ${{ github.ref_name }} with key ${{ secrets.DEPLOY_KEY }}"
- run: ssh deploy@${{ secrets.PROD_HOST }} "cd /srv/tallybook && git pull && npm install && sudo systemctl restart tallybook-web"Load it as data. Notice the trigger key:
workflow = yaml.safe_load(text)
print("Top-level keys:", list(workflow))
trigger = workflow.get("on", workflow.get(True))
print("Trigger:", trigger)
print("Permissions:", workflow["permissions"])Top-level keys: ['name', True, 'permissions', 'jobs']
Trigger: {'push': {'branches': ['**']}}
Permissions: write-allNow map the jobs: what each depends on, and what each step does.
for name, job in workflow["jobs"].items():
print(f"job {name}: needs={job.get('needs', 'nothing')}, environment={job.get('environment', 'none')}")
for step in job["steps"]:
kind, value = ("uses", step["uses"]) if "uses" in step else ("run", step["run"])
print(f" {kind}: {value[:75]}")job test: needs=nothing, environment=none
uses: actions/checkout@v4
uses: actions/setup-node@v4
run: npm install
run: npm test
job deploy: needs=nothing, environment=none
uses: actions/checkout@v4
uses: quickship-dev/ssh-deploy-action@main
run: echo "Deploying ${{ github.ref_name }} with key ${{ secrets.DEPLOY_KEY }}"
run: ssh deploy@${{ secrets.PROD_HOST }} "cd /srv/tallybook && git pull && npm iThe deploy job needs nothing, so it starts at the same time as test, not after it. Tests could fail and the deployment would still go ahead. Combined with the trigger (a push to any branch), any engineer's experimental branch was deployed straight to production. Lesson 6 reviews this file line by line.
Walkthrough
- Run the cells. Draw the order the jobs run in.
- What would
branches: [main]change? What wouldneeds: testchange? - Count how many times a secret is used in the workflow, and in which steps.
- Write the YAML for a
testjob that also runs a linter before the tests.
Practice
Practice
How many steps are in the deploy job?
Check your understanding
Answer every question to check.