AAP-89152 - Add PostgreSQL utility functions for metrics-utility (#16458)

* 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) <noreply@anthropic.com>
Signed-off-by: Ciaran Shiels <cas.ireland@Gmail.com>

* 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) <noreply@anthropic.com>
Signed-off-by: Ciaran Shiels <cshiels@redhat.com>

* 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 <cursoragent@cursor.com>

* 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 <cursoragent@cursor.com>

---------

Signed-off-by: Ciaran Shiels <cas.ireland@Gmail.com>
Signed-off-by: Ciaran Shiels <cshiels@redhat.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ciaran Shiels
2026-08-31 14:59:29 -04:00
committed by GitHub
co-authored by Cursor Claude Sonnet 4.6
parent aea889921e
commit b336400963
@@ -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,
),
]