Master de II. ULL. 1er cuatrimestre
Github Actions enables you to create custom software development lifecycle workflows directly in your Github repository. These workflows are made out of different tasks so-called actions that can be run automatically on certain events.
This enables you to include Continues Integration (CI) and continuous deployment (CD) capabilities and many other features directly in your repository.
For instance, at any time during this course each student has two active automatic kanban project boards:
This is an example of workflow.
We can conceive a GitHub Action to automate the last part of this workflow such that when you close the issue in your student board all the issues in the repo board are automatically closed.
Here is a brief glossary of terms (for more see Core concepts for GitHub Actions):
A Workflow is an automated process that is made up of one or multiple jobs and can be triggered by an event.
Workflows are defined using a YAML file in the .github/workflows
directory.
Workflows can be created inside the .github/workflows
directory by adding a .yml
workflow file.
Here in the terminal we do:
1
2
$ mkdir -p .github/workflows
$ touch .github/workflows/nodejs.yml
and use our favourite editor.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
name: Node.js Package
on:
release:
types: [created]
jobs:
build:
...
publish-npm:
...
publish-gpr:
needs: build
...
To manage GitHub Actions from Visual Studio we can install the extension GitHub Actions
To use it, you have to authorize the VsCode extension to access your GitHub acount
We can also use the online GitHub Interface.
The Github Actions Editor is quite clever: Auto-complete can be triggered with Ctrl+Space almost anywhere.
Auto-complete works even inside expressions
A job is made up of multiple steps and runs in an instance of the virtual environment. Jobs can
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
name: Node.js Package
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
- run: npm ci
- run: npm test
publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
...
publish-gpr:
needs: build
runs-on: ubuntu-latest
steps:
...
The needs
attribute inside the publish-npm
job tell us that this job
can not start until the build
step has finished
A step is an individual task that can run commands in a job. A step can be either
Each step in a job executes on the same runner, allowing the actions in that job to share data with each other.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
name: Node.js Package
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2 # An action
- uses: actions/setup-node@v1
with:
node-version: 12
- run: npm ci # A command
- run: npm test
The run
keyword tells the job to execute a command on the runner. In this case, you are using run: npm test
to run the tests in your package
Actions are the smallest portable building block of a workflow and can be combined as steps to create a job.
Here is another example:
1
2
3
4
5
6
7
8
9
10
name: learn-github-actions
on: [push]
jobs:
check-bats-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm install -g bats
- run: bats -v
uses: actions/checkout@v2
tells the job to retrieve v2
of the community action named actions/checkout@v2
.
checkout
action any time your workflow will run against the repositoryβs codeuses: actions/setup-node@v1
installs the node software package on the runner, giving you access to the npm
command.There are two types of actions:
Docker container actions allow the environment to be packaged with the GitHub Actions code and can only execute in the GitHub-Hosted Linux environment.
JavaScript actions decouple the GitHub Actions code from the environment allowing faster execution but accepting greater dependency management responsibility.
Actions require a metadata file to define the
for your action.
The metadata filename must be either action.yml
or action.yaml
.
Type | Operating system |
---|---|
Docker container | Linux |
JavaScript | Linux, MacOS, Windows |
Here you can find instructions if you want to develop an action for other people to use
Here is an example of an action: actions/create-release
Events are specific activities that trigger a workflow run. For example, a workflow is triggered when somebody pushes to the repository or when a pull request is created. Events can also be configured to listen to external events using Webhooks.
Example of Webhook: When you install Travis in your repo a webhook is installed on push so that Travis will know when you push to your repo.
See also Git Hooks
release
eventA runner is a machine with the Github Actions runner
application installed.
runner
waits for available jobs it can then execute.Github Actions files are written using YAML syntax and have either a .yml
or .yaml
file extension. Here are the most important concepts for the workflow file.
The name of your workflow that is displayed on the Github actions page. If you omit this field, it is set to the file name.
1
name: CI for scapegoat module
The on
keyword defines the Github events that trigger the workflow. You can provide a single event, array or events or a configuration map that schedules a workflow.
1
on: push
or
1
on: [pull_request, issues]
You can set up the workflow to only run on certain branches, paths, or tags. For syntax examples including or excluding branches, paths, or tags, see Workflow syntax for GitHub Actions
For instance, the example below runs anytime the push
event includes a file in the sub-project
directory or its subdirectories, unless the file is in the sub-project/docs
directory.
1
2
3
4
5
on:
push:
paths:
- 'sub-project/**'
- '!sub-project/docs/**'
For example, a push
that changed sub-project/index.js
or sub-project/src/index.js
will trigger a workflow run, but a push
changing only sub-project/docs/readme.md
will not.
A workflow run is made up of one or more jobs. Jobs define the functionality that will be run in the workflow and run in parallel by default.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
jobs:
ci-scapegoat:
# Define the OS our workflow should run on
runs-on: ubuntu-latest
strategy:
# To test across multiple language versions
matrix:
node-version: [12.x]
steps: # Clone the repo. See https://github.com/actions/checkout
- uses: actions/checkout@v2
# Example of using an environment variable
- name: Use Node.js ${{ matrix.node-version }} # Will be: "Use Node.js 12.x"
uses: actions/setup-node@v1 # Install node. See https://github.com/actions/setup-node
with:
node-version: ${{ "{{ matrix.node-version" }} }}
# Install a project with a clean slate
- run: npm ci
- run: npm test
# Environment variables
env:
CI: true
Env defines a map of environment variables that are available to all jobs and steps in the workflow. You can also set environment variables that are only available to a job or step. Here is a simple example taken from the GitHub docs on Environment Variables
1
2
3
4
5
6
7
8
9
10
11
12
13
14
jobs:
weekday_job:
runs-on: ubuntu-latest
env:
DAY_OF_WEEK: Mon
steps:
- name: "Hello world when it's Monday"
if: env.DAY_OF_WEEK == 'Mon'
run: echo "Hello $FIRST_NAME $middle_name $Last_Name, today is Monday!"
env:
FIRST_NAME: Mona
middle_name: The
Last_Name: Octocat
CI: true
There are lots of default environment variables set by GitHub
A map of the input
parameters defined by the action.
Each input
parameter is a key/value
pair.
Input parameters are set as environment variables.
The variable is prefixed with INPUT_
and converted to upper case.
Example
Defines the three input parameters (first_name
, middle_name
, and last_name
)
defined by the hello_world
action.
These input variables will be accessible to the hello-world
action as INPUT_FIRST_NAME
, INPUT_MIDDLE_NAME
, and INPUT_LAST_NAME
environment variables.
1
2
3
4
5
6
7
8
9
jobs:
my_first_job:
steps:
- name: My first step
uses: actions/hello_world@master
with:
first_name: Mona
middle_name: The
last_name: Octocat
You can use expressions to programmatically set variables in workflow files and access contexts.
1
${{ "{{ <expression>" }} }}
You can combine literals, context references, and functions using operators.
An expression can be any combination of
1
2
3
4
5
6
7
8
9
env:
myNull: ${{ null }}
myBoolean: ${{ false }}
myIntegerNumber: ${{ 711 }}
myFloatNumber: ${{ -9.2 }}
myHexNumber: ${{ 0xff }}
myExponentialNumber: ${{ -2.99-e2 }}
myString: ${{ 'Mona the Octocat' }}
myEscapedString: ${{ 'It''s open source!' }}
Operator | Description |
---|---|
( ) |
Logical grouping |
[ ] |
Index |
. |
Property dereference |
! |
Not |
< |
Less than |
<= |
Less than or equal |
> |
Greater than |
>= |
Greater than or equal |
== |
Equal |
!= |
Not equal |
&& |
And |
|| |
Or |
GitHub offers a set of built-in functions
Example:
1
format('Hello {0} {1} {2}', 'Mona', 'the', 'Octocat')
Returns 'Hello Mona the Octocat'
contains('Hello world', 'llo')
returns true
Expressions are commonly used with the conditional if keyword in a workflow file to determine whether a step should run.
When you use expressions in an if
conditional,
you do not need to use the expression syntax (${{ }}
)
because GitHub automatically evaluates the if
conditional as an expression.
For more information about if conditionals, see βWorkflow syntax for GitHub Actions.β
Example expression in an if
conditional
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
steps:
- name: Git checkout
if: github.event.check_suite.app.name == 'Netlify' && github.event.check_suite.conclusion == 'success'
uses: actions/checkout@master
- name: Install Node
if: success()
uses: actions/setup-node@v1
with:
node-version: 10.x
- name: Install npm dependencies
if: success()
run: npm install
- name: Run Audit
if: success()
uses: ./.github/actions/run-audit
success()
returns true
when none of the previous steps have failed or been canceled.
See Job status check functions
You can use the *
syntax to apply a filter and select matching items in a collection:
1
2
3
4
5
[
{ "name": "apple", "quantity": 1 },
{ "name": "orange", "quantity": 2 },
{ "name": "pear", "quantity": 1 }
]
The filter fruits.*.name
returns the array [ "apple", "orange", "pear" ]
Here is another example:
1
contains(github.event.issue.labels.*.name, 'bug')
will be true
if the attribute name
of one of the labels of the issue that
has triggered the event is 'bug'
Contexts are a way to access information about workflow runs, runner environments, jobs, and steps. Contexts use the expression syntax. See Context and expression syntax for GitHub Actions at the GitHub Actions Reference.
1
${{ "{{ <context>" }} }}
The matrix context enables access to the matrix parameters you configured for the current job.
For example, if you configure a matrix build with the os and node versions, the matrix context object includes the os and node versions of the current job.
The github context contains information about
You can read most of the github context data in environment variables.
for example, github.ref
contains the branch or tag ref that triggered the workflow run
The env context contains environment variables that have been set in a workflow, job, or step.
This context changes for each step in a job. You can access this context from any step in a job.
The steps context contains information about the steps in the current job that have already run.
Here is a more complex example using step information and functions
1
2
3
4
5
6
...
- name: save vsix
uses: actions/upload-artifact@master
with:
name: ${{ format('vscode-hugo-{0}-{1}.vsix', steps.build_package.outputs.version, github.sha) }}
path: ${{ format('vscode-hugo-{0}.vsix', steps.build_package.outputs.version) }}
The runner context contains information about the runner that is executing the current job.
Examples are runner.os
for the Operating System or runner.temp
for the path of the temporary directory for the runner. This directory is guaranteed to be empty at the start of each job, even on self-hosted runners.
See an example of runner context
The strategy context enables access to the configured strategy parameters and information about the current job.
Here is a more complex example of strategy:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
jobs:
build:
runs-on: $
strategy:
matrix:
include:
os: macos-latest
env:
- TARGET: x86_64-apple-darwin
- COMPILER: clang
- LINKER: clang
os: ubuntu-latest
env:
- TARGET: armv7-unknown-linux-musleabihf
- COMPILER: arm-linux-gnueabihf-gcc-5
- LINKER: gcc-5-arm-linux-gnueabihf
os: ubuntu-latest
env:
- TARGET: x86_64-unknown-linux-musl
- COMPILER: gcc
- LINKER: gcc
Strategy parameters include fail-fast, job-index, job-total, and max-parallel. Here is the output for the Debugging Context to the log example
The secrets context access to secrets set in a repository. See Creating and storing encrypted secrets.
To create a secret:
On GitHub, navigate to the main page of the repository.
Under your repository name, click Settings.
In the left sidebar, click Secrets.
Type a name for your secret in the βNameβ input box.
Type the value for your secret.
Click Add secret.
To use a secret:
1
2
3
4
5
6
steps:
- name: Hello world action
with: # Set the secret as an input
super_secret: ${{ secrets.SuperSecret }}
env: # Or as an environment variable
super_secret: ${{ secrets.SuperSecret }}
For example, to write a github action to publish a npm package in the npm registry I surely need to give GitHub a token so that it can work on my name and publish the package. Thus, the procedure will be:
You create a token for npm with npm token create with read and publish permits:
1
2
3
4
5
6
7
8
9
10
11
12
[~/.../lexer-generator(master)]$ npm token create
npm password:
ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β token β blah-blah-blah-blah-blahblahblah β
ββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββ€
β cidr_whitelist β β
ββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββ€
β readonly β false β
ββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββ€
β created β 2020-03-30T15:39:01.799Z β
β created β 2020-03-30T15:39:01.799Z β
ββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββ
NPM_TOKEN
secrets
context1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
name: Node.js Package
on:
release:
types: [created]
jobs:
build:
...
publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN }}
This example stores the NPM_TOKEN
secret in the NODE_AUTH_TOKEN
environment variable.
When the setup-node
action creates an .npmrc
file, it references the token from the NODE_AUTH_TOKEN
environment variable. See actions/setup-node/README
In the example above,
the setup-node
action creates an .npmrc
file on the runner with the following contents:
1
2
3
//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}
registry=https://registry.npmjs.org/
always-auth=true
For more details, see also Publishing packages to the npm registry
Extend the lab npm-module with an action inside the repo testing-addlogging-aluXXX
to publish the npm package in npmjs after the production tests
run correctly in several operating systems (for example, windows-latest
, mac-os-latest
, ubuntu-latest
) and different node versions
1
2
3
4
5
6
7
8
9
10
11
jobs: # jobs are made of steps
build:
# Define the OS our workflow should run on
runs-on: ${{ matrix.os }}
strategy:
# To test across multiple language versions
matrix:
os: [macos-latest, ubuntu-latest, windows-latest ]
node-version: [12.x, 14.x]
...
...
Here is another example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix: # A job matrix can generate a maximum of 256 jobs per workflow run
os:
- ubuntu-latest
- macos-latest
- windows-latest
node_version:
- 10
- 12
- 14
architecture:
- x64
# an extra windows-x86 run:
include:
- os: windows-2016
node_version: 12
architecture: x86
name: Node ${{ matrix.node_version }} - ${{ matrix.architecture }} on ${{ matrix.os }} # The name of the job displayed on GitHub.
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node_version }}
architecture: ${{ matrix.architecture }}
- run: npm install
- run: npm test
...
See the docs for jobs.<job_id>.strategy.matrix
To inspect the information that is accessible in each context, you can use this workflow file example.
1
[~/.../scapegoat(master)]$ cat .github/workflows/debug.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
name: Debugging contexts
on: push
jobs:
one:
runs-on: ubuntu-16.04
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Dump job context
env:
JOB_CONTEXT: ${{ toJson(job) }}
run: echo "$JOB_CONTEXT"
- name: Dump steps context
env:
STEPS_CONTEXT: ${{ toJson(steps) }}
run: echo "$STEPS_CONTEXT"
- name: Dump runner context
env:
RUNNER_CONTEXT: ${{ toJson(runner) }}
run: echo "$RUNNER_CONTEXT"
- name: Dump strategy context
env:
STRATEGY_CONTEXT: ${{ toJson(strategy) }}
run: echo "$STRATEGY_CONTEXT"
- name: Dump matrix context
env:
MATRIX_CONTEXT: ${{ toJson(matrix) }}
run: echo "$MATRIX_CONTEXT"
The calls toJSON(value)
return a pretty-print JSON representation of value
. You can use this function to debug the information provided in contexts.
Here is an example of output of the action above.
Install and check the former workflow.
Add another step to the former workflow to see the SECRETS
context. What do you see?
GitHub automatically creates a GITHUB_TOKEN secret to use in your workflow. You can use the GITHUB_TOKEN
to authenticate in a workflow run.
When you enable GitHub Actions, GitHub installs a GitHub App on your repository.
The GITHUB_TOKEN
secret is a GitHub App installation access token.
You can use the installation access token to authenticate on behalf of the GitHub App installed on your repository.
The tokenβs permissions are limited to the repository that contains your workflow.
Before each job begins:
For more see Authenticating with the GITHUB_TOKEN
For example, when the repo contains and npm module and
we want to write a github action to publish the npm package in the GitHub Package Registry
it is enough to use the GITHUB_TOKEN
. There is no need to create a new secret
Thus, this is enough to do the job:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14.x'
registry-url: 'https://registry.npmjs.org'
- run: npm install
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPN_TOKEN }}
- uses: actions/setup-node@v2
with:
registry-url: 'https://npm.pkg.github.com'
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
The setup-node
action creates an .npmrc
file on the runner.
When you use the scope
input to the setup-node
action, the .npmrc
file includes the scope
prefix.
By default, the setup-node
action sets the scope
in the .npmrc
file to the account that contains that workflow file.
1
2
3
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
@ULL-ESIT-PL-2021:registry=https://npm.pkg.github.com
always-auth=true
See Publishing packages to GitHub Packages