Github Actions actually is very similar to the TravisCI, but have much more closer integration with Github, and even its interface is included in the Github WebUI:
So, let’s take a closer look at its abilities, how to use it, and in the following posts will deploy its self-hosted runners to a Kubernetes cluster and will build a CI/CD pipeline to deploy applications using Github Actions and ArgoCD.
- Github Actions pricing
- Github Actions: an overview
- A workflow file structure
- Getting started: Creating workflow file
- Events
- Manual trigger — workflow_dispatch
- Workflow inputs
- Webhooks: create
- Environment variables
- Secrets
- Conditions and if
- needs — jobs dependency
- Actions
- An ArgoCD application
- Github Actions workflow for ArgoCD
Github Actions pricing
Documentation is here>>>.
GitHub Actions is free for all account types but with some limitations:
For example, our project uses GitHub Team, thus we can have 2 gigabytes and 3000 minutes per month.
With this, minutes are different for Linux, macOS, and Windows:
I.e. from our 3000 total, we can use only 300 minutes if we’re using macOS agents and every additional minute will cost additional money:
Also, Github Actions can be working in Github Cloud, and as self-hosted runners, which can solve an issue with access to your secured resources because Github haven’t static IP ranges, so you’re not able to configure your SecurityGroup/firewalls.
Github suggests periodically download a json-file with updated networks (btw, Github Actions is working on Microsoft Azure), but I’m too lazy to create some additional automation to update the security configuration.
Github Actions: an overview
In the Actions, build flow is the following (see Introduction to GitHub Actions):
- an event (for example, a pull-request or a commit to a repository, see the full list here>>>) triggers a workflow, which contains jobs
- a job contains a list of steps, and every step consist of one or more actions
- actions are running on a runner, and multiply actions of a workflow can be running simultaneously
The main components are:
- runner: a server running on Github Cloud or self-hosted, which will execute a job
- workflow: a procedure described in YAML, that includes one or more job, and is triggered by an event
- jobs: a set of steps that are running on the same runner. If a workflow has multiple jobs, by default they will be started in parallel, but also can be configured with dependencies from each other
- steps: зa task to execute a common command or actions. As steps of the same job are running on the same runner, they can share data with each other.
- actions: main “execution blocks” — can be a set of already prepared tasks, or run simple commands
A workflow file structure
In short, let’s see how a workflow file is built:
-
name
: a workflow name -
on
: an event(s), that will trigger this workflow -
jobs
: a list of tasks of this workflow <JOB_NAME>
-
runs-on
: a runner, which will execute job(s) -
steps
: tasks in this job to be executed with uses or run -
uses
: an action to execute -
run
: a command to execute
Getting started: Creating workflow file
Let’s start with a simple file to see how it works.
In your repository root create a directory called .github/workflows/
- here we will store all workflows, that can be trigger with different events:
$ mkdir -p .github/workflows
In this directory, create a file for your flow, for example, named as .github/workflows/actions-test.yaml
:
name: actions-test
on: [push]
jobs:
print-hello:
runs-on: ubuntu-latest
steps:
- run: echo "Hello, world"
Save it and push to Github:
$ git add .github/workflows/actions-test.yaml && git commit -m “Test flow” && git push
Go to the Github WebUI, switch to the Actions tab, you’ll see this workflow execution:
Events
In Events, you can describe conditions to run that flow.
Such a condition can be a pull request or commit to a repository, a schedule, or some event outside of Github that will run a webhook to your repository.
Also, you can configure those conditions for different branches of the repository:
name: actions-test
on:
push:
branches:
- master
pull_request:
branches:
- test-branch
...
Or use a cronjob, see the Scheduled events:
name: actions-test
on:
schedule:
- cron: '* * * *'
Manual trigger — workflow_dispatch
Also, you can configure an ability to execute a workflow manually by using the workflow_dispatch
in the on
:
name: actions-test
on:
workflow_dispatch
jobs:
print-hello:
runs-on: ubuntu-latest
steps:
- run: echo "Hello, world"
And after this, in the Actions you’ll get a button to run that flow:
Workflow inputs
In your workflow, you also can add some inputs
that will be available as variables in steps via the github.event
context:
name: actions-test
on:
workflow_dispatch:
inputs:
userName:
description: "Username"
required: true
default: "Diablo"
jobs:
print-hello:
runs-on: ubuntu-latest
steps:
- run: echo "Username: ${{ github.event.inputs.username }}"
- run: echo "Actor's username: ${{ github.actor }}"
Here, in the ${{ github.event.inputs.username }}
we are getting a value of the workflow_dispatch.inputs.userName
, and in the github.actor
receiving the Github Actions metadata :
A use case can be, for example, to pass a Docker image tag to deploy with ArgoCD.
Webhooks: create
Beside of the push which we've used above we can configure our workflow on any other event in a repository.
See the full list in the Webhook events.
As another example let’s configure our flow to be running when a new branch or tag is created by using the create
:
name: actions-test
on:
create
jobs:
print-hello:
runs-on: ubuntu-latest
steps:
- run: |
echo "Event name: ${{ github.event_name }}"
echo "Actor's username: ${{ github.actor }}"
Here the ${{ github.event_name }}
is used to display the trigger name.
Create a new branch and push it:
$ git checkout -b a-new-branch
Switched to a new branch ‘a-new-branch’
$ git push -u origin a-new-branch
Check:
Environment variables
Also, Github Actions supports environment variables in workflows.
There is a list of the default variables, see the Default environment variables, and you can create your own on a workflow level, jobs level, per a job, or per a step.
During this, pay attention that you access variables in different ways, see the About environment variables:
-
context variable —
${{ env.VARNAME }}
: a value will be set during a workflow file preprocessing before it will be sent to a runner, use it everywhere excepting the run, for example in the if conditions (will be discussed below) -
environment variable —
$VARNAME
: a value will set during a task execution from the run on a runner - to create an own variable during a job’s execution, use a specific file that is set in the default
$GITHUB_ENV
variable
Variables example:
name: vars-test
on:
push
env:
VAR_NAME: "Global value"
jobs:
print-vars:
runs-on: ubuntu-latest
steps:
# using own varibales
- name: "Test global var as $VAR_NAME"
run: echo "Test value $VAR_NAME"
- name: "Test global var as ${{ env.VAR_NAME }}"
run: echo "Test value ${{ env.VAR_NAME }}"
# using default variables
- name: "Test job var as $GITHUB_REPOSITORY"
run: echo "Test value $GITHUB_REPOSITORY"
# this will be empty, as default variables are not in the context
- name: "Test job var as ${{ env.GITHUB_REPOSITORY }}"
run: echo "Test value ${{ env.GITHUB_REPOSITORY }}"
# using 'dynamic' variables
- name: "Set local var"
run: echo "local_var=local value" >> $GITHUB_ENV
- name: "Print local var as $local_var"
run: echo "$local_var"
- name: "Print local var as ${{ env.local_var }}"
run: echo "${{ env.local_var }}"
And result:
Secrets
Documentation is here>>>.
A secret can be added in a repository Settings > Secrets:
Now, add its use in a workflow. A secret can be cases directly via the ${{ secret.SECRETNAME }}
or can be set to a variable:
name: actions-test
on:
push
env:
TEST_ENV: ${{ secrets.TEST_SECRET }}
jobs:
print-hello:
runs-on: ubuntu-latest
steps:
- run: |
echo "Test secret: ${{ secrets.TEST_SECRET }}"
echo "Test secret: ${{ env.TEST_ENV }}"
Run the flow:
Conditions and if
Github Actions supports a conditions check for jobs by using the if
operator followed by an expression, see the About contexts and expressions.
An example:
name: actions-test
on:
push
jobs:
print-hello:
runs-on: ubuntu-latest
steps:
- id: 'zero'
run: echo "${{ github.actor }}"
- id: 'one'
run: echo "Running because of 'github.actor' contains a 'setevoy' string"
if: "contains(github.actor, 'setevoy')"
# this will not run
- id: 'two'
run: echo "Skipping because of 'github.actor' contains a 'setevoy' string"
if: "!contains(github.actor, 'setevoy')"
- id: 'three'
run: echo "Running because of Step Two was skipped"
if: steps.two.conclusion == 'skipped'
- id: 'four'
run: echo "Running because of commit message was '${{ github.event.commits[0].message }}'"
if: contains(github.event.commits[0].message, 'if set')
- id: 'five'
run: echo "Running because of previous Step was successful and the trigger event was 'push'"
if: success() && github.event_name == 'push'
Here, we are using the github context
, the contains()
function, !=
and &&
operators, and steps
context to check the condition.
The result will be:
needs - jobs dependency
Beside of the if: success()
in steps, you can add jobs dependency on each other by using the needs
:
name: actions-test
on:
push
jobs:
init:
runs-on: ubuntu-latest
steps:
- run: echo "An init job"
build:
runs-on: ubuntu-latest
steps:
- run: echo "A build job" && exit 1
needs: 'init'
deploy:
runs-on: ubuntu-latest
steps:
- run: echo "A deploy job"
if: always()
needs: ['init', 'build']
Here, in the build job we are waiting for the init job to finish, and in the deploy job waiting for both init and build, and by using the if: always()
we've set to run the deploy job regardless of the result of execution of the dependency jobs:
Actions
And the last thing to take a look at is the main component of the Github Actions — the Actions.
Actions allows us to use already existing scripts and utilities from the Github Actions Marketplace, or Docker images from the Docker Hub.
See the Finding and customizing actions.
In the example below, we will use the actions/checkout@v2 to clone a repository roo a runner-agent, and omegion/argocd-app-actions to synchronize an ArgoCD application (see the ArgoCD: an overview, SSL configuration, and an application deploy post for details).
An ArgoCD application
Let’s create a testing application:
Update the argocd-cm
ConfigMap, as by default the admin user have no permissions to use ArgoCD tokens (do not do this on Production!:
...
data:
accounts.admin: apiKey,login
...
Log in:
$ argocd login dev-1–18.argocd.example.com
Username: admin
Password:
‘admin’ logged in successfully
Context ‘dev-1–18.argocd.example.com’ updated
Create a token:
$ argocd account generate-token
eyJ***3Pc
Github Actions workflow for ArgoCD
Add a Secret with this token:
Create a new workflow:
name: "ArgoCD sync"
on: "push"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: "Clone reposiory"
uses: actions/checkout@v2
with:
repository: "argoproj/argocd-example-apps.git"
ref: "master"
- name: "Sync ArgoCD Application"
uses: omegion/argocd-app-actions@master
with:
address: "dev-1-18.argocd.example.com"
token: ${{ secrets.ARGOCD_TOKEN }}
appName: "guestbook"
Push it to a repository, and check its execution:
And the application in ArgoCD now is synchronized:
Done.
Originally published at RTFM: Linux, DevOps, and system administration.
Top comments (0)