Using Github Actions for some CI/CD.
Currently I am experiencing strange behavior where my jobs are being skipped despite the conditions being met. deploy-api has two conditions, if code was pushed to master and test-api was a success. But even though we are meeting those conditions, it is still being skipped.
jobs:
test-api:
name: Run tests on API
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v1
- name: Get dependencies
run: npm install
working-directory: ./api
- name: Run tests
run: npm run test
working-directory: ./api
deploy-api:
needs: test-api # other job must finish
if: github.ref == 'refs/heads/master' && needs.test-api.status == 'success' #only run if it's a commit to master AND previous success
As seen in the picture the second job is being skipped despite the push being on the master branch (as seen on the top) AND the previous job being successful.
Am I missing something in the code? Does anyone know of a workaround that can be used?
It would be nice if the UI told the user why it was skipped!
Use needs.test-api.result == 'success' (there is no .status) in the if expression.
See https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#needs-context.
Related
there are some workflows in a repository and triggered by push behavior.(sometime a push will trigger 2 workflows, sometimes will trigger 5 workflows)
but there are some common works like 'build docker image' for integration test.
I'm not sure how to avoid the duplicated work, I tried reuable workflow, but that will build the image 2 or more times。
is there anyway like the needs keywork to make all triggered action depends on the same job result ?
Check if your use case is similar to "Avoid re-running wokflow for the same commit", with Samuel Ryan's workaround:
Yes, you can use the Check Runs API to identify Workflow runs for a ref.
As luck would have it, someone has already built a comprehensive Action for this use-case: fkirc/skip-duplicate-actions.
Add a new “pre” job to your Workflow, this job uses fkirc/skip-duplicate-actions to determine if your main job should be skipped
Add a condition to your main job using the should_skip output of the “pre” job.
For example, adapted from the fkirc/skip-duplicate-actions README:
jobs:
pre_job:
runs-on: ubuntu-latest
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions#v3.4.0
with:
skip_after_successful_duplicate: 'true'
main_job:
needs: pre_job
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
runs-on: ubuntu-latest
steps:
- run: echo "Running slow tests..." && sleep 30
We do have a problem with a GitHub Actions job which is always skipped although all "needed" jobs did run successfully. That's the job:
deploy-api:
needs: [build-test-api, terraform-apply, set-deployment-env]
uses: ./.github/workflows/workflow-api-deploy.yml
To verify that all needs did pass, I have added another job for debugging and printed the result of the needed jobs.
debug-deploy-api:
runs-on: ubuntu-latest
needs: [build-test-api, terraform-apply, set-deployment-env]
if: always() # Had to add this, otherwise it would be skipped just as "deploy-api".
steps:
- run: |
echo "Result of build-test-api: ${{ needs.build-test-api.result }}"
echo "Result of terraform-apply: ${{ needs.terraform-apply.result }}"
echo "Result of set-deployment-env: ${{ needs.set-deployment-env.result }}"
The output is
Result of build-test-api: success
Result of terraform-apply: success
Result of set-deployment-env: success
I don't understand why deploy-api is skipped.
Job began to be skipped after this change
The behavior started after adding a dependency to build-test-api:
With this version of build-test-api, the deploy job did run just fine:
build-test-api:
uses: # reusable WF from internal repo
needs: set-deployment-env
After changing it into
build-test-api:
uses: # reusable WF from internal repo
needs: [set-deployment-env, auto-versioning]
if: |
always() &&
(needs.set-deployment-env.result == 'success') &&
(needs.auto-versioning.result == 'success' || needs.auto-versioning.result == 'skipped')
deploy-api has been skipped always. But build-test-api is, despite that change, still running fine and even appends the created artifact to the workflow run.
Activating runner and step debug logging did not reveal any insights on why the job is still skipped. Any ideas?
Meanwhile I did contact the GitHub Premium Support and they provided a solution:
deploy-api:
if: success('build-test-api') # This line is required, if any of the previous job did not end with status 'success'.
needs: build-test-api
uses: ./.github/workflows/48-reusable-workflow-2.yml
I think I also know why: The documentation says:
You can use the following status check functions as expressions in if conditionals. A default status check of success() is applied unless you include one of these functions.
And definition of success() is as follows:
Returns true when none of the previous steps have failed or been canceled.
The only issue I think is, that is should be:
Returns true when none of the previous steps have failed, canceled or skipped.
If we were to use minimal resources, doing these steps would look something like:
setup --- checkout, install fixed versions via cache
then trigger in parallel 1 command to run:
lint
test
e2e
all in parallel.
Unfortunately, with the current options I've found out there, the best scenario I can find is to have a separate workflow file for lint, test, e2e that are called by a main workflow and upon success build an artifact.
The issue with this is.
checkout will run 3 times.
install command will run 3 times.
Which can be mitigated with caching, but you're still looking at 10-15 seconds extra per job for those steps.
Is there a way to optimise this and still keep the reusable steps?
Even if I would keep everything in 1 giant file, I'd still define the lint, test, e2e steps as separate jobs and not as 1 single step part of a job.
Can we call a 'workflow' and run it in the same environment on the spot somehow?
This example calls checkout and caches the repo using the $ {{ github.run_id }} to prevent the cache conflicting with other runs. The cache is then used by the child workflows in place of checking out. e2e, test and lint will wait for the cache to be created and then all run in parallel.
Depending on your use case it may be excessive to create a new cache for each run_id, you can adjust this as you need. If speed is what you're after I'd also suggest only caching the files that you need for your child workflows, and not take everything like I've done in the example below.
name: main_caller
on: workflow_dispatch
jobs:
cache_checkout:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v3
- uses: actions/upload-artifact#v3
with:
name: my-checkout-${{ github.run_id }}
path: ${{ github.workspace }}/**/*
run_lint:
needs: cache_checkout
uses: ./.github/workflows/lint.yml
run_test:
needs: cache_checkout
uses: ./.github/workflows/test.yml
./.github/workflows/lint.yml example: (e2e and test will follow this pattern)
name: lint
on: workflow_call
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Get Cached Checkout
uses: actions/download-artifact#v3
with:
name: my-checkout-${{ github.run_id }}
- name: Do linting things
run: echo "I'm linting!"
Edited as per comments - all jobs in GitHub run in parallel, but to have cleaner example you could use matrix strategy. To make it work you need it to depend on a 'setup' job that will install everything and create the artifacts (this is done using the needs keyword).
In the following example the install job would setup everything and then the example job, which uses matrix strategy, will run the lint/test/e2e flow in separate parallel jobs after it downloads the required artifacts.
jobs:
install:
runs-on: ubuntu-latest
- uses: actions/checkout#v3
- name: Compile artifacts
run: *install fixed versions/create artifacts*
- uses: actions/upload-artifact#v3
with:
name: my-artifact
path: path/to/my_artifact
example:
runs-on: ubuntu-latest
needs: install
strategy:
matrix:
action: [lint, test, e2e]
steps:
- uses: actions/download-artifact#v3
with:
name: my-artifact
path: path/to/artifact
- name: lint
if: ${{ matrix.lint }}
run: echo "lint"
- name: test
if: ${{ matrix.test }}
run: echo "test"
- name: e2e
if: ${{ matrix.e2e }}
run: echo "e2e"
It will create a workflow like this:
I'm trying to follow an example Github has for testing my build with github actions, and then compressing the test results and uploading them as an artifact.
https://help.github.com/en/actions/automating-your-workflow-with-github-actions/persisting-workflow-data-using-artifacts#uploading-build-and-test-artifacts
I'm having trouble with what to do when my tests fail though. This is my action. When my tests pass everything works great, my results are zipped an exported as an artifact, but if my tests fail, it stops the rest of the steps in the job, so my results never get published.
I tried adding the continue-on-error: true https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error
This makes it continue after it fails and uploads my test results. but then the job is marked as passed, even though my test step failed. Is there some way to have it upload my artifact even if a step fails, while still marking the overall job as failed?
name: CI
on:
pull_request:
branches:
- master
push:
branches:
- master
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v1
- name: Test App
run: ./gradlew test
- name: Archive Rest Results
uses: actions/upload-artifact#v1
with:
name: test-results
path: app/build/reports/tests
You can add
if: always()
to your step to have it run even if a previous step fails
https://docs.github.com/en/actions/learn-github-actions/expressions#status-check-functions
so for a single step it would look like this:
steps:
- name: Build App
run: ./build.sh
- name: Archive Test Results
if: always()
uses: actions/upload-artifact#v1
with:
name: test-results
path: app/build
Or you can add it to a job:
jobs:
job1:
job2:
needs: job1
job3:
if: always()
needs: [job1, job2]
Additionally, as pointed out below, putting always() will cause the function to run even if the build is canceled.
If dont want the function to run when you manually cancel a job, you can instead put:
if: success() || failure()
Other way, you can add continue-on-error: true.
Look like
- name: Job fail
continue-on-error: true
run |
exit 1
- name: Next job
run |
echo Hello
Read more in here.
run a github-actions step, even if the previous step fails
If you only need to execute the step if it succeeds or fails, then:
steps:
- name: Build App
run: ./build.sh
- name: Archive Test Results
if: success() || failure()
uses: actions/upload-artifact#v1
with:
name: test-results
path: app/build
Why use success() || failure() instead of always()?
Reading the Status check functions documentation on Github:
always
Causes the step to always execute, and returns true, even when canceled. A job or step will not run when a critical failure prevents the task from running. For example, if getting sources failed.
Which means the job will run even when it gets cancelled, if that's what you want, then go ahead. Otherwise, success() || failure() would be more suitable.
Note -
The documentation made clear thanks to Vladimir Panteleev in which he submitted the following PR: Github Docs PR #8411
Addon: if you have following sitution. 2 steps i.e. build > deploy and in some cases i.e. workflow_dispatch with input parameters you might want to skip build and proceed with deploy. At the same time you might want deploy to be skipped, when build failed.
Logically that would be something like skipped or not failed as deploy conditional.
if: always() will not work, cause it will always trigger deploy, even if build failed.
Solution is pretty simple:
if: ${{ !failure() }}
Mind that you cannot skip brackets when negating in if:, cause it reports syntax error.
The other answers here are great and work, but you might want a little more granularity.
For instance, ./upload only if ./test ran, even if it failed.
However, if something else failed and prevented the tests from running, don't upload.
# ... Other steps
- run: ./test
id: test
- run: ./upload
if: success() || steps.test.conclusion == 'failure'
steps.*.conclusion will be success, failure, cancelled, or skipped.
success or failure indicate the step ran. cancelled or skipped means it didn't.
Note there is an important caveat that you must test at least one success() or failure() in if.
if: steps.test.conclusion == 'success' || steps.test.conclusion == 'failure' won't work as expected.
you can add || true to your command.
example:
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v1
- name: Test App
run: ./gradlew test || true
I'm relatively new to GitHub Actions and I have 2 jobs–one that runs my tests, and one that deploys my project onto a server.
Obviously I want the tests to run on every branch, but deploying should only happen when something gets pushed to master.
I'm struggling to find a way to run a job on a specific branch. I know it's possible to only run entire workflows on a specific branch, however that would mean I would have a "test" workflow and a "deploy" workflow.
This sounds like a solution, however they would run parallel. In an ideal world, the tests would run first, and only if they succeed, then the deploy job would start. This isn't the case when using 2 separate workflows.
How would I be able to achieve this? Is it possible to run jobs on a specific branch?
In a recent update you can now put if conditionals at job level. See the documentation here. https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif
I tested this workflow which runs the job test on every push, but only runs deploy on the master branch.
name: my workflow
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Execute tests
run: exit 0
deploy:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/master'
steps:
- name: Deploy app
run: exit 0
What follows is my original answer, and an alternative solution if you prefer to have separate workflows.
The first workflow runs for every branch except master. In this workflow you run tests only.
on:
push:
branches:
- '*'
- '!master'
The second workflow runs for just master and runs both your tests and deploys if the tests were successfully passed.
on:
push:
branches:
- master
Most answers provide a solution for one single branch. To restrict the job to run on any specific set of branches, you can do it using the if conditional with multiple disjunction (||) operators; but this is too verbose and doesn't respect the DRY principle.
The same can be archived with less repetition using the contains function.
Using contains:
contains('
refs/heads/dev
refs/heads/staging
refs/heads/production
', github.ref)
compared to using multiple ||:
github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/production' || …
Full example:
---
on: push
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v2
- name: Run tests
run: …
deployment:
name: Deployment
runs-on: ubuntu-latest
needs: [test]
if:
contains('
refs/heads/dev
refs/heads/staging
refs/heads/production
', github.ref)
steps:
- uses: actions/checkout#v2
- name: Deploy
run: …
Here is what I've done for steps that should only run on a specific branch.
- name: Publish Image
# Develop branch only
if: github.ref == 'refs/heads/develop'
run: |
... publish commands ...
2021 update
I know it's possible to only run entire workflows on a specific
branch, however that would mean I would have a "test" workflow and a
"deploy" workflow.
This sounds like a solution, however they would run parallel. In an
ideal world, the tests would run first, and only if they succeed, then
the deploy job would start. This isn't the case when using 2 separate
workflows.
You can now use the event workflow_run to achieve the part the tests would run first, and only if they succeed, then the deploy job would start (read on to see how):
Documentation page of workflow_run
https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#workflow_run
This event occurs when a workflow run is requested or completed, and
allows you to execute a workflow based on the finished result of
another workflow. A workflow run is triggered regardless of the result
of the previous workflow.
For example, if your pull_request workflow generates build artifacts,
you can create a new workflow that uses workflow_run to analyze the
results and add a comment to the original pull request.
Now, considering OP's initial issue:
I want the tests to run on every branch, but deploying should only happen when something gets pushed to master
This now could be solved like this:
The following setup is working, a few minutes ago I just implemented the same logic in one of my repository
Workflow <your_repo>/.github/workflows/tests.yml
name: My tests workflow
on:
push:
branches:
- master
pull_request: {}
jobs:
test:
# ... your implementation to run your tests
Workflow <your_repo>/.github/workflows/deploy.yml
name: My deploy workflow
on:
workflow_run:
workflows: My tests workflow # Reuse the name of your tests workflow
branches: master
types: completed
jobs:
deploy:
# `if` required because a workflow run is triggered regardless of
# the result of the previous workflow (see the documentation page)
if: ${{ github.event.workflow_run.conclusion == 'success' }}
# ... your implementation to deploy your project
Although this discussion is very old but recently I came across the same problem with a slight addition. if condition to check whether the branch is main works but what if someone pushes their branch and updates the workflow yml file to remove the if condition? The deploy job will get triggered without their branch even being reviewed or merged in main and could potentially break the production environment. This could be a concern in open source projects.
I couldn't find an answer for this anywhere so wanted to share my findings. I hope this is the right thread for it.
To ensure that there's no way a job is triggered except for in specific branches, environments can be used. A deploy job will most likely have some api keys to connect to the destination server that might be stored in secrets. Instead of storing them in repository secrets which are accessible globally within the repository, we should store them in respective environments.
The official docs for environments contain detailed explanation with sample scripts but sharing a simple example here. Let's say we want to run production deployment only when main is updated
From repository Settings, create a production environment
Choose Selected Branches in Deployment Branches dropdown and add main in the pattern
Add the api keys in production environment secrets
In the workflow yml we just need to add environment information environment: production as follows (using script from #peterevans's answer)
name: my workflow
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Execute tests
run: exit 0
deploy:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
environment: production
steps:
- name: Deploy app
run: exit 0
The environment information indicates where the secrets have to be read from. If the current branch name doesn't match the pattern provided in Selected Branches then the job will immediately fail with an error. Since we have a condition to only run this on main, normally that won't bother us because this job will be skipped on other branches anyways. But if someone, mistakenly or with ill intentions, modifies the yml file and removes the condition before pushing their branch, they'll get an error. So, our system remains secure at least from the threat here.
Hope this helps anyone wondering the same.
The following worked for me inside a job:
Where a PR target branch is one of the following:
if: contains(github.base_ref, 'staging')
|| contains(github.base_ref, 'production')
When the source branch of the pull request is one of them below:
if: contains(github.head_ref, 'feature')
|| contains(github.head_ref, 'release')
name: CI
on: push
jobs:
prod-check:
if: ${{ github.ref == 'refs/heads/main' }}
runs-on: ubuntu-latest
steps:
- run: echo "Deploying to production server on branch $GITHUB_REF"
According to the documentation, the if condition is wrapped like so
if: ${{ github.ref == 'refs/heads/main' }}
While you can't have conditions at job level at this moment, you can have conditions at step level - see Contexts and expression syntax for GitHub Actions.
For getting a branch name, current solution is to check GITHUB_REF environment variable - see Default environment variables and this question for details.
Putting it all together - if you decide to go with accepted answer in last link, your workflow might look like this:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Run tests
run: ./my-tests.sh
deploy:
runs-on: ubuntu-latest
needs: test
steps:
- name: Extract branch name
shell: bash
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF##*/})"
id: extract_branch
- name: Deploy
run: ./deploy.sh
if: steps.extract_branch.outputs.branch == 'master'
If you'd rather keep everything in workflow file instead of separated script, you can always add if to each step in given job.
I hope it's just a temporary solution/workaround, and job conditions are going to be added before end of beta.
For steps or jobs you can also use github.ref_name which is the branch or tag name that triggered the workflow run.
name: my workflow
on: push
jobs:
if: github.ref_name == 'main'
test:
runs-on: ubuntu-latest
steps:
- name: Execute tests
run: exit 0
For more information about the github context check here.
You need to add these two script to the workflows
//main.yml
name: main
on:
push:
branches:
- main
//size.yml
name: size
on:
pull_request:
branches:
- main
There are already some pretty good answers. Here's a way to specify multiple branches in your conditional:
name: my workflow
on: push
jobs:
deploy:
if: contains(fromJson('["refs/heads/master", "refs/heads/main"]'), github.ref)
runs-on: ubuntu-latest
steps:
- name: Execute tests
run: exit 0
2022
Below workflow runs only for push in main branch:
name: Distribute to Firebase
on:
push:
branches: [ main ]
jobs:
...