
This article gives a brief introduction to the concepts and syntax of github actions. While theofficial documentation of github actions is comprehensive, the aim of this article is to help avoid those early learning mistakes.
Context 👨🏼🏫
Github actions is a platform to automate developer workflows. It was built to reduce the organisational burden attached to large open-source community driven repositories. This burden would manifest in the form of hundreds, if not thousands, of contributors, branches, pull requests, merges as well as testing, labelling, creating release documentation and many other tasks or events.
The purpose of an action is to listen to these events and trigger a workflow in response. For example, if a contributor raises an issue, you may want to sort it, label it, and assign it automatically. A workflow is a.yml
or.yaml
file with a set of instructions you define.
While CI/CD is often used to convey its utility, it is just one of many possible workflows you can create to serve your needs. The examples in this article are simple workflows created merely to introduce some of the basic concepts.
- Executing Shell Commands
- Accessing Environment Variables
- Calling Composite Actions
- Setting & Passing Variables
- Making Web Service Calls
1.Executing Shell Commands 🐚
The first example below is a very basic workflow executing different commands in their native shells. The point of this example is to illustrate the role of therun-on
andshell
instructions. A workflow refers to all the instructions within the file, which is comprised of one or morejobs
, which in turn is comprised of one or moresteps
.
Eachjob
listed within a workflow is executed concurrently on a different github server, but you can choose to host them yourself. The significance of this means you need to specify which operating system you want yourjob
to run on by using therun-on
keyword. This can include various versions of windows, macOS, ubuntu or even being self-hosted.
If for example all your steps within a job run powershell commands or scripts, you could specifyrun-on: windows-latest
and call it job done as powershell would be understood by the runners of that operating system. But let's say you needed to run a one off Zshell script in the same job, here theshell
keyword can allow you to override the shell of the specified OS by specifying the correct shell language.
Below is an example of 'Hello World' being printed to the console inpowershell
,bash
,zshell
and evenpython
, which are all being run on awindows
github server. Github also acceptspowershell
,pwsh
(powershell core) andcmd
when overriding for Windows commands. See the code example for more useful information in the comments and follow all links to source file.
name:1 - Run Hello Worlds scripts# <- Workflow name.on:# <- The trigger definition block.push:# <- An event to trigger the action, of type push.branches:[# <- Target Branches. Accepts an array.main,another-branch]jobs:# <- The execution of work block.Job-Identifier:# <- Job ID. Contains related action steps.name:Executing Hello World Script# <- Job Name.runs-on:windows-latest# <- Tells the server which OS to run on. Can also be windows, macOS, or even self-hosted.steps:# <- Step definitions.-uses:actions/checkout@v2# <- Use keyword selects an action. Actions/ path in github is where common actions are predefined.-name:Printing Powershell# <- Optional step name, but advised.run:./Powershell/HelloWorld.ps1# <- Run keyword executes a command. In this case a powershell script.-name:Printing Bashshell:bash# <- Overrides the default shell language of your specified server.run:./Bash/HelloWorld.sh-name:Printing ZShellshell:shrun:./Zshell/HelloWorld.zsh-name:Printing Pythonshell:pythonrun:exec(open('./Python/HelloWorld.py').read())
Run ./Powershell/HelloWorld.ps1Hello World from Powershell!Run ./Bash/HelloWorld.shHello World from Bash!Run ./Zshell/HelloWorld.zshhello world from Zshell!Runexec(open('./Python/HelloWorld.py').read())Hello World from Python!
2.Accessing Environment Variables 🌱
The following example illustrates how you can readenvironment variables
. There are two kinds of variables, those provided by github which use protected names and can be found in thedocumentation, and custom variables you can set yourself throughout your workflow.
Another significant feature of theworkflow
,job
andstep
relationship is how custom variables are scoped. Declared variables within the workflow using the keywordenv
follow an access hierarchy and can only be accessed within the element they were defined. Those variables declared at the highestworkflow
level can be accessed by alljobs
andsteps
. Variables declared within ajob
can only be used by steps within that job and if declared inside astep
they can only be used by that step.
2.1 Custom & Protected Environment Variables
In the below workflow example you can see that 3 custom variables are declared at different levels:BEST_PINT
,BEST_WHISKEY
andBEST_COCKTAIL
. In thePrint Variables to Script
step ascript is executed to print these variables to the console along with a sample of various set github environment variables.
name:2 - Access Github Environment Variableson:push:branches:[main,another-branch]pull_request:# <- Pull request trigger. Used in Example 2.2.branches:[main# <- If any pull request is made to branch 'main'.]env:BEST_PINT:Guinness# <- Custom environment variable declared at workflow level.jobs:#Example 2.1Access-Environment-Variables:name:Print Github Environment Variablesruns-on:windows-latestenv:BEST_WHISKEY:Midleton# <- Scoped to this job and subsequent steps.steps:-uses:actions/checkout@v2-name:Print Variables to Scriptrun:./Powershell/GithubEnvVariables.ps1env:BEST_COCKTAIL:Whiskey Sour# <- Scoped to this step only.-name:Inspect Environment Variablesrun:env# <- Prints to output the available variables to this step.
The owner and repository name.GITHUB_REPOSITORY:'Mulpeter91/Github-Actionman'The commit SHA that triggered the workflow.GITHUB_SHA:'321d557ec2d724d2c6aaf056b14859ea8468051e'The jobidyou assigned to the current job.GITHUB_JOB:'Job-Identifier-Sample'A unique numberforeach workflow run within a repository. This number does not changeifyou re-run the workflow run.GITHUB_RUN_ID:'1836698654'An unique numberforeachtimethe same workflow is run again. Starts at 1 and increments by 1.GITHUB_RUN_NUMBER:'18'The name of the runner executing the job.RUNNER_NAME:'GitHub Actions 4'I love a pint of Guinness with a glass of Midleton and end the night on a Whiskey Sour.
A useful command to inspect available environment variables within a step isrun: env
. Notice that the below output does not containBEST_COCKTAIL
because it was defined and scoped to the previous step.
...APPDATA=C:\Users\runneradmin\AppData\RoamingAZURE_EXTENSION_DIR=C:\Program Files\Common Files\AzureCliExtensionDirectoryBEST_PINT=GuinnessBEST_WHISKEY=MidletonCABAL_DIR=C:\cabalChocolateyInstall=C:\ProgramData\chocolatey...
You must remember to use the correct syntax for referencing variables in your target shell. For example, Windows runners would required the format$env:NAME
while the Linux runners using bash shell would use$NAME
.
2.2 Specific Event Variables
Most github environment variables will always populate, such asGITHUB_ACTOR
but some will only be populated during a specific event trigger. In the aboveworkflow
example you can see a trigger has been added forpull_request
. This has been added to show you some of the variables that will only populate during that event, such asGITHUB_BASE_REF
andGITHUB_HEAD_REF
.
#Example 2.2Pull-Request-Variables:name:Obtain variables useful to a pull requestruns-on:windows-latestenv:var:nothingsteps:-uses:actions/checkout@v2-name:Print Variables for Pull Request# <- Add a pipe key on the run command to make a multiple.run:|Write-Host "Actor: $Env:GITHUB_ACTOR"Write-Host "Target Branch: $Env:GITHUB_BASE_REF"Write-Host "Source Branch: $Env:GITHUB_HEAD_REF"
Actor: Mulpeter91Target Branch: mainSource Branch: pull-request-ex
2.3 Accessing Event Metadata
Another variable worth noting and is heavily effected by the action event type isGITHUB_EVENT_PATH
. This variable contains the directory within your runner to a temporarily storedevent.json
file. This file contains substantial metadata regarding the specific event triggered within the workflow and can be fed into a json object for easy access to specific data nodes.
Every event type has it's own structured version of the file. So what exists in apull_request
:event.json
will not exactly match the nodes in apush
:event.json
.
#Example 2.3-name:Print Json from Action Event Filerun:./PowershellEventFile.ps1
"Event metadata file path:$Env:GITHUB_EVENT_PATH`n""File Contents:"$EVENT_FILE = Get-Content -Path$Env:GITHUB_EVENT_PATHWrite-Host$EVENT_FILE$EVENT_JSON =$EVENT_FILE | ConvertFrom-Json"`nSample selectors"Write-Host"OBJECT.head_commit.author.username:"$EVENT_JSON.head_commit.author.usernameWrite-Host"OBJECT.head_commit.url:"$EVENT_JSON.head_commit.url
Event metadata file path: D:\a\_temp\_github_workflow\event.jsonFile Contents:(see above consolelink)Sample selectorsOBJECT.head_commit.author.username: Mulpeter91OBJECT.head_commit.url: https://github.com/Mulpeter91/Github-Actionman/commit/443da01e18050bd8912d3fac24a86f0c340a2ea8
3.Calling Composite Actions ⚙️
Composite actions are a specific type of workflow file which are designed to abstract out and reuse a set of instructions for one or more requesting workflows. They are typically stored in their own repositories, such asGithub's own shared actions orGoogle's integration actions, but they can also be stored and executed in the same repository.
A step utilises theuses
keyword when executing acomposite action
. In the below example you will notice two steps each using a composite action. The first is using github's sharedactions/checkout@v2
and the other is using our localcomposite action
.
Composite actions depend on targeted releases to know which version of the code to execute. In the case ofcheckout@v2
this is referencing releasev2
in thecheckout
repository. You need to checkout your code in order to build it, test it or in our case execute composite actions.
name:3 - Running a local Composite Actionon:push:branches:[main,another-branch]jobs:Run-Composite-Action:name:Print message from another actionruns-on:windows-lateststeps:-uses:actions/checkout@v2# <- Required to checkout your code in order to access composite actions from with the repo-name:Use hello world composite actionuses:./.github/actions/hello-world# <- Use keyword for calling other actions
Thecomposite action
file requires aname
anddescription
field with an optionalauthor
field. The run also needs to addusing: 'composite'
before executing its steps.
name:Print Hello Worlddescription:Prints a Hello World message.author:Robert Mulpeter @Mulpeter91runs:using:"composite"# <- Required declaration of a composite action.steps:-run:Write-Host "Hello World from Composite Action!"shell:pwsh
Run ./.github/actions/hello-worldRun Write-Host"Hello World from Composite Action!"Hello World from Composite Action!
Another important point regardingcomposite actions
is that they must be defined inside a file called eitheraction.yml
oraction.yaml
. It is recommended that if you have multiple composite actions in the same repo that you house them in their own directories within the.github
directory. While these directories can contain other files such as docker files, they must containoneaction
file. See workingrepo for an example.
4.Setting and Passing Variables 🤾
The belowstep
examples are all run on the sameworkflow
file and combine parts of the previous code with the added fun of setting variables from outside theyml
file and passing variables around the workflow.
4.1 Passing Parameters to Composite Action
In the below example we are using acomposite action
much like example 3 but withinput
parameters. Thestep
passing these named parameters to the action with thewith
keyword and<variable>
name, in this case 'message'.
jobs:Create-Variables:name:Creating and passing variablesruns-on:windows-lateststeps:#Example 4.1-uses:actions/checkout@v2-name:Use print message composite actionuses:./.github/actions/print-messagewith:# <- With keyword to signify parametersmessage:"CobraKaineverdies"# <- Named parameter in the called action.
The composite action lists its parameters with theinputs
keyword. Parameters can berequired: true
orfalse
, include adescription
and adefault
value if no value is passed. In our case, amessage
value is sent but aversion
value is not.
name:Print Parametersdescription:Prints a message passed from the workflow.author:Robert Mulpeter @Mulpeter91inputs:# <- keyword for defining action parameters.message:required:truedescription:"Themessagetobeprinted"version:required:falsedescription:"Theversion."default:"🤟🏻"runs:using:"composite"steps:-run:Write-Host ${{ inputs.message }} ${{ inputs.version }}shell:pwsh
Run ./.github/actions/print-messageRun Write-Host Cobra Kai never dies 🤟🏻Cobra Kai never dies 🤟🏻
4.2 Set Variables from Environment File
The following example combines a parameterised composite action with reading the contents of an.env
file into the environment variables for access by the workflow.
#Example 4.2-name:Set variables from environment fileuses:./.github/actions/read-env-filewith:filePath:./.github/variables/variables.env
Using>> $Env:GITHUB_ENV
instructs github to read the variable into the environment variable dictionary.
name:Read Env Variables from filedescription:Reads environment variables from a passed .env file.author:Robert Mulpeter @Mulpeter91inputs:filePath:required:truedescription:"Filepathtovariablefile."default:./.github/variables*runs:using:"composite"steps:-run:|Get-Content ${{ inputs.filePath }} >> $Env:GITHUB_ENV # <- Adding directly $Env:GITHUB_ENV saves at the workflow levelshell:pwsh
It is advised to keep all variable related files within the.github
directory.
DOJO_1=Miyagi-Do Karate
Run Get-Content ./Powershell/Variables.ps1>>$Env:GITHUB_ENV Get-Content ./Powershell/Variables.ps1>>$Env:GITHUB_ENVenv: DOJO_1: Miyagi-Do Karate
4.3 Set Variables from Powershell File
The following example achieves the same outcome of example 4.2 but adds environment variables by executing apowershell
script directly in the workflow step.
#Example 4.3-name:Set variables from powershell filerun:Get-Content ./Powershell/Variables.ps1 >> $Env:GITHUB_ENV
DOJO_2=Eagle Fang KarateDOJO_3=Cobra-Kai Karate
Run Get-Content ./Powershell/Variables.ps1>>$Env:GITHUB_ENV
4.4 Set Variables from Local Step Variable
The below example takes a local environment variable declared in the step and reads it directly into the github environment dictionary. Note from the below console output, that the variable$LOCAL_VARIABLE
has been read into the dictionary under variable$WORKFLOW_VARIABLE
which is accessible in the subsequentInspect Environment Variables
step.
#Example 4.4-name:Set local step variable to environment variablerun:|echo "WORKFLOW_VARIABLE=$(echo ${Env:LOCAL_VARIABLE})" >> $Env:GITHUB_ENVenv:LOCAL_VARIABLE:Karate Kid-name:Inspect Environment Variablesrun:env
echo"WORKFLOW_VARIABLE=$(echo${Env:LOCAL_VARIABLE})">>$Env:GITHUB_ENVenv: DOJO_1: Miyagi-Do Karate DOJO_2: Eagle Fang Karate DOJO_3: Cobra-Kai Karate LOCAL_VARIABLE: Karate KidRunenvenv: DOJO_1: Miyagi-Do Karate DOJO_2: Eagle Fang Karate DOJO_3: Cobra-Kai Karate WORKFLOW_VARIABLE: Karate Kid
4.5 Pass Variable to Dependant Job
We previously noted thatjobs
are run concurrently by default and that variables are scoped to the element they are defined in. The following example illustrates how you can enforce a dependency between jobs to have them run consecutively to each other by using theneeds
array and pass a variable from the initial job to thedependent job
using theoutputs
keyword rather than sending everything to the high level environment dictionary.
The below step is extracted from the first jobcreate-variables
and uses theoutputs
keyword with an object reference to stepstep_output
. This step in turn uses the::set-ouput name=NAME::Value
command to set the outputted variable.
jobs:create-variables:name:Creating and passing variablesruns-on:windows-latestoutputs:output1:${{ steps.step_output.outputs.TONIGHTS_DINNER }}#Example 4.5-id:step_outputname:Create variable output from steprun:|echo "::set-output name=TONIGHTS_DINNER::Burrito"
The next step which is in the subsequentObtain-Variables
job then uses theneeds
keyword to wait for the referenced job to complete. The step then references the outputted variable and assigns it to the internalDinner
variable.
Obtain-Variables:needs:[Create-Variables]# <- Jobs run concurrently by default. Over this with the 'needs' keyword to set dependents.name:Reading previous variablesruns-on:windows-lateststeps:#Example 4.5-name:Print output variablerun:|Write-Host "Tonights dinner will be " $Env:Dinnerenv:Dinner:${{ needs.create-variables.outputs.dinner }}
Run Write-Host"Tonights dinner will be"$Env:DinnerTonights dinner will be Burrito
5. Web Requests
The below workflow demonstrates a series of simple web calls to theGithub Apipulls
endpoint, which returns information on pull requests. You can feed the response into a json object to access relevant data. The belowInvoke-WebRequest
calls will work because this repo is public. If private you will need tocreate an OAuthPersonal Access Token
to the header with-Headers @{"Authorization"="Bearer <token>"}
. If the repository belongs to an organisation to which you are a member you will need authorize that created token to enable access viaconfigure SSO
.
name:5 - Process Web Requestson:pull_request:branches:[main]jobs:Obtain-Pull-Request-Data:name:Call Github APIruns-on:windows-latestenv:PR_STATE:closed# <- query parameters to github are case sensitivesteps:-uses:actions/checkout@v2-name:Call the Github /pulls endpointrun:./Powershell/GithubWebRequests.ps1
"This will return a list of all open pull requests:"$URI="https://api.github.com/repos/$Env:GITHUB_REPOSITORY/pulls"Write-Host$URI"`nThis willreturnall pull requests of a specified state:"$URI = "https://api.github.com/repos/$Env:GITHUB_REPOSITORY/pulls?state=$Env:PR_STATE"Write-Host$URI"`nThis will return a specific pull request:"$PR_NUMBER=$Env:GITHUB_REF_NAME-replace"/.*"# <- You can also get the PR number from the pull request event file.$URI="https://api.github.com/repos/$Env:GITHUB_REPOSITORY/pulls/$PR_NUMBER"Write-Host$URI$RESPONSE= Invoke-WebRequest-Uri$URI-Method Get-TimeoutSec 480Write-Host$RESPONSE"`nAccessing variables from the object:"$JSON_OBJECT =$RESPONSE | ConvertFrom-JsonWrite-Host "HTML URL:"$JSON_OBJECT.html_urlWrite-Host "TITLE:"$JSON_OBJECT.titleWrite-Host "BODY:"$JSON_OBJECT.bodyWrite-Host "USER:"$JSON_OBJECT.user.loginWrite-Host "REQUESTED REVIEWERS:"$JSON_OBJECT.requested_reviewersWrite-Host "MERGE_COMMIT_SHA:"$JSON_OBJECT.merge_commit_sha
This willreturna list of all open pull requests:https://api.github.com/repos/Mulpeter91/Github-Actionman/pullsThis willreturnall pull requests of a specified state:https://api.github.com/repos/Mulpeter91/Github-Actionman/pulls?state=closedThis willreturna specific pull request:https://api.github.com/repos/Mulpeter91/Github-Actionman/pulls/24Accessing variables from the object: HTML URL: https://github.com/Mulpeter91/Github-Actionman/pull/24TITLE: Test TitleBODY: Test BodyUSER: Mulpeter91REQUESTED REVIEWERS: MERGE_COMMIT_SHA: bd98939094bdb3d775966900ec43a126cf5fac80
Conclusion
The purpose of this article and these examples was to give you an introduction to basic concepts and syntax in order to continue learning github actions with a clearer vision of the platform. But this is just the tip of the iceberg. Github actions are capable of far more precise workflows with the use of more complex syntax.
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse