From Azure Pipelines to GitHub Actions
I use Azure Pipelines a lot, both for CI/CD (including automated testing for Azure Data Factory) and for building disposable Azure environments repeatably. Most of my code, however, lives in GitHub. GitHub provides its own service for automating CI/CD software workflows – GitHub Actions – and in this post I compare it to Azure Pipelines.
This might be useful background if you're considering migrating to GitHub Actions or just curious about the comparison. GitHub's guide to migrating from Azure Pipelines to GitHub Actions provides more detailed information, but doesn't say as much about missing features.
Example Azure Pipeline
This is a short Azure Pipeline I built to give me something to reproduce in GitHub actions. It purposely doesn't do much – it's a simple example to enable comparison.
name: AzurePipelines-HelloWorld trigger: [ main ] pool: vmImage: ubuntu-latest steps: - pwsh: Write-Host "Hello from Azure Pipelines!" displayName: Say hello - bash: ls -la $(System.DefaultWorkingDirectory)/.github/workflows displayName: Inspect GitHub workflows folder - task: UseDotNet@2 displayName: Use .NET Core inputs: version: 3.1.x
It's executed on commits to my main
branch and does three things:
- prints a greeting
- lists some files
- installs .NET Core on the build agent.
Using GitHub Actions
GitHub Actions are conceptually close to Azure Pipelines, but with differences in syntax. There's a table of corresponding concepts and keywords below. GitHub calls a pipeline a workflow – a workflow is defined in a YAML file, just like an Azure Pipeline1).
The first difference appears even before you write any code: in Azure, a pipeline is an object defined in Azure DevOps with an associated YAML file. In GitHub, you save a workflow YAML file in repo folder /.github/workflows
and it is the pipeline – no further configuration required.
Here's a GitHub workflow equivalent to the Azure pipeline above:
name: GitHubActions-HelloWorld on: push: branches: [ main ] jobs: examplejob: runs-on: ubuntu-latest steps: - name: Say hello run: Write-Host "Hello from GitHub Actions!" shell: pwsh - name: Checkout repo uses: actions/checkout@v2 - name: Inspect GitHub workflows folder run: ls -la $GITHUB_WORKSPACE/.github/workflows shell: bash - name: Use .NET Core uses: actions/setup-dotnet@v1 with: dotnet-version: 3.1.x
So what's different?
GitHub uses
on
to configure all execution triggers for a workflow, with event type arguments indicating which specific events should trigger a workflow. Azure uses a set of distinct keywords for this:trigger
(used in my example) is equivalent toon
push
pr
is equivalent toon
pull_request
schedules
is equivalent toon
schedule
.
Unlike Azure Pipelines, GitHub workflows can't be triggered manually – this issue is raised often but remains open. Azure also supports the syntax
trigger: none
to disable a pipeline – you can't do this is GitHub either, but I use the branch list[ none ]
as a workaround. (Literally this means “run the workflow on pushes to thenone
branch”, but as long as I don't have one of those I'm good).Both platforms use the
jobs
keyword, but Azure allows you to omit it in single-job pipelines. This is less verbose, although arguably makes pipelines harder to read. Similarly Azure permits single-job pipelines to omit thejob
keyword, while GitHub requires each job to be explicitly and uniquely identified (GitHub'sjobs
collection is a dictionary while Azure's is a list).Jobs can be grouped into
stages
in Azure – there's no parallel concept for that in GitHub. This makes managing some processes trickier, for example approving deployments.The absence of approvals is raised in the manual trigger issue. A workaround devised by Aaron Powell implements each stage in a separate workflow – a stage requiring approval creates a GitHub issue at the end of its workflow, then issue labels are used to indicate approval and control execution of the workflow for the next stage.
Azure pipeline jobs are executed on an agent allocated from a pool – GitHub refers to an agent as a runner. Both platforms provide a number of cloud-based agents/runners (and seem to share VM images), and both permit you to host your own.2) A job's agent type is indicated by the
pool
vmImage
in Azure and itsruns-on
argument in GitHub.steps
are a concept common to both platforms. Code for a step can either be supplied directly as a script or loaded from a pre-defined library. Steps can be given more readable names, but only GitHub allowsname
to be a step's first argument.In Azure,
script
executes a command in the agent's default shell – other shells can be specified explicitly e.g. with keywordspwsh
orbash
. GitHub's equivalent isrun
– without arguments it too uses the agent's default shell, but alternatives can be specified using itsshell
argument.Pre-defined libraries are called tasks in Azure and actions in GitHub. The GitHub equivalent to Azure's
task
keyword isuses
. Both platforms provide a number of built-in tasks and host marketplaces for community extensions.The Git repo containing an Azure Pipeline definition is automatically checked out when the pipeline is executed – not so in GitHub. The additional
actions/checkout@v2
action step is necessary to allow me to use the contents of the repo.
Side-by-side
I've laid out the Azure pipeline and GitHub workflow side-by-side here for direct comparison (you might need to be on a full size screen to see the effect). I've included non-mandatory keywords in the Azure pipeline, showing how alike the two syntax structures are.
name: AzurePipelines-HelloWorld trigger: branches: include: [ main ] jobs: - job: examplejob pool: vmImage: ubuntu-latest steps: - pwsh: Write-Host "Hello from Azure..." displayName: Say hello # (repo already checked out automatically) - bash: ls -la $(System.DefaultWorkingDir… displayName: Inspect GitHub workflows - task: UseDotNet@2 displayName: Use .NET Core inputs: version: 3.1.x
name: GitHubActions-HelloWorld on: push: branches: [ main ] jobs: examplejob: runs-on: ubuntu-latest steps: - name: Say hello run: Write-Host "Hello from GitHub..." shell: pwsh - name: Checkout repo uses: actions/checkout@v2 - name: Inspect GitHub workflows run: ls -la $GITHUB_WORKSPACE/.github… shell: bash - name: Use .NET Core uses: actions/setup-dotnet@v1 with: dotnet-version: 3.1.x
Summary
The major differences I see here are in syntax structure and support around triggers. On the whole, GitHub's syntax enforces more consistency, with the result that workflows are easier to read:
- the single
on
keyword groups all execution triggers into a single place - required container elements (like
jobs
andjob_id
) make workflow structure explicit - allowing
run
oruses
to be preceded byname
makes natural-language action descriptions easy to find.
Trigger support in Azure is more flexible – it's possible to disable a single pipeline, run one manually and use stages
to require approvals. From the associated GitHub issue it's not only Azure DevOps users who miss this functionality.
From my Microsoft-focussed perspective, a practical deficiency of GitHub is the number of ready-rolled tasks available for Visual Studio and Azure, but I suspect it's only a matter of time. Microsoft's growing GitHub Actions library (and the fact that it owns GitHub!) make me wonder if we won't all end up here eventually.
Concepts and keywords
This table maps corresponding concepts and keywords across the two platforms.
Azure Pipelines | GitHub Actions | ||||
---|---|---|---|---|---|
Concept | Keyword | Concept | Keyword | ||
Pipeline | Workflow | ||||
Trigger | trigger /pr /schedules | Event | on (with <event_name> argument) |
||
Stage | stage | Not supported | |||
Job | job (optional) | Job | <job_id> (mandatory) |
||
Dependency | demands | Dependency | needs |
||
Execution condition | condition | Execution condition | if |
||
Agent (from pool) | pool | Runner | runs-on |
||
Script | script /bash /powershell /pwsh | Script | run (with shell argument) |
||
Task | task | Action | uses |
||
Display name | displayName | Display name | name (can be first task/script argument) |
||
Task-specific parameters | inputs | Action-specific parameters | with |
||
Execution condition | condition | Execution condition | if |
Complete syntax guides are available for both Azure Pipelines and GitHub Actions.
If you found this article useful, please share it!
