GitHub Actions step executed while it shouldn't, based on a conditional - bash

I have the following steps' sequence in a GitHub Actions job (first one is used more or less for debugging purposes
env:
FAIL_OUTCOME: 'fail'
- name: debug
shell: bash
run: |
echo "evaluation-1 result is ${{ steps.evaluation_1.outputs.evaluation-1-outcome }}
echo "evaluation-2 result is ${{ steps.evaluation_2.outputs.evaluation-2-outcome }}
echo $FAIL_OUTCOME
- name: send slack failure
if: ${{ steps.evaluation_1.outputs.evaluation-1-outcome }} == $FAIL_OUTCOME || ${{ steps.evaluation_2.outputs.evaluation-2-outcome }} == $FAIL_OUTCOME
uses: rtCamp/action-slack-notify#v2
env:
...
- name: send slack success
if: ${{ steps.evaluation_1.outputs.evaluation-1-outcome }} != $FAIL_OUTCOME && ${{ steps.evaluation_2.outputs.evaluation-2-outcome}} != $FAIL_OUTCOME
uses: rtCamp/action-slack-notify#v2
env:
...
Here is the outcome of the debug action:
echo "evaluation-1 result is
echo "evaluation-2 result is fail
where it seems that first outcome is not set.
However, what puzzles me is that success action is also executed, i.e.
${{ steps.evaluation_1.outputs.evaluation-1-outcome }} != $FAIL_OUTCOME && ${{ steps.evaluation_2.outputs.evaluation-2-outcome}} != $FAIL_OUTCOME
becomes true. How is it possible?
To provide for more context, the outputs' assignment in previous steps are as follows:
echo "::set-output name=evaluation-2-outcome::$FAIL_OUTCOME"

The straight answer to your question is that you are misusing the $FAIL_OUTCOME value in if statements.
There you need to use:
if: steps.evaluation_1.outputs.evaluation-1-outcome == env.FAIL_OUTCOME || steps.evaluation_2.outputs.evaluation-2-outcome == env.FAIL_OUTCOME
...
if: steps.evaluation_1.outputs.evaluation-1-outcome != env.FAIL_OUTCOME && steps.evaluation_2.outputs.evaluation-2-outcome != env.FAIL_OUTCOME
or:
if: ${{ steps.evaluation_1.outputs.evaluation-1-outcome == env.FAIL_OUTCOME || steps.evaluation_2.outputs.evaluation-2-outcome == env.FAIL_OUTCOME }}
...
if: ${{ steps.evaluation_1.outputs.evaluation-1-outcome != env.FAIL_OUTCOME && steps.evaluation_2.outputs.evaluation-2-outcome != env.FAIL_OUTCOME }}
Env variables can be accessed as $FAIL_OUTCOME only in bash scopes - everywhere else, you need to explicitly use env. prefix.
However, I would recommend doing it properly and not fighting with env variables combined with set-output madness - which has a lot of gotchas and it's hard to debug and maintain.
Slack success and failures can be easily handled by checking the output of the whole job:
- name: send slack failure
if: failure()
- name: send slack success
if: success()
If you want to communicate about failure of some steps only:
notify_failure:
if: always() && !cancelled() && needs.check_failure_step.result != 'success'
needs: check_failure_step
And instead of using echo set-output:: just exit 1 to fail a certain job.
Combining that with needs and jobs output values and if: always() you can achieve anything you want.
It gives you also a huge advantage of seeing which job has failed straight on workflow run summary without looking to its "output" or "debug" logs.

Related

Github actions failing to recognise outputs of another job in an if/needs condition

