Apply Property substitution in Custom policy checks

In Liquibase Pro 4.31+, you can utilize Custom policy checks property substitution when setting the SCRIPT_ARGS during check creation. This enables dynamic and context-aware scripting, such as configuring behavior based on runtime attributes like the current Git branch or target environment. It makes your Custom policy checks more flexible, reusable, and maintainable.

Prerequisites

Using property substitution in your Custom policy check

You can substitute any command argument, system property, or environment variable within the Liquibase ecosystem.

SCRIPT_ARGS syntax used when creating a Custom Policy check:

  • liquibase.properties file example: 
    targeturl="${liquibase.command.url}"
  • User supplied property: 
    pipelinename="${PIPELINE_NAME}"
  • Environment variable example: 
    loglevel="${LIQUIBASE_LOG_LEVEL}"

Step-by-step

Create a checks settings file

  1. Create a checks settings file if you don't already have one. In the CLI, run this command:
  2. liquibase checks show
  3. If you don't have a checks settings file, a prompt appears that allows you to create one. Confirm the file creation in the prompt. By default, the settings file is named liquibase.checks-settings.conf.

Create a new custom policy check

  1. Create a new file in your Liquibase working directory or a subdirectory like /scripts. This file will contain the Python script that is your custom policy check. In this example, we title our new file custom_check_no_tables.py.
  2. Open the new custom_check_no_tables.py file and add the following custom policy check to it:
  3. # import Liquibase modules containing useful functions
    import liquibase_utilities as lb
    import sys
    
    # define reusable variables
    obj = lb.get_database_object()  # database object to examine
    liquibase_status = lb.get_status()  # Status object of the check

    if loglevel >= 2: //Notify the user when the loglevel value is triggered logger.info("LOG LEVEL = " + loglevel) # write check logic # this condition checks if the current object is a table # and whether it starts with the prefix 'tbl_' if lb.is_table(obj) and not obj.getName().lower().startswith("tbl_"): # indicate that the custom check has been triggered liquibase_status.fired = True # set the message for Liquibase to return when check is triggered liquibase_status.message = "Tables must start with the prefix tbl_. Add this prefix." # halt execution of the script sys.exit(1) # default return code False

    Note: Liquibase will run the check against every object in the database, so this script doesn't need a Python looping mechanism to iterate through database objects.

Configure and customize your new check in the CLI

  1. Initiate the customization process. In the CLI, run this command:
  2. liquibase checks customize --check-name=CustomCheckTemplate

    The CLI prompts you to finish configuring your file. A message displays:

    This check cannot be customized directly because one or more fields does not have a default value.

    Liquibase will then create a copy of CustomCheckTemplate and initiate the customization workflow.

  3. Give your new check a short name for easier identification. In this example, we will name the check CustomCheckNoTables.
  4. Tip: For ease of use, it is a best practice to name your custom check after your Python script.

  5. Set the severity to return a code of 0–4 when triggered. In this example, we will set the severity to 1.

    These severity codes allow you to determine if the job moves forward or stops when this check triggers.
    Learn more here: Use Policy Checks in Automation: Severity and Exit Code
    Options:
    • 'INFO' | 0
    • 'MINOR' | 1
    • 'MAJOR' | 2
    • 'CRITICAL' | 3
    • 'BLOCKER' | 4
  6. Set SCRIPT_DESCRIPTION. In this example, we will set the description to:
  7. This script looks to see if any tables exist and notifies you if one is detected.
  8. Set SCRIPT_SCOPE. In this example, we will set the scope to database. The Python sample provided in this tutorial requires it.
  9. In general, you should set the scope to changelog or database depending on what your custom script does:

    • changelog: for example, if your check looks for syntax patterns or attributes in your Liquibase Changelog (the changes you author in your repository). With this value, the check runs once per changeset.
    • database: for example, if your check looks for the presence of keys, indexes, or table name patterns in your database schema (including Liquibase Tracking Tables). With this value, the check runs once for each database object.

    Tip: It is a best practice for your custom checks to have only one scope, not both scopes.

  10. Set SCRIPT_MESSAGE. This message will display when the check is triggered. In this example we will leave this blank, as we are handling the message in the script.
  11. Set SCRIPT_PATH to the location of your script. You can provide either:
    • a relative path, which is based on the location of the changelog file you specify with --changelog-file. This means that the path is relative to where that changelog file is stored.
      Example: If your changelog is in a folder called project/, and your script is in project/scripts/, the relative path to the script would be scripts/your-script.py.
      OR
    • An absolute path, which starts from the root of the your computer's file system and includes the full path to the script.
      Example: The full path to the script would be: /Users/yourname/project/scripts/your-script.py
      In this example, we will set the path to: scripts/custom_check_no_tables.py.
  12. Essential step for Property substitution: Set the SCRIPT_ARGS. This allows you to pass dynamic information into the custom policy check without modifying the Python code. With property substitution, you can reference properties such as environment variables, CLI arguments, or values from the defaults file using standard property substitution syntax. loglevel is a variable in the Custom policy check and its value is set to the ${LIQUIBASE_LOG_LEVEL} property which can be changed dynamically in the Environment variables.
    In this example, we will enter loglevel="${LIQUIBASE_LOG_LEVEL}"
  13. Set REQUIRES_SNAPSHOT. If your script scope is changelog, set whether the check requires a database snapshot. Specify true if your check needs to inspect database objects. (If your script scope is database, Liquibase always takes a snapshot, so this prompt does not appear.)
  14. Note: The larger your database, the more performance impact a snapshot causes. If you cannot run a snapshot due to memory limitations, see Memory Limits of Inspecting Large Schemas.

