GitHub Actions And The Case of the Missing Environment: Part 3— The Dummy Job Solution
In this part of the journey, we implement the dummy job technique to overcome GitHub Actions’ restrictions on directly using vars. variables in reusable workflows. This solution introduces a preparatory job to extract environment variables and pass them to the reusable workflow via outputs. Here’s the code for atat-manual-trigger-terraform-plan-with-dummy-job.yaml and a step-by-step walkthrough of the solution.
The Next Generation…
Just like we attempted previously, we have two workflows. One that acts as the entry point workflow and another that is the reusable workflow. The entry point workflow encapsulates the trigger, accesses the GitHub environment context and passes on the variables as parameters to the reusable workflow. The reusable workflow encapsulates the Terraform Core Workflow operation terraform plan . The entry point workflow begins with a workflow_dispatch trigger, which allows manual invocation with an environment input. Permissions are set to allow GitHub Actions to read repository contents and write ID tokens, necessary for authentication workflows such as Azure login.
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to run against'
type: environment
required: true
permissions:
id-token: write
contents: read
This part is identical to the previous iterations. The key input is environment, dynamically provided when triggering the workflow, which will later propagate to the reusable workflow. I’m not sure if I actually need the permissions block here. I also include it in the reusable workflow because the reusable workflow actually has the step that performs the authentication and saves the OIDC token to the special ACTIONS_ID_TOKEN_REQUEST_TOKEN variable.
Adding the Dummy Job
The first job in the entry point workflow is the dummy job, aptly named prepare-environment. Its sole responsibility is to extract the vars. variables from the GitHub Environment and convert them into outputs using the GITHUB_OUTPUT file. These outputs are then used by subsequent jobs.
prepare-environment:
runs-on: ubuntu-latest
environment:
name: $
outputs:
application-name: $
environment-name: $
client-id: $
tenant-id: $
subscription-id: $
terraform-version: $
terraform-directory: $
backend-resource-group: $
backend-storage-account: $
backend-storage-container: $
steps:
- name: Set Outputs from Environment Variables
id: set-outputs
run: |
echo "application-name=$" >> $GITHUB_OUTPUT
echo "environment-name=$" >> $GITHUB_OUTPUT
echo "client-id=$" >> $GITHUB_OUTPUT
echo "tenant-id=$" >> $GITHUB_OUTPUT
echo "subscription-id=$" >> $GITHUB_OUTPUT
echo "terraform-version=$" >> $GITHUB_OUTPUT
echo "terraform-directory=$" >> $GITHUB_OUTPUT
echo "backend-resource-group=$" >> $GITHUB_OUTPUT
echo "backend-storage-account=$" >> $GITHUB_OUTPUT
echo "backend-storage-container=$" >> $GITHUB_OUTPUT
How It Works:
- The environment block binds this job to the GitHub Environment, making vars. variables accessible.
- The outputs section defines outputs for the job. These outputs correspond to the environment variables needed by the reusable workflow.
- The run script writes key-value pairs to the GITHUB_OUTPUT file, exposing them as outputs.
Section 3: Passing Outputs to the Reusable Workflow
The second job, terraform-plan, depends on the prepare-environment job (needs: prepare-environment) and uses its outputs as inputs for the reusable workflow.
terraform-plan:
needs: prepare-environment
uses: ./.github/workflows/atat-terraform-core-workflow-plan-with-params.yaml
with:
application-name: $
environment-name: $
client-id: $
tenant-id: $
subscription-id: $
terraform-version: $
terraform-directory: $
backend-resource-group: $
backend-storage-account: $
backend-storage-container: $
How It Works:
- The needs directive ensures that terraform-plan runs only after prepare-environment completes.
- The with block passes the outputs from the dummy job into the reusable workflow using needs.prepare-environment.outputs.
syntax.
The reusable workflow receives these inputs as if they were directly passed from the entry point, allowing it to operate seamlessly with the necessary environment context.
Conclusion: A Functional but Inelegant Solution
This dummy job approach successfully circumvents GitHub Actions’ limitations. By leveraging prepare-environmentto extract and pass environment variables, the reusable workflow operates with all the required inputs. However, the solution feels like overkill for what should be a straightforward task. The prepare-environment job adds unnecessary complexity, serving as little more than a translator for GitHub Environment variables. While functional, it underscores the need for GitHub Actions to better integrate its environment features with reusable workflows. Until then, this workaround remains a practical—if frustrating—solution. I’m not happy with it, but it works. The big question is: Is this even better than not using Reusable Workflows anyway? I mean, I am able to encapsulate the Terraform Core workflow operation into a reusable workflow but I have to add almost as much complexity as I am removing by introducing more complexity in the form of this dummy job!
Nuts.
What do you think is better?
Dummy Job with a DRY Terraform Core Workflow Reusable GitHub Action. OR
Hard Coded Terraform Core Workflow GitHub Action.