I have set up a workflow with multiple jobs. Job A is a requirement for Job B to run.
The workflow is triggered on PR, and Job A checks for the existence of a comment on the PR:
job-a:
outputs:
comment: ${{ steps.find-comment.outputs.comment }}
steps:
- name: Check if QR already exists
uses: peter-evans/find-comment#v2
id: find-comment
with:
issue-number: ${{ github.event.number }}
comment-author: "github-actions[bot]"
body-includes: Preview Bundle
- name: Store find-comment output
run: echo "comment=${{ steps.find-comment.outputs.comment-id }}" >> $GITHUB_OUTPUT
- name: Check find-comment outcome
run: echo "This is the output of find-comment ${{ steps.find-comment.outputs.comment-id }}" # Successfully logs out comment id
I set the output of the check into comment in outputs for that job.
In Job B, I then need to check if that output is an ID or an empty string:
job-b:
needs: [job-a]
if: always() && needs.job-a.outputs.comment == ''
steps:
- name: Check for find-comment
run: echo "This is the input of find-comment ${{ needs.job-a.outputs.comment }}" # always logs out an empty string
The if check always equates to true, i.e. the outputs.comment is always an empty string. I've tried multiple variations of the if check, with and without the always(), with and without the [], but needs.job-a.outputs.comment always is an empty string even though it successfully logs out the comment id in the check in job-a. This job then always runs, which is not what I want. I only want the job to run when the PR comment doesn't exist.
Can anyone tell me what I'm doing wrong here?
You need to define an identifier for the step in order to be able to refer it as an output for the job. See the relevant part of the doc:
Sets a step's output parameter. Note that the step will need an id to be defined to later retrieve the output value.
So you should change your code like:
job-a:
outputs:
comment: ${{ steps.store-comment.outputs.comment }}
steps:
- name: Check if QR already exists
uses: peter-evans/find-comment#v2
id: find-comment
with:
issue-number: ${{ github.event.number }}
comment-author: "github-actions[bot]"
body-includes: Preview Bundle
- name: Store find-comment output
id: store-comment
run: echo "comment=${{ steps.find-comment.outputs.comment-id }}" >> $GITHUB_OUTPUT
The answer was to change the outputs variable to take comment-id instead of just comment. I thought the chained property had to match what you put int eh echo command, but apparently it has to match what is in the outputs of peter-evans/find-comment#v2: https://github.com/peter-evans/find-comment#outputs
job-a:
outputs:
comment: ${{ steps.store-comment.outputs.comment-id }}

Check if multiple secrets exist in Github Actions

I want to check that all necessary secrets exist and fail the build if some of them are missing.
In my script I have this step
- name: Check if secrets exist
env:
secret_key1: ${{ secrets.MY_SECRET_1 }}
secret_key2: ${{ secrets.MY_SECRET_2 }}
secret_key3: ${{ secrets.MY_SECRET_3 }}
if: ${{ env.secret_key1 == '' }} || ${{ env.secret_key2 == '' }} || ${{ env.secret_key3 == '' }}
run: exit 1
but this always exists with status code 1, even if all secrets are present.
I have checked that if I use only one secret it works correctly, e.g.
- name: Check if secret exists
env:
secret_key: ${{ secrets.MY_SECRET }}
if: ${{ env.secret_key == '' }}
run: exit 1
Am I using wrong syntax or is the problem somewhere else?
Your condition should look like this:
- name: Check if secrets exist
env:
secret_key1: ${{ secrets.MY_SECRET_1 }}
secret_key2: ${{ secrets.MY_SECRET_2 }}
secret_key3: ${{ secrets.MY_SECRET_3 }}
if: ${{ (env.secret_key1 == '') || (env.secret_key2 == '') || (env.secret_key3 == '') }}
run: exit 1
Also, you can omit the expression syntax (${{ }}) because GitHub automatically evaluates the if conditional as an expression:
- name: Check if secrets exist
env:
secret_key1: ${{ secrets.MY_SECRET_1 }}
secret_key2: ${{ secrets.MY_SECRET_2 }}
secret_key3: ${{ secrets.MY_SECRET_3 }}
if: env.secret_key1 == '' || env.secret_key2 == '' || env.secret_key3 == ''
run: exit 1
Screenshot: click
For more information, see Expressions.

Unable to read Github Actions job's outputs from another job

