Flow Variables

Structure of variable definitions, flow file stages, and nested actions that call on variables.

In Liquibase 4.17.0+, you can define variables as key-value pairs to use in one or more stages of your flow files. When you run the flow command, Liquibase substitutes the variable names for their values.

You can define variables in three ways:

  • As global variables in the flow file's header section
  • As stage variables within individual stages
  • Included in an external YAML file that you reference in the flow file's header section

In Liquibase 4.24.0+, flow files inherit variables from other flow files that you call on. This lets you pass variables through multiple nested layers of flow files.

Stage variables have the highest precedence, followed by included variables, then global variables, and then variables inherited from another flow file.

Uses

You can define a variable once but reuse it many times in different situations in your flow file. This provides flexibility and reduces code duplication. For example, variables may be useful to efficiently run multiple kinds of commands that contain the same file, directory, or other string.

It is a best practice to use global variables for things that can apply to your whole flow file, and stage variables for things that only apply to specific stages. Included variables are best for things that apply to multiple flow files.

You can also check the values of variables by using Flow Conditionals to dynamically run code in your flow files.

In Liquibase 4.26.0+, you can specify a default variable value for Liquibase to use if it doesn't receive an explicit one. For example, you could use this to specify optional command arguments (such as contexts and labels) as NULL if not explicitly specified in your definition.

Syntax

Define variables

Variable definitions always use the syntax key: "value". You must specify quotation marks around the value.

You can define included variables in an external YAML file, in this case liquibase.flowvariables.yaml:

PROJECT_NAME: "MyFlowProject"
THIS_DATE: "2024-05-12T15-00-20"

You can define global variables at the top of your flow file, in this case liquibase.myflowfile.yaml:

globalVariables:
  STATUS_FILE: "status.txt"
  UPDATE_LOG: "update.log"
  HISTORY_FILE: "history.txt"

You can define stage variables within a particular stage, in this case stage-status:

stages:
  stage-status:
    stageVariables:
      STATUS_FILE: "status.txt"

Use variables

Variable calls in your flow files use the following syntax:

  • Included variables: ${NAMESPACE.VARIABLE_KEY}
  • Global and stage variables: ${VARIABLE_KEY}

For example, we can run the status command in a flow file using included, global, and stage variables. In the following example, the included variables all have the namespace FLOW_VARS and reference variables we previously defined in the file liquibase.flowvariables.yaml:

include:
  FLOW_VARS: liquibase.flowvariables.yaml

globalVariables:
  DIRECTORY_NAME: "./${FLOW_VARS.PROJECT_NAME}_${FLOW_VARS.THIS_DATE}"
  STATUS_FILE: "status.txt"

stages:
  stage-status:

    stageVariables:
      VERBOSE_STATE: "true"

    actions:
      - type: liquibase
        command: status
        globalArgs: {output-file: "${DIRECTORY_NAME}/${STATUS_FILE}"}
        cmdArgs: {verbose: "${VERBOSE_STATE}"}

Default values

In Liquibase 4.26.0 and later, you can give variables default values using the following syntax:

Example: VARIABLE_KEY: "${VARIABLE_KEY:-defaultValue}"

This is useful if you want to dynamically specify optional arguments for commands in your flow file. For example:

globalVariables:
  VERBOSE_STATE: "${VERBOSE_STATE:-null}"
  # Set the default value to null

stages:
  stage-status:

    stageVariables:
      VERBOSE_STATE: "true"
      # Set a value for this stage only

    actions:
      - type: liquibase
        command: status
        cmdArgs: {verbose: "${VERBOSE_STATE}"}
        # This stage passes a non-null value to verboseState

  other-stage:
    actions:
      - type: liquibase
        command: status
        cmdArgs: {verbose: "${VERBOSE_STATE}"}
        # This stage doesn't define any stage variables
        # Liquibase uses the definition from the global variable, which defaults to null

In the preceding example, VERBOSE_STATE is set to null by default as a global variable and is set to a non-null value as a stage variable. During stage-status, Liquibase passes the non-null value. During stage-other, VERBOSE_STATE is not defined as a stage variable, so Liquibase passes the global value, which is null. When Liquibase detects a null value in a global or command argument, it omits the entire argument at runtime.

Note: It's possible to define a variable without default values both globally and in a stage. In this design, you simply rely on the stage variable's higher precedence to control the value of the output in different situations. However, explicitly specifying default values can make the intended behavior of a flow file clearer to developers.

Default values can also be useful when you use them with Flow Conditionals. For example:

globalVariables:
  UPDATE: "${UPDATE:-null}"
  CHANGELOG_FILE: "${CHANGELOG_FILE:-simple-changelog.xml}"

stages:
  someStage:
    actions:
    - type: liquibase
      if: "UPDATE != null"
      command: update
      cmdArgs: {changelog-file: "${CHANGELOG_FILE}"}

