I am trying to setup a custom action for GitHub Actions and currently this is what I have:
name: 'Install Dependencies'
inputs:
os:
description: 'The OS to fetch the dependencies for'
required: True
runs:
using: "composite"
steps:
- run: echo I am a test
shell: bash
if: startsWith(os, 'Linux')
What I am trying to do is to eventually have a bunch of different shell scripts that are tailored to do the job for a specific OS and therefore in my action, I want to select the appropriate script based on the os parameter that was passed into it.
When I am invoking the action as shown above though, I get Unexpected value 'if'. So my question is: How can I only run execute the run command if a condition is met?
The simplest option is probably to use a naming convention for your scripts and run call them with the name of the os:
myscript.$(os).sh
A workaround is to use bash's || and negating the desired condition to ensure the command that follows is only executed if the condition is true (which is negated to false, which runs the command after ||):
- run: ${{ !startsWith(os, 'Linux') }} || echo "I am a test"
shell: bash
Related
I am writing a GitHub Action that does some CD and it uses yq to insert environment variables into a yaml file for deployment.
I'm trying to read a JSON from a GH secret that will eventually be read from env and loaded into python, where said string will be evaluated as a dictionary.
Running this in a terminal, for example:
yq -i '.value="{\"web\": \"test\"}"' test.yaml
Gives me:
value: '{"web": "test"}'
But in a Github Action, where I am doing this:
env:
JSON="{\"web\": \"test\"}"
...
- name: test
run : |
yq -i '
.value=strenv(JSON)
' deployment.yaml
Gives me:
Error: Bad expression, please check expression syntax
Doing other variations of that string, e.g. '{\"web\": \"test\"}', '\"{\"web\": \"test\"}\"' etc also gives me the same error.
I've tried searching on the yq repository and consulted the documentation but can't seem to find what I am looking for.
To summarise, my problem is that I want to read a JSON string as a string when it is evaluated by yq.
One of yq users has recently contributed to yq's github action docs regarding using env variables in github actions - it may help here:
- name: Get an entry with a variable that might contain dots or spaces
id: get_username
uses: mikefarah/yq#master
with:
cmd: yq '.all.children.["${{ matrix.ip_address }}"].username' ops/inventories/production.yml
- name: Reuse a variable obtained in another step
run: echo ${{ steps.get_username.outputs.result }}
See https://mikefarah.gitbook.io/yq/usage/github-action for more info.
Disclaimer: I wrote yq
I'm developing a Github actions workflow. This workflow runs on Linux, Mac, and Windows.
As part of the workflow, I have to check whether 2 environment variables are equal. If they don't - fail the job.
As described here, Github Actions support if: condition:
steps:
- run: # How can I make a cross-platform failure here?
if: ${{ envA }} != ${{ envB }}
How can I make the job fail if the above condition is true?
In the beginning, I thought of a script, but there must be a more elegant way to fail a job.
I'd do run: exit 1. That will simply exit with an exit code of 1, on all three platforms.
Proof that it's cross-platform: https://github.com/rmunn/Testing/runs/220188838 which runs the following workflow:
name: Test exiting on failure
on: [push]
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout#v1
- name: Try to fail
run: exit 1
- name: Print message if we don't fail
run: echo Should not get here
(An earlier version of this answer recommended "/bin/false", but that would only work on Linux and macOS).
In 2021, there is perhaps a more graceful way to do this:
- name: A/B Check
if: ${{ envA }} != ${{ envB }}
uses: actions/github-script#v3
with:
script: |
core.setFailed('envA and envB are not equivalent!')
Here, we use the github-script action to provide a one liner script that will fail the job. The "A/B Check" step will only run if the condition in the if line is true, so the script will only run in that case, which is what we want.
The nice thing about this approach is that you will get nicely formatted output in the Actions UI in your repo, showing that the "A/B Check" step caused the failure, and why (i.e. "envA and envB are not equivalent").
Note that if you have additional steps in the job after this, and you do NOT want them to run if the A/B check fails, you'll want to use if: success() on them to prevent them from running in that case.
The Github workflow commands docs gives a hint on this.
Toolkit function
Equivalent workflow command
core.setFailed
Used as a shortcut for ::error and exit 1
Given that, you can do the following without using any external workflows.
steps:
- name: A/B Check
if: ${{ envA }} != ${{ envB }}
run: |
echo "::error file={name},line={line},endLine={endLine},title={title}::{message}"
exit 1
I would like to allow a job to run only if its name can be found in some file.
jobs:
job1:
if : run some script that returns true if "${{ github.job }}" is in file.txt
steps:
...
However I'm quite sure i cant run a script outside of run, which I believe can't appear before the steps section, right?
If the option above simply isn't possible, perhaps there is a way to run this script on the first step and have the next steps not run unless the said critical step passed?
Thanks!
You could for example use 2 jobs in your action, where the second job would only be executed if the first one succeed return true using an if condition and outputs.
The workflow would look like something as below:
jobs:
job1:
runs-on: ubuntu-latest
outputs:
output1: ${{ steps.script.outputs.bool }}
steps:
- name: Run the python script
id: script
run: |
value=$((your command running the script here) 2> &1) # should return true
echo "::set-output name=bool::$value" # set the value as output
job2:
runs-on: ubuntu-latest
# Wait from the job1 to be completed before starting job2
needs: job1
if ${{ ${{needs.job1.outputs.output1}} == true }} # won't execute if output of job 1 isn't true
steps:
...
If you want to know more about:
if condition + context and expression
outputs
Note: The contains expression in the if condition could also be used eventually depending on your context (what the script does). It could even be the easiest solution :)
I am new to github actions, and I see two things used for configuring the steps (correct me if i am wrong), with and env.
What is the difference between these two and how they are used.
uses: someAction
with:
x: 10
y: 20
env:
x1: 30
y2: 40
with - is specifically used for passing parameters to the action
env - is used specifically for introducing environment variables that can be accessed depending on scope of the resource
workflow envs - can be accessed by all resources in the workflow except services
job envs - can be accessed by all resources under job except services
step envs - can be accessed by any resource within the step
Here is an example on how parameters are handled
Let's say an action is created with following parameter in action.yaml
name: 'Npm Audit Action'
inputs:
dirPath:
description: 'Directory path of the project to audit'
required: true
default: './'
Then we will provide this parameter through the with tag in our workflow
- name: Use the action
uses: meroware/npm-audit-action#v1.0.2
with:
dirPath: vulnerable-project
Then in the action code we would handle it as such if building a Node.js action
const core = require("#actions/core");
const dirPath = core.getInput("dirPath");
Envs within actions are accessed differently, let's say we are building a Node.js action, then we would access it through process.env. Going back to our example action
name: 'Npm Audit Action'
env:
SOME_ENV: 'hey I am an env'
Then this could be accessed as
const { someEnv: SOME_ENV } = process.env
You can see in the documentation with: used to define a variable.
While env defines an environmnent variable, as defined here and in jobs.<job_id>.env
an environment variable defined in a step will override job and workflow variables with the same name, while the step executes.
A variable defined for a job will override a workflow variable with the same name, while the job executes.
You need both to access secrets:
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 }}
I want to exit a job if a specific condition is met:
jobs:
foo:
steps:
...
- name: Early exit
run: exit_with_success # I want to know what command I should write here
if: true
- run: foo
- run: ...
...
How can I do this?
There is currently no way to exit a job arbitrarily, but there is a way to allow skipping subsequent steps if an earlier step failed, by using conditionals:
jobs:
foo:
steps:
...
- name: Early exit
run: exit_with_success # I want to know what command I should write here
- if: failure()
run: foo
- if: failure()
run: ...
...
The idea is that if the first step fails, then the rest will run, but if the first step doesn't fail the rest will not run.
However, it comes with the caveat that if any of the subsequent steps fail, the steps following them will still run, which may or may not be desirable.
Another option is to use step outputs to indicate failure or success:
jobs:
foo:
steps:
...
- id: s1
name: Early exit
run: # exit_with_success
- id: s2
if: steps.s1.conclusion == 'failure'
run: foo
- id: s3
if: steps.s2.conclusion == 'success'
run: ...
...
This method works pretty well and gives you very granular control over which steps are allowed to run and when, however it became very verbose with all the conditions you need.
Yet another option is to have two jobs:
one which checks your condition
another which depends on it:
jobs:
check:
outputs:
status: ${{ steps.early.conclusion }}
steps:
- id: early
name: Early exit
run: # exit_with_success
work:
needs: check
if: needs.check.outputs.status == 'success'
steps:
- run: foo
- run: ...
...
This last method works very well by moving the check into a separate job and having another job wait and check the status. However, if you have more jobs, then you have to repeat the same check in each one. This is not too bad as compared to doing a check in each step.
Note: In the last example, you can have the check job depend on the outputs of multiple steps by using the object filter syntax, then use the contains function in further jobs to ensure none of the steps failed:
jobs:
check:
outputs:
status: ${{ join(steps.*.conclusion) }}
steps:
- id: early
name: Early exit
run: # exit_with_success
- id: more_steps
name: Mooorreee
run: # exit_maybe_with_success
work:
needs: check
if: !contains(needs.check.outputs.status, 'failure')
steps:
- run: foo
- run: ...
Furthermore, keep in mind that "failure" and "success" are not the only conclusions available from a step. See steps.<step id>.conclusion for other possible reasons.