DEV Community

Cover image for Passing dynamic number of parameters to a reusable Github Actions workflow
Vasily Loginov
Vasily Loginov

Posted on

Passing dynamic number of parameters to a reusable Github Actions workflow

Source code

Github Actions and reusable workflows

Github Actions are one of the best tools for building and publishing your systems (especially when your company already uses Github, and especially when you don't even have a choice, welcome to enterprise development).

Github Actions have a concept of reusable workflows. Those allow you to expose / reuse parts of your actions to other parties, thus giving a possibility of creating a standardized flow.

Github itself provides several reusable workflows, and you've probably already used them many times. One of them is a checkout action which allows you to check out a repo.

Passing dynamic number of parameters

Reusable workflows allow defining a set of input and output params. The above checkout action can be a relatively comprehensive example (see here). And while in most of the cases it it is convenient to specify all the inputs as static variables, in some cases you don't know all the parameters in advance.

One of the examples is config templating. The templating action itself depends on the variables the config file contents, which are not known in advance. In this article I will go through one of the approaches to pass dynamic number of parameters using JSON and create a template-file action as an example.

Assumptions

  • Inputs are considered trusted. The approach below allows someone to introduce a list of env vars into your workflow. While in most of the cases it doesn't matter, because the context of the action is defined by the caller, please take a second to see what an attacker can do with the action's environment in your particular case.
  • Make sure that you need it. Please note it is always better to be explicit if you can, and specify all the parameters as inputs. If you are not sure - don't use this approach, and go with the standard static inputs instead.

General approach

To do that we will use the following approach

  • Introduce a JSON object with all the variables we need as one of the inputs;
  • Convert the variables from the object into the environment in the action using jq which is included in the actions by default;
  • Introduce them into the environment using export and eval statements;
  • Use envsubst to do the actual substitution.

Step 0: Passing multiline strings

We will be getting our dynamic params as a JSON object. To pass a JSON string to an action, you can use one of the YAML miltiline strings, for example here:

      TEMPLATE_VARS: |
        {
          "APP_ENVIRONMENT": "dev",
          "LOG_LEVEL": "DEBUG"
        }
Enter fullscreen mode Exit fullscreen mode

Step 1: Checking out the repository

In order to access the repo code in the action, we'd need to checkout it. We can use the above checkout action provided by Github:

- name: Checkout
    uses: actions/checkout@v4
Enter fullscreen mode Exit fullscreen mode

Step 2: Converting JSON variables into the environment variables

Here is where the (standard Linux command) magic happens!

We can define our action step as the following:

      - name: Template File
        env:
          # Expose as an env var to handle double quotes correctly in the JSON
          TEMPLATE_VARS: ${{ inputs.TEMPLATE_VARS }}
        run: |
          set -ex

          # Export vars from the input JSON
          PARSED_VARS=$(echo "${TEMPLATE_VARS}" | jq 'to_entries[] | "\(.key)=\(.value)"' | xargs -I '{}' echo "export {}")

          # Load the vars into the environment
          eval "${PARSED_VARS}"

          # Create envsubst template
          ENVSUBST_VAR_LIST=$(echo "${TEMPLATE_VARS}" | jq 'to_entries[] | "${\(.key)}"' | xargs)

          # Template the config file
          envsubst "${ENVSUBST_VAR_LIST}" < ${{ inputs.TEMPLATE_FILE_PATH }} > ${{ inputs.TARGET_FILE_PATH }}
Enter fullscreen mode Exit fullscreen mode

The following things are happening in the above:

  1. We define our input variable as an environment. This makes sure the quotes are not messed up in the output.
  2. Then we introduce a PARSED_VARS variable which contains a list of export $VARIABLE_1=value statements. We do that using jq utility to extract keys and values from the input JSON.
  3. We introduce the parsed variables into the environment using eval.
  4. We create a list of variables envsubst should substitute. This makes sure that standard variables like $PATH are not substituted, unless they are specified in the input JSON.
  5. We run the envsubst command to template our input file and store it in the target.

That's it! The output file should contain a result of substitution of the variables.

Top comments (0)