From b33640096308c4987f94212bb77e2c4f9691c1ef Mon Sep 17 00:00:00 2001 From: Ciaran Shiels <33393426+cshiels-ie@users.noreply.github.com> Date: Mon, 31 Aug 2026 19:59:29 +0100 Subject: [PATCH] AAP-89152 - Add PostgreSQL utility functions for metrics-utility (#16458) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add PostgreSQL utility functions for metrics-utility Creates two plpgsql functions used by metrics-utility when querying the AWX controller database via a read-only connection: - metrics_utility_parse_yaml_field: extracts a named field from YAML-encoded text - metrics_utility_is_valid_json: validates whether a text value is valid JSON GRANT EXECUTE TO PUBLIC so any database user (including ms_awx_readonly) can call the functions without requiring an explicit per-user grant. The migration is a no-op on SQLite so unit and functional tests are unaffected. Assisted-by: Claude Code (claude-sonnet-4-6) Co-Authored-By: Claude Sonnet 4.6 (1M context) Signed-off-by: Ciaran Shiels * Fix regex injection and broad exception in metrics utility migration - Escape `field` parameter before interpolating into regex pattern using regexp_replace with the whole-match `&` reference rather than bare concatenation; prevents regex metacharacters in the field name from altering match semantics - Narrow `EXCEPTION WHEN others` to `WHEN invalid_text_representation` (SQLSTATE 22P02) in metrics_utility_is_valid_json so only the expected JSON-cast failure is swallowed and unexpected runtime errors propagate Assisted-by: Claude Code (claude-sonnet-4-6) Co-Authored-By: Claude Sonnet 4.6 (1M context) Signed-off-by: Ciaran Shiels * Renumber migration 0206 to 0210 to avoid conflict Re-parent create_metrics_utility_functions migration from 0205 to 0209 after other migrations (0206–0209) merged to devel since branch creation. Co-authored-by: Cursor * Renumber migration 0210 to 0211 to avoid conflict 0210_remove_host_last_job_fields arrived on devel after the last rebase, re-parent create_metrics_utility_functions to depend on it instead. Co-authored-by: Cursor --------- Signed-off-by: Ciaran Shiels Signed-off-by: Ciaran Shiels Co-authored-by: Claude Sonnet 4.6 (1M context) Co-authored-by: Cursor --- .../0211_create_metrics_utility_functions.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 awx/main/migrations/0211_create_metrics_utility_functions.py diff --git a/awx/main/migrations/0211_create_metrics_utility_functions.py b/awx/main/migrations/0211_create_metrics_utility_functions.py new file mode 100644 index 0000000000..ef2a149acb --- /dev/null +++ b/awx/main/migrations/0211_create_metrics_utility_functions.py @@ -0,0 +1,60 @@ +from django.db import migrations + +from ._sqlite_helper import dbawaremigrations + +CREATE_FUNCTIONS_SQL = """ + CREATE OR REPLACE FUNCTION metrics_utility_parse_yaml_field( + str text, + field text + ) + RETURNS text AS + $$ + DECLARE + line_re text; + field_re text; + escaped_field text; + BEGIN + field_re := ' *[:=] *(.+?) *$'; + escaped_field := regexp_replace(field, '[.+*?^${}()|\\[\\]\\\\]', '\\\\&', 'g'); + line_re := '(?n)^' || escaped_field || field_re; + RETURN trim(both '"' from substring(str from line_re)); + END; + $$ + LANGUAGE plpgsql; + + CREATE OR REPLACE FUNCTION metrics_utility_is_valid_json(p_json text) + RETURNS boolean + AS + $$ + BEGIN + RETURN (p_json::json IS NOT NULL); + EXCEPTION + WHEN invalid_text_representation + THEN RETURN false; + END; + $$ + LANGUAGE plpgsql; + + GRANT EXECUTE ON FUNCTION metrics_utility_parse_yaml_field(text, text) TO PUBLIC; + GRANT EXECUTE ON FUNCTION metrics_utility_is_valid_json(text) TO PUBLIC; +""" + +DROP_FUNCTIONS_SQL = """ + DROP FUNCTION IF EXISTS metrics_utility_parse_yaml_field(text, text); + DROP FUNCTION IF EXISTS metrics_utility_is_valid_json(text); +""" + + +class Migration(migrations.Migration): + dependencies = [ + ('main', '0210_remove_host_last_job_fields'), + ] + + operations = [ + dbawaremigrations.RunSQL( + sql=CREATE_FUNCTIONS_SQL, + reverse_sql=DROP_FUNCTIONS_SQL, + sqlite_sql=dbawaremigrations.RunSQL.noop, + sqlite_reverse_sql=dbawaremigrations.RunSQL.noop, + ), + ]