I have the following workflow in Github Actions, where I have a job that create some outputs and a dependant job that read those outputs, pretty similar to the example from the docs:
name: Sandbox
on:
push:
env:
POSTGRESQL_VERSION: "14.4.0-debian-11-r13"
jobs:
setup:
runs-on: ubuntu-latest
outputs:
prod_tag: "steps.prod_tag.outputs.prod_tag"
postgresql_version: "steps.postgresql_version.outputs.postgresql_version"
steps:
- uses: actions/checkout#v3
- id: prod_tag
run: |
if [[ ${{ github.ref_type }} == "tag" ]]; then
echo "::set-output name=prod_tag::${{github.ref_name}}"
else
echo "::set-output name=prod_tag::latest"
fi;
- id: postgresql_version
run: echo "::set-output name=postgresql_version::${POSTGRESQL_VERSION}"
- name: Show output variables
run: |
echo "PROD TAG: ${{steps.prod_tag.outputs.prod_tag}}"
echo "POSTGRESQL_VERSION: ${{steps.postgresql_version.outputs.postgresql_version}}"
show_outputs:
runs-on: ubuntu-latest
needs: setup
steps:
- run: |
echo "PROD TAG: ${{needs.setup.outputs.prod_tag}}"
echo "POSTGRESQL_VERSION: ${{needs.setup.outputs.postgresql_version}}"
However, in my example, it doesn't work as expected and show_outputs shows PROD TAG: steps.prod_tag.outputs.prod_tag and POSTGRESQL_VERSION: steps.postgresql_version.outputs.postgresql_versioninstead of the values set in the setup job, that should be latest and 14.4.0-debian-11-r13. In the step Show output variables of the setup job I can see that the values are properly set, and I've tried several different approaches (setting the variables from the same step, not taking the value from the environment variable, etc) but with no success.
Any idea what can be wrong with my example?
You should surround the variables with ${{ and }}
try with:
outputs:
prod_tag: ${{ steps.prod_tag.outputs.prod_tag }}
postgresql_version: ${{ steps.postgresql_version.outputs.postgresql_version }}
instead of:
outputs:
prod_tag: "steps.prod_tag.outputs.prod_tag"
postgresql_version: "steps.postgresql_version.outputs.postgresql_version"

GitHub Actions Job Based on Multiple Conditions from Inputs

I've been trying to create conditions for Jobs in GitHub Actions but I can't seem to get it working
I have the following Inputs:
on:
workflow_dispatch:
inputs:
env:
description: 'Select the Environment'
type: choice
required: true
options:
- SIT
- UAT
op:
description: 'Deploy or Delete Apps'
type: choice
required: true
options:
- Deploy
- Delete
ver:
description: 'Type the app version'
required: true
and the below jobs:
jobs:
create-sit-app:
runs-on: ubuntu-latest
name: 'Deploy App for SIT'
if: |
(${{ github.event.inputs.env }} == 'SIT' && ${{ github.event.inputs.op }} == 'Deploy')
steps:
........
........
........
I also tried this
(${{ github.event.inputs.env == 'SIT' }} && ${{ github.event.inputs.op == 'Deploy' }})
And this
${{ github.event.inputs.env == 'SIT' }} && ${{ github.event.inputs.op == 'Deploy' }}
Managed to do it like this:
if: (github.event.inputs.env == 'SIT' && github.event.inputs.op == 'Deploy')

Sequencing GitHub actions job on conditions

