How to force to exit in Github Actions step - github-actions

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.

Related

How to throw error and stop action on condition in GH action [duplicate]

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

Github actions: Run step / job in a workflow if changes happen in specific folder

Is there any way for us to control what jobs/steps to run in a workflow based on the changes in a specific folder
Eg:
I have said, following folders in my git repo : a, b, c
On every PR merge to my branch I will trigger a workflow. The workflow will execute jobs say,
A -> B -> C. I want to run job A only if changes are present for folder "a/**", B for "b/**" and so on.
So, If in the PR changes only happen in "a/**"and "b/**" workflow will skip job execution for C, making the workflow run to be A->B
You could use the paths-filter custom action with if conditions at the jobs or step levels, using a setup job as preliminary to check if your specific path has been updated, saving the result as an output.
Here is an example
name: Paths Filter Example
on: [push, workflow_dispatch]
jobs:
paths-filter:
runs-on: ubuntu-latest
outputs:
output1: ${{ steps.filter.outputs.workflows }}
steps:
- uses: actions/checkout#v2
- uses: dorny/paths-filter#v2
id: filter
with:
filters: |
workflows:
- '.github/workflows/**'
# run only if 'workflows' files were changed
- name: workflow tests
if: steps.filter.outputs.workflows == 'true'
run: echo "Workflow file"
# run only if not 'workflows' files were changed
- name: not workflow tests
if: steps.filter.outputs.workflows != 'true'
run: echo "NOT workflow file"
next-job:
runs-on: ubuntu-latest
# Wait from the paths-filter to be completed before starting next-job
needs: paths-filter
if: needs.paths-filter.outputs.output1 == 'true'
steps:
...
That way, you could have something like this in your jobs: A --> B or A --> C depending on the path that has been updated.
Yes: https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#registry_package
This is the syntax:
on:
push:
paths:
- 'a/**'
- 'b/**'

Github Actions Workflow that runs when branch is main AND a matching tag is present [duplicate]

I want to trigger a Github workflow only if a code is pushed to a specific branch and if a tag exists, but my config (github workflow) does not work as expected:
name: Deployment
on:
push:
branches:
- feature/BRANCH-NAME
tags:
- *
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v1
- uses: actions/setup-node#v1
with:
node-version: '10.x'
- name: Install dependencies
run: |
npm install
- name: Lint & build
run: |
npm run build
The workflow is triggered even if a tag does not exist.
How could I fix this?
EDIT: This workaround seemed to have solved my problem at the time of writing but I cannot guarantee that it still works as expected.
Since I couldn't find a way to implement an AND condition (i.e. tagged AND on master), I used the following workaround:
name: Worflow demo
on:
push:
tags:
- v*
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v1
- name: Exit if not on master branch
if: endsWith(github.ref, 'master') == false
run: exit -1
- name: Next job ...
This will trigger if there is a tagged (e.g. tag v1.0.0) commit pushed:
on:
push:
tags:
- v*
The first step ('Exit if not on master branch') then checks if the current branch doesn't end with master and exits the workflow (the subsequent tests will not start):
- name: Exit if not on master branch
if: endsWith(github.ref, 'master') == false
run: exit -1
Hope this helps someone else as well.
can use release event and github.event.release.target_commitish to make only tags on 'my_branch' to trigger the build
name: workflow demo
on:
release:
types:
- published
jobs:
my_job:
runs-on: ubuntu-latest
steps:
- name: build only on my_branch tag
if: ${{ github.event_name == 'release' && github.event.release.target_commitish == 'my_branch'}}
run: "something"
To fix the multiple unintended runs I removed the "branches:" scalar and just include and !exclude tags that I want my workflow to run on.
Following runs on tagged releases, not on release candidates:
name: 'tagged-release'
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
- '!*-rc[0-9]+'
The accepted answer didn't seem to work for me, as pointed out by dilithiummatrix in the comments.
So I tried outputting the available values of the github object, which you can do by adding this in your workflow file to see what is available:
- name: Dump job github var
env:
GITHUB_VAR: ${{ toJson(github) }}
run: echo "$GITHUB_VAR"
From this I noticed that as Billy Clark also pointed out, that github.event.base_ref contains refs/heads/production. So this worked for me:
# Only release from prod branch
- name: Exit if not on production branch
if: endsWith(github.event.base_ref, 'production') == false
run: exit -1
You can so by writing the following YAML code.
Keep in mind that you have to put the branches-ignore so that the workflow is not activated when you create branches. The part where you check whether the tag is pushed to a specific branch is covered in the second part of the answer.
name: Deployment
on:
push:
tags:
- *
branches-ignore:
- '*'
You can check for the name of the branch with the following code; specifically for every step of the job you are trying to accomplish.
- name: job
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
if: contains(env.BRANCH_NAME, <branch-name>)
then follow up with some more code you want the workflow to do.

Allow a Job to run if it passes a script

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 :)

github-action: does the IF have an ELSE?

in github action
I have an if, but I still need to run someother thing if I'm in the else case. Is there a clean way to do it or do I have to do another step with the same condition at false?
- if: contains(['SNAPSHOT'],env.BUILD_VERSION)
name:IF
run: echo ":)"
- if: false == contains(['SNAPSHOT'], env.BUILD_VERSION)
name: Else
run: echo ":("
GitHub Actions doesn't have else statement to run a different command/action/code. But you're right, all what you need to do is to create another step with reversed if condition. BTW, you can just use ! instead of false ==, if you surround your statement with ${{ }}.
Here are some links: if statement, operators
You can do the following which would only run the script where the condition passes:
job_name:
runs-on: windows-latest
if: "!contains(github.event.head_commit.message, 'SKIP SCRIPTS')" <--- skips everything in this job if head commit message does not contain 'SKIP SCRIPTS'
steps:
- uses: ....
- name: condition 1
if: "contains(github.event.head_commit.message, 'CONDITION 1 MSG')"
run: script for condition 1
- name: condition 2
if: "contains(github.event.head_commit.message, 'CONDITION 2 MSG')"
run: script for condition 2
and so on. Of course you would use your own condition here.
You may consider using the haya14busa/action-cond action. It is useful when the if-else operation is needed to set dynamic configuration of other steps (don't need to duplicate the whole step to set different values in a few parameters).
Example:
- name: Determine Checkout Depth
uses: haya14busa/action-cond#v1
id: fetchDepth
with:
cond: ${{ condition }}
if_true: '0' # string value
if_false: '1' # string value
- name: Checkout
uses: actions/checkout#v2
with:
fetch-depth: ${{ steps.fetchDepth.outputs.value }}
Alternative solution to github actions based commands, is to use shell script commands for if else statement.
On Ubuntu machine,example workflow to check if commit has a tag or not,
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v2
- run: |
ls
echo ${{ github.ref }}
ref='refs/tags/v'
if [[ ${{ github.ref }} == *${ref}* ]]; then
v=$(echo ${{ github.ref }} | cut -d'/' -f3)
echo "version tag is ${v}"
else
echo "There is no github tag reference, skipping"
fi
You can also do something like this if you want to check for some data type value without using built in functions
- name: Notify Team on Slack
if: ${{github.repository == 'calebcadainoo/cascade-img'}}
run: |
# some command
Over here, I'm triggering event on each push.
I'm pushing to two repos at the same time but I only want to run my command once.
Read More on Expressions Here:
https://docs.github.com/en/actions/learn-github-actions/expressions