Create a Custom Policy Check
This tutorial shows you how to create, customize, and run a Liquibase custom policy check using a sample Python script. For a conceptual overview of this feature, see Custom Policy Checks.
For examples of more advanced Python scripts and guidance on writing Python code to use in the Liquibase policy checks framework, see Sample Custom Policy Check Scripts.
Prerequisites
Liquibase prerequisites
- Install Liquibase 4.29.0+
- Configure a valid Liquibase Pro license key
- Ensure the Liquibase Checks extension is installed. In Liquibase 4.31.0+, it is already installed in the
/liquibase/internal/lib
directory, so no action is needed. If the checks JAR is not installed, downloadliquibase-checks-<version>.jar
and put it in theliquibase/lib
directory. Or, if you use Maven, add this dependency to yourpom.xml
file:
<dependency>
<groupId>org.liquibase.ext</groupId>
<artifactId>liquibase-checks</artifactId>
<version>2.0.0</version>
</dependency>
Python prerequisites
Before creating a custom policy check with Python, we recommend being familiar with:
- Python 3.10.14+. (See here for the official Python tutorial)
- Optional: General coding and Python best practices which will improve your check performance:
- Efficient handling of structured data objects
- Effective and targeted parsing of text, objects, and SQL
- Using regular expressions and other pattern-matching tools within Python
- Using Python virtual environments. Liquibase comes with a built-in virtual environment for Liquibase custom policy checks. The built-in environment includes Liquibase Python modules and some common external Python modules—no configuration needed. However, if you want to install additional modules, or if you want your IDE to recognize the Liquibase modules, you must Create a Python Virtual Environment separately.
Tip: Downloading Python itself is not required to create custom checks in the Liquibase checks framework, but it may be useful to test checks against Python 3.10.14+.
Create a checks settings file
- Create a checks settings file if you don't already have one. In the CLI, run this command:
- 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
.
liquibase checks show
Create a new custom policy check
- 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 filecustom_check_no_tables.py
. - Open the new
custom_check_no_tables.py
file and add the following custom policy check to it:
# 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
status = lb.get_status() # Status object of the check
# write check logic
if lb.is_table(obj): # checks if the current object is a table
status.fired = True # indicates that the custom check has been triggered
status.message = "No tables allowed!" # message for Liquibase to return when check is triggered
sys.exit(1) # halt execution of the script
# default return code
False
The purpose of this sample check is to ensure that there are no tables in the database.
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
- Initiate the customization process. In the CLI, run this command:
- Give your new check a short name for easier identification. In this example, we will name the check
CustomCheckNoTables
. - Set the severity to return a code of 0–4 when triggered. In this example, we will set the severity to
1
. Options: 'INFO' | 0
'MINOR' | 1
'MAJOR' | 2
'CRITICAL' | 3
'BLOCKER' | 4
- Set the script description for the custom check. In this example, we will set the description to:
- Set the script scope for the custom check to
database
. The Python sample provided in this tutorial requires it. changelog
: for example, if your check looks for syntax patterns or attributes in your Liquibase changelog. 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. With this value, the check runs once for each database object.- Set the 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.
- Set the script path. This is the relative path where your script is stored, whether it is stored locally or in a repository. In this example, we will set the path to:
scripts/custom-check-no-tables.py
. - Set the script argument. This allows you to pass dynamic information into the custom policy check without modifying the Python code. For example, if you specify
MAX_SIZE=10
in the CLI, you can retrieve it in your code with a variable:max_size = int(liquibase_utilities.get_arg("MAX_SIZE"))
. If you customize your check later, you can specify a new value in the CLI. - If your script scope is
changelog
, set whether the check requires a database snapshot. Specifytrue
if your check needs to inspect database objects. (If your script scope isdatabase
, Liquibase always takes a snapshot.)
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.
Tip: For ease of use, it is a best practice to name your custom check after your Python script.
This script looks to see if any tables exist and notifies you if one is detected.
In general, you should set the scope to changelog
or database
depending on what your custom script does:
Tip: It is a best practice for your custom checks to have only one scope, not both scopes.
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!
Run your new check
To run your custom check, you must use the checks run
command. Liquibase provides additional security configuration parameters for this command to ensure you do not accidentally execute Python code on your database:
- Custom policy checks are disabled by default. Using the
checks run
command, you must set--checks-scripts-enabled=true
in the CLI or setLIQUIBASE_COMMAND_CHECKS_RUN_CHECKS_SCRIPTS_ENABLED=TRUE
via environment variable. - Custom policy check Python scripts can filter to specific directory paths. Using the
checks run
command, you can set the--checks-scripts-path
parameter,LIQUIBASE_COMMAND_CHECKS_RUN_CHECKS_SCRIPTS_PATH
environment variable, and other standard methods.- When set, the check's Python script must be in the specified path(s) to execute successfully.
- If you don't set a script path, Liquibase accepts any script path.
Warning: Custom policy checks are not isolated and can interact both with local file systems and network utilities like the targeted database. It is a best practice to review all checks prior to execution to ensure they only affect the intended object(s).
For example, if you enable custom checks via the CLI and want to run all policy checks, including your new check:
liquibase checks run --checks-scripts-enabled=true
If you instead only want to run policy checks with the scope database
(such as this check), you must set the --checks-scope
parameter to database
:
liquibase checks run --checks-scope=database --checks-scripts-enabled=true
If you instead only want to run this specific check, you must specify the check name with --check-name
parameter:
liquibase checks run --check-name=CustomCheckNoTables --checks-scripts-enabled=true
Troubleshooting
Scope mismatch attribute error
If the scope you specify in the CLI while creating your check is mismatched with what your Python code actually does, you may receive an error like this:
Error while executing script 'custom-check-no-tables.py': AttributeError: 'NoneType' object has no attribute 'getObjectTypeName' line: 7
The Python code provided in this tutorial calls on database objects. This means you necessarily have to set the scope to database
while you create the check. Conversely, if you are creating a check that calls on changelog objects, you necessarily have to set the scope to changelog
.
Next steps
- Sample Custom Policy Check Scripts: see examples of real-world scripts for custom policy checks that you can adapt
- Liquibase Python Modules: learn how to access the Liquibase API to write more custom checks
- Write Dynamic Status Messages for Custom Policy Checks: improve the usability of your checks with better output messages