Custom Policy Checks
Custom Policy Checks allow you to enforce compliance for a wide array of security, code standards, data quality, and other policies using Python scripts. This feature expands the capabilities of Policy Checks and allows all Liquibase Pro users who write Python scripts to create nearly any check for their workflow.
You can create checks tailored to your specific policies and technical contexts so you don't have to wait for your specific check to be created and released by Liquibase developers.
Note:
This is a Liquibase Pro feature, so you need a Liquibase Pro License Key to use it.
The Liquibase Checks extension, which includes all Custom Policy Check functionality, is available to Liquibase Pro 4.29.0+ users only on MacOS or Linux. This extension is required to use the Python-based custom policy checks.
Uses
Liquibase provides a Library of Policy Checks, but you may find that they don't fully serve the nuanced conditions and contexts you need to evaluate all your policy enforcement goals. Custom policy checks add the flexibility and power of Python to greatly expand your ability to parse and inspect your changelogs and databases for compliance.
Create a Custom Policy Check
Liquibase prerequisites
- Liquibase 4.29.0+ with the Liquibase Checks extension - only available to users on MacOS or Linux
- Download the
liquibase-checks-<version>.jar
and put it in theliquibase/lib
directory. - If you use Maven, add this dependency to your
pom.xml
file:
Copy<dependency>
<groupId>org.liquibase.ext</groupId>
<artifactId>liquibase-checks</artifactId>
<version>1.0.0</version>
</dependency> - Download the
- Java Development Kit 17+ (available for Open JDK and Oracle JDK)
- Linux, macOS, or Windows operating system
- Familiarity with Liquibase concepts: Changelog, Changeset, checks-scope, and snapshot
Python prerequisites
Before creating a custom policy check with Python, we recommend being familiar with:
- Python 3.10+
- Optional: General coding and Python best practices which will improve your check performance:
- Efficiently handling of structured data objects
- Effective and targeted parsing of text, objects, and SQL
- Using Regular Expressions and other pattern matching tools within Python
- Optional: Import and use external utility and helper files
- Using an IDE and
pip install liquibase-checks-python
to develop Python checks is optional, but will allow you to utilize auto-completion and auto-documentation of helper methods. - Checks Python API Helper Scripts
Python is not required to create custom checks in Liquibase, but it may be useful to test them. It is a best practice to test custom checks with Python 3.10.14+.
Tool | Version |
---|---|
Python | 3.10.14 |
GraalPy | 24.0.0 |
Create a checks settings file
- Run
liquibase checks customize --check-name=CustomCheckTemplate
to create a checks settings file.
A prompt appears that allows you to create the configuration file. - Confirm the file creation in the prompt.
- Run
liquibase checks customize --check-name=CustomCheckTemplate
again to begin the customization process.
Note: The entire list of checks are available in the CLI when the checks settings creation is complete. The output is long and can be copied to a separate file for reference.
Create a new check
- Create a new file in your Liquibase working directory. In this example, we will use this as our new file:
custom-check-no-tables.py
- Open the new
custom-check-no-tables.py
file and add the following custom policy check to it:
This file will contain the Python script that is your custom policy check.
Tip: Notice the status.fired = True
statement. The custom policy check notifies Liquibase that the check condition is met when it is set to true.
import liquibase_utilities as lb # this is where we are importing our liquibase_utilities helper script provided by liquibase
import sys # useful for indicating a check has been triggered
obj = lb.get_database_object() # this gets the database object the liquibase policy check is examining
status = lb.get_status() # this gets the status object of the liquibase check, used for reporting status and messages from the custom policy check
if lb.is_table(obj): # function provided from liquibase utilities
status.fired = True # indicates the custom check has been triggered
status.message = "No tables allowed!" # set the message of the custom check, which liquibase will return
sys.exit(1) # exit from the check
Tip: If you're new to Python, it is a best practice to read the official Python tutorials before making custom checks.
Note: If your check requires a snapshot of a database with hundreds or thousands of objects, this will take time and impact performance.
Configure and customize your new check in the CLI
- In the CLI, run this command to initiate the customization process:
- Give your check a short name for easier identification. In this example, we will name the check
CustomCheckNoTables
.
Now you have successfully created the checkCustomCheckNoTables
fromCustomCheckTemplate
. - 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
: This scope indicates the check reviews all current changesets. Set the scope tochangelog
when your check will inspect and trigger based on conditions or actions determined in a changeset.database
: This scope indicates the check runs once for each database object. Set the the scope todatabase
when your check will inspect and trigger based on the existing state of a database schema. This can mean you're looking for the presence of keys, indexes, or table name patterns.-
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.
Tip: Option for advanced users: You can create Status Message Variables for Custom Policy Checks which are identified in your Python script.
- Set the script type. In Liquibase Pro 4.29.0, Python is the only available script type.
- Set the script path. This is the relative path where your script is stored, whether it is stored locally or in a repository.
- Set the script argument. This is a way for you to pass dynamic information into the custom policy check without modifying the Python code. The script arguments allow your Python scripts remain reusable with different variables.
- Set whether the check requires 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 this check and initiate the customization workflow.
Note: The new check short name CustomCheckNoTables
and all of its associated information comes from the Python script you created. Your company may have their own coding standards that these scripts must adhere to.
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.
Tip: It is a best practice to store all of your scripts in the same folder for easy access.
In this example, we will set the path to Scripts/custom-check-no-tables.py
.
REQUIRES_SNAPSHOT (options: true, false) [false]:
Note: You may need to query the database if the changelog requires a snapshot. Requiring a snapshot calls for a database connection and impacts performance. It can take time to process if your database has hundreds or thousands of objects.
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-paths
parameter,LIQUIBASE_COMMAND_CHECKS_RUN_CHECKS_SCRIPTS_PATHS
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 with both local file systems and network utilities like the targeted database. We recommend reviewing these 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
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
.