In the preceding example, Liquibase checks whether the value of the global variable UPDATE is not null before executing the rest of the action.

Note: If you define a default value for a variable, the default is inherited by any nested flow files that call on the parent flow file. The default value also applies to the nested flow files.

Environment variables

You can substitute the values of environment variables in your flow file. The syntax is the same as substituting other variables. For example:

stages:
  systemEnvars:
    actions:
      - type: shell
        command: echo "Running Liquibase from ${LIQUIBASE_HOME}"

  commandStuff:
    actions:
      - type: liquibase
        command: update-sql
        globalArgs: {changelog-file: "${LIQUIBASE_OUTPUT_FILE}"}

This lets you avoid manually updating information in your flow file, such as the path of the system environment variable LIQUIBASE_HOME, which could change if you later modify your installation directory.

Substituting environment variables may also be useful if you want to avoid modifying your flow file whenever you change the value of Liquibase parameters like LIQUIBASE_OUTPUT_FILE. Instead of defining and updating variables in your flow file, you can simply update the values of the external environment variables the flow file calls on. Then, when you run your flow file, Liquibase uses the new values without you ever having modified the flow file itself.

Command substitutions

In Liquibase 4.28.0 and later, you can also use variables in the command field of a flow file action. For example:

globalVariables:
  MY_COMMAND: "status"
  MY_CHANGELOG: "newChangelog.txt"

stages:
  myStage:
    actions:
      - type: liquibase
        command: ${MY_COMMAND}
        cmdArgs: {changelog-file: "${MY_CHANGELOG}"}

The preceding example calls on a global variable from the command field. You can use the same syntax to call on environment variables instead.

Empty strings

You can specify the empty string as the value of a variable. For example, if you want to run all changesets in your changelog regardless of their contexts/labels:

stages:
  default:

    stageVariables:
      LABEL_NAMES: ""

    actions:
      - type: liquibase
        command: update
        cmdArgs: {label-filter: "${LABEL_NAMES}"}

Property substitution with full YAML strings

In Liquibase 4.28.0 and later, you can also use variables to dynamically expand into full YAML strings during property substitution. The YAML strings you insert can also contain other variables, which can expand into more YAML strings.

For example, this may be useful if you want multiple stages of a single flow file to run a command, but you want to substitute different variables to modify that command in each stage of the flow file.

globalVariables:
  COMMAND_ARGS: "changelog-file: './liquibase.${STAGE_CHANGELOG}.yaml'"

stages:
  stage01:

    # Set up vars for property substitution in THIS stage only
    stageVariables:
      STAGE_CHANGELOG: "stage01"

    actions:
      # Run the update command
      - type: liquibase
        command: update
        cmdArgs: {"${COMMAND_ARGS}"}
        # This expands to: cmdArgs: { changelog-file: './liquibase.${STAGE_CHANGELOG}.yaml'}
        # which expands to: cmdArgs: { changelog-file: './liquibase.stage01.yaml'}

Example Flow File

Defines variables that you can use in other flow files.

# List of variables
PROJNAME: "MyFlowProj"
THISDATE: "2022-11-28T15-00-20"

Includes command arguments, global arguments, and a referenced include file (YAML).

# Bring in and namespace an external file with yaml 'key: val' pairs for use in this file
include:
  FLOWVARS: liquibase.flowvariables.yaml

# Set up some global variables for property substitution in ANY stage
globalVariables:
  DIRNAME: "./${FLOWVARS.PROJNAME}_${FLOWVARS.THISDATE}"
  STATUSFILE: "status.txt"
  UPDATELOG: "update.log"
  HISTORYFILE: "history.txt"

# Start of the stages.
stages:

  # A prep stage. There can be more than one stage if desired.
  stage-prep:

    actions:
      - type: shell
        command: bash -c "mkdir -p ${DIRNAME}"

  # Another stage.
  stage-dowork:

    # set up vars for property substitution in THIS stage only
    stageVariables:

      VERBOSESTATE: TRUE

    actions:
      # Do a validate command
      - type: liquibase
        command: validate

      # Tell me what is pending a deployment
      - type: liquibase
        command: status
        globalArgs:
          outputfile: "${DIRNAME}/${STATUSFILE}"
          showbanner: false
        cmdArgs: {verbose: "${VERBOSESTATE}"}

      # Quality Checks for changelog
      - type: liquibase
        command: checks run
        cmdArgs: {checks-scope: changelog}

      # Run update
      - type: liquibase
        command: update

      # Quality Checks for database
      - type: liquibase
        command: checks run
        cmdArgs: {checks-scope: database}

      # Create a history file
      - type: liquibase
        command: history
        globalArgs: {outputfile: "${DIRNAME}/${HISTORYFILE}"}

Related links