Introduction
In a complex CI/CD workflow, various data would often have to be exchanged across jobs. Anything from generated environment variables down to important build artifacts would be fair game. GitHub Actions supports this via what are called job outputs, which can make your workflows much more modular and flexible. Be it API credentials, a database ID, something dynamically generated, job outputs provide a structured way to carry information forward between dependent jobs.
Definition of Job Outputs
Outputs of a job in GitHub Actions are defined inside of the job using the outputs
field. These will map to the results of steps in the same job and can be referenced from other jobs utilizing the dependsOn keyword. Outputs will always be strings, and will be evaluated at the closing of the job execution. Outputs are particularly useful for when you want to be able to pass dynamic information such as tokens, IDs, or version numbers across your workflow.
jobs:
job1:
runs-on: ubuntu-latest
outputs:
output1: ${{ steps.step1.outputs.result }}
steps:
- id: step1
run: echo "result=hello" >> "$GITHUB_OUTPUT"
job2:
runs-on: ubuntu-latest
needs: job1
steps:
- run: echo "Job 1 output: ${{ needs.job1.outputs.output1 }}"
In the example above, job1
defines an output named output1
that is set to the result of the first step. Then in job2 you can use the value of that output using the needs context in order to pass the value between jobs seamlessly.
Using Outputs in Matrix Jobs
Things get somewhat more interesting if you are employing a matrix approach. Matrix jobs create one output for each instance of the job. Best to make sure, though, that the names for these outputs are unique, especially if you are using a combination of multiple versions or configurations since the order of execution for matrix jobs is not guaranteed.
jobs:
job1:
runs-on: ubuntu-latest
strategy:
matrix:
version: [1, 2, 3]
outputs:
output_${{ matrix.version }}: ${{ steps.generate_output.outputs.version }}
steps:
- id: generate_output
run: echo "version=${{ matrix.version }}" >> "$GITHUB_OUTPUT"
job2:
runs-on: ubuntu-latest
needs: job1
steps:
- run: echo "Matrix outputs: ${{ toJSON(needs.job1.outputs) }}"
Here we define a three-version matrix. Each of the matrix jobs outputs a job displaying its version number. job2
can then utilize all matrix outputs en bloc due to the needs
context.
Monitoring GitHub Actions Workflows
CICube is a GitHub Actions monitoring tool that provides you with detailed insights into your workflows to further optimize your CI/CD pipeline. With CICube, you will be able to track your workflow runs, understand where the bottlenecks are, and tease out the best from your build times. Go to cicube.io now and create a free account to better optimize your GitHub Actions workflows!
Passing Sensitive Data Between Jobs
Sometimes, you want to pass some secret information between jobs, be it API keys, passwords, or tokens. GH Actions creates functionality for handling secrets, such as their redaction in logs, out of the box. You can also explicitly mask sensitive information that you pass as job outputs using the add-mask
command.
jobs:
job1:
runs-on: ubuntu-latest
outputs:
api_key: ${{ steps.generate_key.outputs.key }}
steps:
- id: generate_key
run: |
key="my-sensitive-api-key"
echo "::add-mask::$key"
echo "key=$key" >> "$GITHUB_OUTPUT"
job2:
runs-on: ubuntu-latest
needs: job1
steps:
- run: echo "Using API key: ${{ needs.job1.outputs.api_key }}"
Here, in the above example, API key created in job1
is masked before passing to job2
so that it won't show up in the logs.
Passing Database Credentials -Real-World Use Case
Consider an applied view-a real-world example-only integrating a database in a CI pipeline. You would create, in a job, the database and then pass the credential or the connection string to another job for the testing purpose.
jobs:
create-db:
runs-on: ubuntu-latest
outputs:
db_id: ${{ steps.db_setup.outputs.db_id }}
db_password: ${{ steps.db_setup.outputs.db_password }}
steps:
- id: db_setup
run: |
db_id=$(uuidgen)
db_password=$(openssl rand -base64 32)
echo "db_id=$db_id" >> "$GITHUB_OUTPUT"
echo "db_password=$db_password" >> "$GITHUB_OUTPUT"
test-db:
runs-on: ubuntu-latest
needs: create-db
steps:
- run: |
echo "Testing database with ID: ${{ needs.create-db.outputs.db_id }}"
echo "Using password: ${{ needs.create-db.outputs.db_password }}"
Here, create-db
outputs a database id and password. These are then given as arguments to test-db
, which can use them in conducting integration tests.
Conclusion
Passing information between jobs in GitHub Actions can really clean up and streamline your CI/CD workflows. Whether you are passing API keys, dynamic values, or build artifacts, job outputs give you flexible and secure ways to handle data across jobs. By following best practices, add-mask features, and matrix strategies, you'll be in a position to create workflows that are secure, maintainable, and efficient.