You have now successfully created and customized a policy check!

Copy
Example Custom policy check property substitution output

Customization complete. Review the table below to confirm your changes.
+---------------------+-----------+--------+----------+----------+--------------------------------+--------------------------------+
| Short Name          | Scope     | Type   | Status   | Severity | Customization                  | Description                    |
+---------------------+-----------+--------+----------+----------+--------------------------------+--------------------------------+
| CustomCheckNoTables | changelog | sql,   | disabled | 0        | SCRIPT_DESCRIPTION = Custom    | Executes a custom check        |
|                     |           | xml,   |          |          | check                          | script.                        |
|                     |           | yaml,  |          |          | SCRIPT_SCOPE = CHANGELOG       |                                |
|                     |           | json   |          |          | SCRIPT_MESSAGE = The message   |                                |
|                     |           |        |          |          | to display when the check is   |                                |
|                     |           |        |          |          | triggered                      |                                |
|                     |           |        |          |          | SCRIPT_TYPE = PYTHON           |                                |
|                     |           |        |          |          | SCRIPT_PATH = null             |                                |
|                     |           |        |          |          | SCRIPT_ARGS = null             |                                |
|                     |           |        |          |          | REQUIRES_SNAPSHOT = false      |                                |
+---------------------+-----------+--------+----------+----------+--------------------------------+--------------------------------+
| MyCustomCheck       | changelog | sql,   | enabled  | 0        | SCRIPT_DESCRIPTION = Custom    | Executes a custom check        |
|                     |           | xml,   |          |          | check                          | script.                        |
|                     |           | yaml,  |          |          | SCRIPT_SCOPE = CHANGELOG       |                                |
|                     |           | json   |          |          | SCRIPT_MESSAGE = The message   |                                |
|                     |           |        |          |          | to display when the check is   |                                |
|                     |           |        |          |          | triggered                      |                                |
|                     |           |        |          |          | SCRIPT_TYPE = PYTHON           |                                |
|                     |           |        |          |          | SCRIPT_PATH = python_script.py |                                |
|                     |           |        |          |          |                                |                                |
|                     |           |        |          |          | SCRIPT_ARGS =                  |                                |
|                     |           |        |          |          | logfile="${LIQUIBASE_LOG_LEVEL |                                |
|                     |           |        |          |          | }"                             |                                |
|                     |           |        |          |          | REQUIRES_SNAPSHOT = false      |                                |
+---------------------+-----------+--------+----------+----------+--------------------------------+--------------------------------+
Liquibase command 'checks copy' was executed successfully.

Note: If the content of ${property-name} does not match a property, it is left as-is and it is not removed. The supported format includes alphanumeric characters, +, -, . , and _.

If a Custom Policy Check calls on a property that is not defined, you will get an error:
Error while executing script 'test.py': Exception: No value found for argument 'test' line: 13

Next steps