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.
Contents
Github Actions pricing
Documentation is here>>>.
GitHub Actions is free for all account types but with some limitations:
Product | Storage | Minutes (per month) |
---|---|---|
GitHub Free | 500 MB | 2,000 |
GitHub Pro | 1 GB | 3,000 |
GitHub Free for organizations | 500 MB | 2,000 |
GitHub Team | 2 GB | 3,000 |
GitHub Enterprise Cloud | 50 GB | 50,000 |
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:
Operating system | Minute multiplier |
---|---|
Linux | 1 |
macOS | 10 |
Windows | 2 |
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:
OS | Resources | Price per extra minute |
Linux | 2 cores, 7 GB | $ 0,008 |
Windows | 2 cores, 7 GB | $0,016 |
MacOS | 2 cores, 7 GB | $ 0,08 |
Self-hosted | – | free |
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 nameon
: an event(s), that will trigger this workflowjobs
: 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 withuses
orrun
uses
: an action to executerun
: 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:
[simterm]
$ mkdir -p .github/workflows
[/simterm]
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 the Github:
[simterm]
$ git add .github/workflows/actions-test.yaml && git commit -m "Test flow" && git push
[/simterm]
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:
[simterm]
$ git checkout -b a-new-branch Switched to a new branch 'a-new-branch' $ git push -u origin a-new-branch
[/simterm]
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 therun
, for example in theif
conditions (will be discussed below) - environment variable –
$VARNAME
: a value will set during a task execution from therun
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:
[simterm]
$ argocd login dev-1-18.argocd.example.com Username: admin Password: 'admin' logged in successfully Context 'dev-1-18.argocd.example.com' updated
[/simterm]
Create a token:
[simterm]
$ argocd account generate-token eyJ***3Pc
[/simterm]
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.