Skip to content

Commit

Permalink
PD-5477: Custom Policy Checks overview
Browse files Browse the repository at this point in the history
  • Loading branch information
adrian-velonis1 committed Dec 18, 2024
1 parent 652991d commit 8f3cd70
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 129 deletions.
132 changes: 3 additions & 129 deletions Content/liquibase-pro/policy-checks/custom-policy-checks/home.htm
Original file line number Diff line number Diff line change
Expand Up @@ -8,134 +8,8 @@
<body>
<MadCap:snippetBlock src="../../../Z_Resources/Snippets/images/icon-pro-top.flsnp" />
<h1>Custom Policy Checks</h1>
<p>Custom Policy Checks allow you to enforce compliance for a wide array of security, code standards, data quality, and other policies using Python scripts. In addition to <MadCap:xref href="../home.htm">Policy Checks</MadCap:xref> built into <MadCap:variable name="General.LBPro" />, you can use Custom Policy Cheks to write Python scripts to create nearly any check tailored for your workflow.</p>
<p class="note" MadCap:autonum="&lt;b&gt;Note: &lt;/b&gt;">This is a <MadCap:variable name="General.LBPro" /> feature, so you need a <a href="https://docs.liquibase.com/workflows/liquibase-pro/how-to-apply-your-liquibase-pro-license-key.html" target="_blank"><MadCap:variable name="General.LBPro" /> License Key</a> to use it.</p>
<h2>Uses</h2>
<p><MadCap:variable name="General.Liquibase" /> provides a <MadCap:xref href="../checks/home.htm">Library of Policy Checks</MadCap:xref>, 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 <MadCap:variable name="General.changelog" />s and databases for compliance.</p>
<h2>Prerequisites</h2>
<h3><MadCap:variable name="General.Liquibase" /> prerequisites</h3>
<ul>
<li>Install <MadCap:variable name="General.Liquibase" /> 4.29.0+</li>
<li>Install the <a href="https://mvnrepository.com/artifact/org.liquibase.ext/liquibase-checks"><MadCap:variable name="General.Liquibase" /> Checks extension</a>&#160;<ul><li>Download the <code class="language-text">liquibase-checks-&lt;version&gt;.jar</code> and put it in the <code class="language-text">liquibase/lib</code> directory.</li><li>If you use Maven, add this dependency to your <code class="language-text">pom.xml</code> file:</li><MadCap:codeSnippet><MadCap:codeSnippetCopyButton /><MadCap:codeSnippetBody MadCap:useLineNumbers="False" MadCap:lineNumberStart="1" MadCap:continue="False" xml:space="preserve">&lt;dependency&gt;
&lt;groupId&gt;org.liquibase.ext&lt;/groupId&gt;
&lt;artifactId&gt;liquibase-checks&lt;/artifactId&gt;
&lt;version&gt;1.0.0&lt;/version&gt;
&lt;/dependency&gt;</MadCap:codeSnippetBody></MadCap:codeSnippet></ul></li>
<li>Java Development Kit 17+
(available for Open JDK and Oracle JDK)</li>
<li>Linux, macOS, or Windows operating system</li>
<li>Familiarity with <MadCap:variable name="General.Liquibase" /> concepts: <MadCap:xref href="../../../concepts/changelogs/home.html">Changelog</MadCap:xref>, <MadCap:xref href="../../../concepts/changelogs/changeset.html">Changeset</MadCap:xref>, <MadCap:xref href="../../../commands/policy-checks/subcommands/home.htm">Policy Checks Commands</MadCap:xref>, <MadCap:xref href="../../../commands/policy-checks/command-parameters/checks-scope.htm">checks-scope</MadCap:xref>, and <MadCap:xref href="../../../commands/inspection/snapshot.html">snapshots</MadCap:xref></li>
</ul>
<h3>Python prerequisites</h3>
<p>Before creating a custom policy check with Python, we recommend being familiar with:</p>
<ul>
<li><a href="https://docs.python.org/3/reference/index.html">Python 3.10+</a>
</li>
<li>Optional: General coding and Python best practices which will improve your check performance:</li>
<ul>
<li>Efficiently handling of structured data objects</li>
<li>Effective and targeted parsing of text, objects, and SQL</li>
<li>Using Regular Expressions and other pattern matching tools within Python</li>
</ul>
<li>Optional: Import and use external utility and helper files</li>
<ul>
<li>Using an IDE and <code class="language-shell">pip install liquibase-checks-python</code> to develop Python checks is optional, but will allow you to utilize auto-completion and auto-documentation of helper methods.</li>
<li>
<MadCap:xref href="api-helper-scripts.htm">Custom Policy Check Helper Scripts</MadCap:xref>
</li>
</ul>
</ul>
<p class="tip" MadCap:autonum="&lt;b&gt;Tip: &lt;/b&gt;">If you're new to Python, it is a best practice to read the <a href="https://docs.python.org/3/library/distribution.html">official Python tutorials</a> before making custom checks.</p>
<p><b>Downloading Python itself is not required to create custom checks</b> in <MadCap:variable name="General.Liquibase" />, but it may be useful to test them. It is a best practice to test custom checks with Python 3.10.14+.</p>
<table style="width: 50%;border-top-left-radius: 5px;border-top-right-radius: 5px;border-bottom-right-radius: 5px;border-bottom-left-radius: 5px;border-left-style: solid;border-left-width: 1px;border-left-color: #d3d3d3;border-right-style: solid;border-right-width: 1px;border-right-color: #d3d3d3;border-top-style: solid;border-top-width: 1px;border-top-color: #d3d3d3;border-bottom-style: solid;border-bottom-width: 1px;border-bottom-color: #d3d3d3;margin-left: 0;margin-right: auto;">
<caption style="font-weight: bold;">Compatible Versions</caption>
<col />
<col />
<thead>
<tr>
<th>Tool</th>
<th>Version</th>
</tr>
</thead>
<tbody>
<tr>
<td>Python</td>
<td>3.10.14</td>
</tr>
<tr>
<td>GraalPy</td>
<td>24.0.0</td>
</tr>
</tbody>
</table>
<h2>Create a checks settings file</h2>
<ol>
<li><b>Create a checks settings file</b> if you don't already have one. In the&#160;CLI, run this command:</li><pre><code class="language-text">liquibase checks show</code></pre>
<li>If you don't have a checks settings file, a prompt appears that allows you to create the configuration file. Confirm the file creation in the prompt. By default, the settings file is named <code class="language-text">liquibase.checks-settings.conf</code>.</li>
</ol>
<h2>Create a new custom policy check</h2>
<ol>
<li><b>Create a new file</b> in your <MadCap:variable name="General.Liquibase" /> working directory. This file will contain the Python script that is your custom policy check. In this example, we title our new file <code class="language-text">custom-check-no-tables.py</code>.</li>
<li><b>Open</b> the new <code class="language-text">custom-check-no-tables.py</code> file and add the following custom policy check to it:</li>
<MadCap:codeSnippet>
<MadCap:codeSnippetCopyButton />
<MadCap:codeSnippetBody MadCap:useLineNumbers="True" MadCap:lineNumberStart="1" MadCap:continue="False" xml:space="preserve" style="mc-code-lang: Python;"># import Liquibase helper scripts 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</MadCap:codeSnippetBody>
</MadCap:codeSnippet>
<p>The purpose of this sample check is to ensure that there are no tables in the database.</p>
<p class="note" MadCap:autonum="&lt;b&gt;Note: &lt;/b&gt;"><MadCap:variable name="General.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.</p>
</ol>
<h2>Configure and customize your new check in the CLI</h2>
<ol>
<li><b>Initiate the customization process</b>. In the CLI, run this command:</li><pre><code class="language-text">liquibase checks customize --check-name=CustomCheckTemplate</code></pre>
<p>The CLI prompts you to finish configuring your file. A message displays:</p><pre><code class="language-text">This check cannot be customized directly because one or more fields does not have a default value.</code></pre>
<p><MadCap:variable name="General.Liquibase" /> will then create a copy of this check and initiate the customization workflow.</p>
<li><b>Give your check a short name</b> for easier identification. In this example, we will name the check <code class="language-text">CustomCheckNoTables</code>.<br />Now you have successfully created the check <code class="language-text">CustomCheckNoTables</code> from <code class="language-text">CustomCheckTemplate</code>.</li>
<p class="note" MadCap:autonum="&lt;b&gt;Note: &lt;/b&gt;">The new check short name <code class="language-text">CustomCheckNoTables</code> 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.</p>
<li>Set the <b>severity</b> to return a code of 0-4 when triggered. In this example, we will set the severity to <code class="language-text">1</code>. Options:</li>
<MadCap:snippetBlock src="../../../Z_Resources/Snippets/def/attributes/cli/policy-checks/cli-checks-severity-customization.flsnp" />
<li>Set the <b>script description</b> for the custom check. In this example we will set the description to:</li><pre><code class="language-text">This script looks to see if any tables exist and notifies you if one is detected.</code></pre>
<li>Set the <b>script scope</b> for the custom check to <code class="language-text">database</code>. The Python sample provided in this tutorial requires it.</li>
<p>In general, you should set the scope to <code class="language-text">changelog</code> or <code class="language-text">database</code> depending on what your custom script does:</p>
<ul>
<li><code class="language-text">changelog</code>: for example, if your check looks for syntax patterns or attributes in your <MadCap:variable name="General.Liquibase" />&#160;<MadCap:variable name="General.changelog" />. With this value, the check runs <b>once per <MadCap:variable name="General.changeset" /></b>. </li>
<li><code class="language-text">database</code>: 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 <b>once for each database object</b>.</li>
</ul>
<p class="tip" MadCap:autonum="&lt;b&gt;Tip: &lt;/b&gt;">It is a best practice for your custom checks to have <b>only one scope</b>, not both scopes.</p>
<li>
<p>Set the <b>script message</b>. 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.</p>
<p class="tip" MadCap:autonum="&lt;b&gt;Tip: &lt;/b&gt;"><b>Option for advanced users:</b> You can create <MadCap:xref href="status-message-variable.htm">Script Message Variables for Custom Policy Checks</MadCap:xref> which are identified in your Python script.</p>
</li>
<li>Set the <b>script type</b>. In <MadCap:variable name="General.LBPro" /> 4.29.0, Python is the only available script type.</li>
<li>Set the <b>script path</b>. This is the relative path where your script is stored, whether it is stored locally or in a repository.</li>
<p>In this example, we will set the path to <code class="language-text">Scripts/custom-check-no-tables.py</code>.</p>
<li>Set the <b>script argument</b>. This allows you to pass dynamic information into the custom policy check without modifying the Python code. The script arguments ensure that your Python scripts remain reusable with different variables.</li>
<li>Set whether the check requires a <b><MadCap:xref href="../../../commands/inspection/snapshot.html">snapshot</MadCap:xref></b>:</li><pre><code class="language-text">REQUIRES_SNAPSHOT (options: true, false) [false]:</code></pre>
<p class="note" MadCap:autonum="&lt;b&gt;Note: &lt;/b&gt;">If your check requires a snapshot, it may need to query the database, which can impact performance. The larger your database, the more performance impact this causes.</p>
</ol>
<p>You have now successfully created and customized a policy check!</p>
<h2>Run your new check</h2>
<p>To <b>run your custom check</b>, you must use the <code><a href="../../../commands/policy-checks/subcommands/run.htm">checks run</a></code> command. <MadCap:variable name="General.Liquibase" /> provides additional security configuration parameters for this command to ensure you do not accidentally execute Python code on your database:</p>
<ul>
<li>Custom policy checks are <b>disabled by default</b>. Using the <code class="language-text">checks run</code> command, you must set <code class="language-text">--checks-scripts-enabled=true</code> in the CLI or set <code class="language-text">LIQUIBASE_COMMAND_CHECKS_RUN_CHECKS_SCRIPTS_ENABLED=TRUE</code> via environment variable.</li>
<li>Custom policy check Python scripts can <b>filter to specific directory paths</b>. Using the <code class="language-text">checks run</code> command, you can set the <code class="language-text">--checks-scripts-path</code> parameter, <code class="language-text">LIQUIBASE_COMMAND_CHECKS_RUN_CHECKS_SCRIPTS_PATH</code> environment variable, and other standard methods.<ul><li>When set, the check's Python script must be in the specified path(s) to execute successfully.</li><li>If you don't set a script path, <MadCap:variable name="General.Liquibase" /> accepts any script path.</li></ul><p class="warning" MadCap:autonum="&lt;b&gt;Warning: &lt;/b&gt;">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).</p></li>
</ul>
<p>For example, if you enable custom checks via the CLI and want to run all policy checks, including your new check:</p><pre><code class="language-text">liquibase checks run --checks-scripts-enabled=true</code></pre>
<p> If you instead only want to run policy checks with the scope <code class="language-text">database</code> (such as this check), you must set the <code class="language-text">--checks-scope</code> parameter to <code class="language-text">database</code>:</p><pre><code class="language-text">liquibase checks run --checks-scope=database --checks-scripts-enabled=true</code></pre>
<p>If you instead only want to run this specific check, you must specify the check name with <code class="language-text">--check-name</code> parameter:</p><pre><code class="language-text">liquibase checks run --check-name=CustomCheckNoTables --checks-scripts-enabled=true</code></pre>
<h2>Troubleshooting</h2>
<p>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:</p><pre><code class="language-text">Error while executing script 'custom-check-no-tables.py': AttributeError: 'NoneType' object has no attribute 'getObjectTypeName' line: 7</code></pre>
<p>The Python code provided in this tutorial calls on database objects. This means you necessarily have to set the scope to <code class="language-text">database</code> while you create the check. Conversely, if you are creating a check that calls on <MadCap:variable name="General.changelog" /> objects, you necessarily have to set the scope to <code class="language-text">changelog</code>.</p>
<p>Custom Policy Checks are Python scripts that allow you run advanced policies using the <MadCap:variable name="General.Liquibase" />&#160;<MadCap:xref href="../home.htm">Policy Checks</MadCap:xref> framework. Custom policy checks allow you to enforce compliance for a wide array of security, code standards, data quality, and more.</p>
<p>While it's possible to configure the behavior of many <a href="../checks/home.htm">built-in <MadCap:variable name="General.Liquibase" /> policy checks</a>, those checks have a limited scope. In contrast, you can use custom policy checks to create unique checks for any situation in your <MadCap:variable name="General.Liquibase" /> workflow.</p>
<p class="note" MadCap:autonum="&lt;b&gt;Note: &lt;/b&gt;">This is a <MadCap:variable name="General.LBPro"></MadCap:variable> feature, so you need a <a href="https://docs.liquibase.com/workflows/liquibase-pro/how-to-apply-your-liquibase-pro-license-key.html" target="_blank"><MadCap:variable name="General.LBPro"></MadCap:variable> License Key</a> to use it.</p>
</body>
</html>
Loading

0 comments on commit 8f3cd70

Please sign in to comment.