I have: Job A, Job B & Job C.
when Job A completes
If job B runs I need job C to run (after job B has completed with success)
if Job B skipped I need Job C to run (If job A has completed with success)
See below for code snip:
check_if_containers_exist_to_pass_to_last_known_tagger_job: (JobA)
name: check_if_containers_exist
environment: test
runs-on: ubuntu-latest
#needs: [push_web_to_ecr, push_cron_###_to_ecr, push_to_###_shared_ecr, push_to_###_ecr]
needs: push_###_to_shared_ecr
#if: ${{ github.ref == 'refs/heads/main' }}
outputs:
signal_job: ${{ steps.step_id.outputs.run_container_tagger_job }}
steps:
- name: Configure AWS credentials
id: config-aws-creds
uses: aws-actions/configure-aws-credentials#v1
with:
aws-access-key-id: ${{ secrets.SHARED_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.SHARED_AWS_SECRET_ACCESS_KEY }}
aws-region: eu-west-2
- name: Check if container exists (If containers don't exist then don't run last known tagging job for rollback)
id: step_id
run: |
aws ecr describe-images --repository-name anonymizer --image-ids imageTag=testing-latest
if [ $? == 254 ]; then echo "::set-output name=run_container_tagger_job::false"; else echo "::set-output name=run_container_tagger_job::true"; fi
tag_latest_testing_containers_as_last_known_testing_containers: (Job B)
needs: check_if_containers_exist_to_pass_to_last_known_tagger_job
if: needs.check_if_containers_exist_to_pass_to_last_known_tagger_job.outputs.signal_job == 'true'
uses: ###/###/.github/workflows/container-tagger.yml####
with:
tag_to_identify_containers: testing-latest
new_tag_to_apply_to_containers: last-known-testing
aws-region: eu-west-2
run_cron_and_cycle_containers: false
secrets:
SHARED_AWS_ACCESS_KEY_ID: ${{ secrets.SHARED_AWS_ACCESS_KEY_ID }}
SHARED_AWS_SECRET_ACCESS_KEY: ${{ secrets.SHARED_AWS_SECRET_ACCESS_KEY }}
tag_testing_containers_to_testing_latest: (Job C)
needs: [check_if_containers_exist_to_pass_to_last_known_tagger_job,tag_latest_testing_containers_as_last_known_testing_containers]
if: ${{ always() }}
uses: ###/##/.github/workflows/container-tagger.yml####
with:
tag_to_identify_containers: dev-${{ github.sha }}
new_tag_to_apply_to_containers: james-cron-test
aws-region: eu-west-2
run_cron_and_cycle_containers: true
secrets:
SHARED_AWS_ACCESS_KEY_ID: ${{ secrets.SHARED_AWS_ACCESS_KEY_ID }}
SHARED_AWS_SECRET_ACCESS_KEY: ${{ secrets.SHARED_AWS_SECRET_ACCESS_KEY }}
ENVIRONMENT_AWS_ACCESS_KEY_ID: ${{ secrets.TESTING_AWS_ACCESS_KEY_ID }}
ENVIRONMENT_AWS_SECRET_ACCESS_KEY: ${{ secrets.TESTING_AWS_SECRET_ACCESS_KEY }}
It might not be the most elegant solution, but it works.
The workaround would consist of adding 2 extra steps at the end of the Job A, and set the 2 steps always execute (if: always()).
The first one is used to create a text file and write the job status into it.
The second one is used to upload this text file as an artifact.
Then, in Job B and Job C, you will need to add the steps to download the artifacts and read the status of Job A to then perform or not specific operations.
Here is a demo of how it might look:
jobs:
JOB_A:
name: Job A
...
steps:
- name: Some steps of job A
...
- name: Create file status_jobA.txt and write the job status into it
if: always()
run: |
echo ${{ job.status }} > status_jobA.txt
- name: Upload file status_jobA.txt as an artifact
if: always()
uses: actions/upload-artifact#v1
with:
name: pass_status_jobA
path: status_jobA.txt
JOB_B:
needs: [JOB_A]
if: always()
name: Job B
...
steps:
- name: Download artifact pass_status_jobA
uses: actions/download-artifact#v1
with:
name: pass_status_jobA
- name: Set the status of Job A as output parameter
id: set_outputs
run: echo "::set-output name=status_jobA::$(<pass_status_jobA/status_jobA.txt)"
- name: Check Job A status
if: ${{ steps.set_outputs.outputs.status_jobA }} == "success"
run: |
...
JOB_C:
needs: [JOB_A]
if: always()
name: Job C
...
steps:
- name: Download artifact pass_status_jobA
uses: actions/download-artifact#v1
with:
name: pass_status_jobA
- name: Set the status of Job A as output parameter
id: set_outputs
run: echo "::set-output name=status_jobA::$(<pass_status_jobA/status_jobA.txt)"
- name: Check Job A status
if: ${{ steps.set_outputs.outputs.status_jobA }} == "failure"
run: |
...
Note that here, all jobs will always run, but Job B steps after the check will only run for Job A success, and Job C steps after the check will only run for Job A failure.
Job A --> Success --> Job B + Job C checks --> Job B steps
Job A --> Failure --> Job B + Job C checks --> Job C steps
Reference

Resources