Table of Contents
- GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows us to automate your build, test, and deployment pipeline.
- We can create workflows that build and test every pull request to our repository or deploy merged pull requests to production. There are many use-cases and events which we can combine to get the outputs we desire.
In a nutshell, we define a set of commands in a file and push it inside our code. when this file is identified by Github, it will run those commands in a cloud device hosting our code.
workflow
: A workflow is a.yml
(learn about its syntax here.) file that we add in the.github/workflows
folder, and check out in our project repo. It consists of a set ofjobs
that will run on our code when triggered by anevent
. Thesejobs
can also be triggered manually. We can have multiple workflows in our projectEvent
: An event is a specific activity in a repository that triggers a workflow run. For eg, a trigger can originate from GitHub when someone creates a pull request, opens an issue, or pushes a commit to a repository. You can also trigger a workflow run on a schedule, by posting to a REST API, or manually. check this list for all events:
3. Jobs
: Jobs are a set of step
s that run sequentially, in the same runner. a workflow defines 1 or more Jobs
and in a job
, we define 1 or more steps
. Each Job runs in its own container and therefore the steps in each job are executed in parallel unless configured otherwise
4. Step
: A step is the smallest unit of command in a Github workflow. Each step is either a shell script that will be executed or an action
that will be run. Steps are executed in order and are dependent on each other. Since each step is executed on the same runner, we can share data from one step to another
- As previously mentioned, the
Jobs
run in parallel containers while thesteps
run sequentially inside the same container. therefore we can mix and match whether to run a particular task in a separate job (to allow it to run in parallel) or in the same job( to allow it to run in sequential order, alongside othersteps
) - The failure of a
step
in ajob
will result in failure of the wholejob
by default(unless configured otherwise) but it won’t affect the other jobs running in parallel
Considering the following example :
name
: Denotes the name of the GitHub action that will show up in the action run historyon
: This section denotes the events that will trigger the jobs defined in thejob
section. there can be many sub configurations in this block. For eg, if we want the workflow to get triggered by a pull request, we can mention the names ofbranches
against which the PR should be made to trigger. another sub-config available for PR istype
: which can take the values ofclosed
,opened
,edited
, .. etc. The complete list of events can be found herejobs
: This section consists of various jobs that will be executed in parallel once the event(s) mentioned inon
section occur. their name can be anything:
jobs:
lint-static_checks-test-build:
<config>
parallel_job_2:
<config>
parallel_job_3:
<config>
...
3.1 Setting up a job.
A job could consist of many attributes for a specific configuration. An example of some of the common attributes would look like these:
jobs:
my_first_job:
permissions:
contents: read
packages: write
runs-on: [self-hosted, ubuntu-latest]
steps:
...
job2:
if: $ <expression>
needs: my_first_job
permissions
: These are the container-level permissions that we are providing to our job. There can be multiple types of permissions.runs-on:
here we are defining the machine that will be used to execute the jobif: $}
: These are conditional expressions that are executed in a declarative manner. the conditions can be applied to various levels in a workflow. it can be used to skip or perform the whole job or just a step. But what do these conditions work upon? the$
can be used to evaluate any condition like$1+2==3
. There are also certain properties that get generated for everystep
orjob
like$job.status == 'success'
. These are called context.needs
: This is an attribute that makes different parallel running jobs run sequentially. ideally,job2
in the first example should be running in parallel tomy_first_job
. by usingneeds
, we are making sure thatjob2
will run aftermy_first_job
steps
: A job contains a sequence of tasks called steps. Steps can run commands, run set up tasks, or run actions in your repository, a public repository, or action published in a Docker registry. Steps can take inputs (in the form of constants) and generate output. The steps in a job are executed sequentially. By default, the steps are configured to fail execution if the previous step has failed or given a failure output, but this behaviour could be modified via conditional expressions.
3.2 Structure of a step
steps:
- name: Setup Path Filter task and Execute
uses: dorny/paths-filter@v2
id: filter
with:
filters: |
md: ['CHANGELOG.md'] - name: FAIL if mandatory files are not changed
if: $ steps.filter.outputs.md == 'false'
uses: actions/github-script@v3
with:
script: |
core.setFailed('Mandatory markdown files were not changed') - name: Generate AAR and APK files.
shell: bash
run: ./gradlew assembleDebug
name
: This is an optional key that gives a small description of the step. if the name key is present, then it will show up in the workflow terminal during executionid
: This is another optional key that uniquely identifies the step. we can get details about the step(its inputs, outputs, execution time, etc) using this key. here the 2nd step “FAIL if mandatory files are not changed” uses the input of its previous step to determine whether to run or fail the action viasteps.filter.*
syntaxuses
andrun
: These are 2 very important features of a step. a step must define either one of these.- when using
uses
, we are letting the runner know that we want to execute a bunch of commands that are written in some action from the marketplace. It’s like using a library. for eg dorny/paths-filter is a popular activity for checking whether some file has changed or not - When using
run
, we are letting the runner know that we want to execute a particular command on the host machine. this is like using a terminal in our own machine: whether the command can execute successfully or not depends upon the already present software on the system and whether it would support its execution. For eg :./gradlew assembleDebug
is a terminal command that we want to run, and it will only execute successfully if the host machine hasgradle
installed by default, or as a part of the previous sequential steps shell
: This command is used in association withrun
command to let the host machine know about the terminal we want to usewith
: This attribute is used with steps that use 3rd party actions (viauses
)only and provides inputs to them. you can pass numbers, strings, or even js scripts via arguments in thewith
block. The keys are defined by those actions themselvesenv
: This attribute is similar towith
, but is used to define inputs for the step itself, and can be used with bothuses
andrun
Since we can have multiple workflows in our repo, there are chances that we will be using the same steps in each of them and this will cause duplication. And since duplication is bad and difficult to maintain, we might want a solution that allows us to isolate steps
from the workflows in a centralised place and reuse them whenever required.
Hereβs when Workflow Composition becomes helpful. Using workflow composition we can create separate files for each step and use them via uses
syntax in any workflow. the steps will be independent of the job or system environment and therefore will be able to run in any workflow with a very small amount of integration.
Hereβs an example of a workflow that DOES NOT use composition:
if we have to create 10 more workflows that use Check lint
and Upload Lint results
, it would require us to copy all those long Gradle commands and path names manually which are error-prone. It would also be difficult to maintain 10 such copies for future modifications.
With composition, this file becomes as small as this:
Only 2 lines are required to run both lint checks and upload results! now we can create 10 or even 100 copies, we donβt need to worry about making a mistake in path files or Gradle commands! this is because these details are present in a separate YML file .github/mini_flows/s3_lint/action.yml
with somewhat similar syntax:
More Stories
How to Buy Your Next Pair of Glasses Online
Google Pixel Buds Vs Apple AirPods – The Battle of the Earbuds
Replace albums with keywords in iCloud Shared Photos β Six Colors