Merge branch 'devel' into AAP-60052

This commit is contained in:
Peter Braun
2026-05-27 15:39:23 +02:00
committed by GitHub
9 changed files with 107 additions and 30 deletions

View File

@@ -17,7 +17,7 @@ from awx.api import serializers
from awx.api.generics import APIView, GenericAPIView
from awx.api.permissions import WebhookKeyPermission
from awx.main.models import Job, JobTemplate, WorkflowJob, WorkflowJobTemplate
from awx.main.constants import JOB_VARIABLE_PREFIXES
from awx.main.utils.common import get_job_variable_prefixes
logger = logging.getLogger('awx.api.views.webhooks')
@@ -166,7 +166,7 @@ class WebhookReceiverBase(APIView):
'extra_vars': {},
}
for name in JOB_VARIABLE_PREFIXES:
for name in get_job_variable_prefixes():
kwargs['extra_vars']['{}_webhook_event_type'.format(name)] = event_type
kwargs['extra_vars']['{}_webhook_event_guid'.format(name)] = event_guid
kwargs['extra_vars']['{}_webhook_event_ref'.format(name)] = event_ref

View File

@@ -325,6 +325,22 @@ register(
category_slug='jobs',
)
register(
'INCLUDE_DEPRECATED_AWX_VAR_PREFIX',
field_class=fields.BooleanField,
default=True,
label=_('Include Deprecated AWX Variable Prefix'),
help_text=_(
'When enabled (default), auto-generated job variables are emitted '
'with both the tower_ prefix and the deprecated awx_ prefix for '
'backward compatibility. Disable to emit only tower_ prefixed '
'variables and eliminate duplicates. The awx_ prefix is deprecated '
'and this setting will default to False in a future release.'
),
category=_('Jobs'),
category_slug='jobs',
)
register(
'AWX_ISOLATION_BASE_PATH',
field_class=fields.CharField,

View File

@@ -100,10 +100,6 @@ MAX_ISOLATED_PATH_COLON_DELIMITER = 2
SURVEY_TYPE_MAPPING = {'text': str, 'textarea': str, 'password': str, 'multiplechoice': str, 'multiselect': str, 'integer': int, 'float': (float, int)}
JOB_VARIABLE_PREFIXES = [
'awx',
'tower',
]
# Note, the \u001b[... are ansi color codes. We don't currenly import any of the python modules which define the codes.
# Importing a library just for this message seemed like overkill

View File

@@ -52,7 +52,7 @@ from awx.main.models.mixins import (
WebhookTemplateMixin,
OpaQueryPathMixin,
)
from awx.main.constants import JOB_VARIABLE_PREFIXES
from awx.main.utils.common import get_job_variable_prefixes
logger = logging.getLogger('awx.main.models.jobs')
@@ -817,19 +817,20 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskMana
def awx_meta_vars(self):
r = super(Job, self).awx_meta_vars()
prefixes = get_job_variable_prefixes()
if self.project:
for name in JOB_VARIABLE_PREFIXES:
for name in prefixes:
r['{}_project_revision'.format(name)] = self.project.scm_revision
r['{}_project_scm_branch'.format(name)] = self.project.scm_branch
if self.scm_branch:
for name in JOB_VARIABLE_PREFIXES:
for name in prefixes:
r['{}_job_scm_branch'.format(name)] = self.scm_branch
if self.job_template:
for name in JOB_VARIABLE_PREFIXES:
for name in prefixes:
r['{}_job_template_id'.format(name)] = self.job_template.pk
r['{}_job_template_name'.format(name)] = self.job_template.name
if self.execution_node:
for name in JOB_VARIABLE_PREFIXES:
for name in prefixes:
r['{}_execution_node'.format(name)] = self.execution_node
return r

View File

@@ -58,7 +58,8 @@ from awx.main.utils.common import (
)
from awx.main.utils.encryption import encrypt_dict, decrypt_field
from awx.main.utils import polymorphic
from awx.main.constants import ACTIVE_STATES, CAN_CANCEL, JOB_VARIABLE_PREFIXES
from awx.main.constants import ACTIVE_STATES, CAN_CANCEL
from awx.main.utils.common import get_job_variable_prefixes
from awx.main.redact import UriCleaner, REPLACE_STR
from awx.main.consumers import emit_channel_notification
from awx.main.fields import AskForField, OrderedManyToManyField
@@ -1568,7 +1569,8 @@ class UnifiedJob(
by AWX, for purposes of client playbook hooks
"""
r = {}
for name in JOB_VARIABLE_PREFIXES:
prefixes = get_job_variable_prefixes()
for name in prefixes:
r['{}_job_id'.format(name)] = self.pk
r['{}_job_launch_type'.format(name)] = self.launch_type
@@ -1577,7 +1579,7 @@ class UnifiedJob(
wj = self.get_workflow_job()
if wj:
schedule = getattr_dne(wj, 'schedule')
for name in JOB_VARIABLE_PREFIXES:
for name in prefixes:
r['{}_workflow_job_id'.format(name)] = wj.pk
r['{}_workflow_job_name'.format(name)] = wj.name
r['{}_workflow_job_launch_type'.format(name)] = wj.launch_type
@@ -1588,12 +1590,12 @@ class UnifiedJob(
if not created_by:
schedule = getattr_dne(self, 'schedule')
if schedule:
for name in JOB_VARIABLE_PREFIXES:
for name in prefixes:
r['{}_schedule_id'.format(name)] = schedule.pk
r['{}_schedule_name'.format(name)] = schedule.name
if created_by:
for name in JOB_VARIABLE_PREFIXES:
for name in prefixes:
r['{}_user_id'.format(name)] = created_by.pk
r['{}_user_name'.format(name)] = created_by.username
r['{}_user_email'.format(name)] = created_by.email
@@ -1602,7 +1604,7 @@ class UnifiedJob(
inventory = getattr_dne(self, 'inventory')
if inventory:
for name in JOB_VARIABLE_PREFIXES:
for name in prefixes:
r['{}_inventory_id'.format(name)] = inventory.pk
r['{}_inventory_name'.format(name)] = inventory.name

View File

@@ -8,7 +8,7 @@ from crum import impersonate
# AWX
from awx.main.models import UnifiedJobTemplate, Job, JobTemplate, WorkflowJobTemplate, Project, WorkflowJob, Schedule, Credential
from awx.api.versioning import reverse
from awx.main.constants import JOB_VARIABLE_PREFIXES
from awx.main.utils.common import get_job_variable_prefixes
@pytest.mark.django_db
@@ -160,7 +160,13 @@ class TestMetaVars:
job = Job.objects.create(name='job', created_by=admin_user)
job.save()
user_vars = ['_'.join(x) for x in itertools.product(['tower', 'awx'], ['user_name', 'user_id', 'user_email', 'user_first_name', 'user_last_name'])]
user_vars = [
'_'.join(x)
for x in itertools.product(
get_job_variable_prefixes(),
['user_name', 'user_id', 'user_email', 'user_first_name', 'user_last_name'],
)
]
for key in user_vars:
assert key in job.awx_meta_vars()
@@ -179,7 +185,7 @@ class TestMetaVars:
workflow_job.workflow_nodes.create(job=job)
data = job.awx_meta_vars()
for name in JOB_VARIABLE_PREFIXES:
for name in get_job_variable_prefixes():
assert data['{}_user_id'.format(name)] == admin_user.id
assert data['{}_user_name'.format(name)] == admin_user.username
assert data['{}_workflow_job_id'.format(name)] == workflow_job.pk
@@ -189,7 +195,7 @@ class TestMetaVars:
schedule = Schedule.objects.create(name='job-schedule', rrule='DTSTART:20171129T155939z\nFREQ=MONTHLY', unified_job_template=job_template)
job = Job.objects.create(name='fake-job', launch_type='workflow', schedule=schedule, job_template=job_template)
data = job.awx_meta_vars()
for name in JOB_VARIABLE_PREFIXES:
for name in get_job_variable_prefixes():
assert data['{}_schedule_id'.format(name)] == schedule.pk
assert '{}_user_name'.format(name) not in data
@@ -201,7 +207,7 @@ class TestMetaVars:
job = Job.objects.create(launch_type='workflow')
workflow_job.workflow_nodes.create(job=job)
result_hash = {}
for name in JOB_VARIABLE_PREFIXES:
for name in get_job_variable_prefixes():
result_hash['{}_job_id'.format(name)] = job.id
result_hash['{}_job_launch_type'.format(name)] = 'workflow'
result_hash['{}_workflow_job_name'.format(name)] = 'workflow-job'

View File

@@ -1,7 +1,7 @@
from unittest import mock
from awx.main.models import UnifiedJob, UnifiedJobTemplate, WorkflowJob, WorkflowJobNode, WorkflowApprovalTemplate, Job, User, Project, JobTemplate, Inventory
from awx.main.constants import JOB_VARIABLE_PREFIXES
from awx.main.utils.common import get_job_variable_prefixes
def test_incorrectly_formatted_variables():
@@ -50,7 +50,7 @@ class TestMetaVars:
maker = User(username='joe', pk=47, id=47)
inv = Inventory(name='example-inv', id=45)
result_hash = {}
for name in JOB_VARIABLE_PREFIXES:
for name in get_job_variable_prefixes():
result_hash['{}_job_id'.format(name)] = 42
result_hash['{}_job_launch_type'.format(name)] = 'manual'
result_hash['{}_user_name'.format(name)] = 'joe'
@@ -75,8 +75,48 @@ class TestMetaVars:
project=Project(name='jobs-sync', scm_revision='12345444'),
job_template=JobTemplate(name='jobs-jt', id=92, pk=92),
).awx_meta_vars()
for name in JOB_VARIABLE_PREFIXES:
for name in get_job_variable_prefixes():
assert data['{}_project_revision'.format(name)] == '12345444'
assert '{}_job_template_id'.format(name) in data
assert data['{}_job_template_id'.format(name)] == 92
assert data['{}_job_template_name'.format(name)] == 'jobs-jt'
class TestGetJobVariablePrefixes:
"""Tests for the get_job_variable_prefixes() helper function."""
def test_default_returns_both(self):
from django.conf import settings
with mock.patch.object(settings, 'INCLUDE_DEPRECATED_AWX_VAR_PREFIX', True, create=True):
assert get_job_variable_prefixes() == ['awx', 'tower']
def test_disabled_returns_tower_only(self):
from django.conf import settings
with mock.patch.object(settings, 'INCLUDE_DEPRECATED_AWX_VAR_PREFIX', False, create=True):
assert get_job_variable_prefixes() == ['tower']
def test_fallback_when_setting_not_available(self):
"""When setting is not available, falls back to both prefixes for backward compatibility."""
fake_settings = mock.MagicMock(spec=[])
with mock.patch('django.conf.settings', fake_settings):
assert get_job_variable_prefixes() == ['awx', 'tower']
def test_job_metavars_both_prefixes(self):
"""With INCLUDE_DEPRECATED_AWX_VAR_PREFIX=True, both awx_ and tower_ variables."""
from django.conf import settings
with mock.patch.object(settings, 'INCLUDE_DEPRECATED_AWX_VAR_PREFIX', True, create=True):
data = Job(name='fake-job', pk=1, id=1, launch_type='manual').awx_meta_vars()
assert 'awx_job_id' in data
assert 'tower_job_id' in data
def test_job_metavars_tower_only(self):
"""With INCLUDE_DEPRECATED_AWX_VAR_PREFIX=False, only tower_ prefixed variables."""
from django.conf import settings
with mock.patch.object(settings, 'INCLUDE_DEPRECATED_AWX_VAR_PREFIX', False, create=True):
data = Job(name='fake-job', pk=1, id=1, launch_type='manual').awx_meta_vars()
assert 'tower_job_id' in data
assert 'awx_job_id' not in data

View File

@@ -37,7 +37,7 @@ from awx.main.utils import encrypt_field, encrypt_value
from awx.main.utils.safe_yaml import SafeLoader
from awx.main.utils.licensing import Licenser
from awx.main.constants import JOB_VARIABLE_PREFIXES
from awx.main.utils.common import get_job_variable_prefixes
from receptorctl.socket_interface import ReceptorControl
@@ -372,12 +372,12 @@ class TestExtraVarSanitation(TestJobExecution):
extra_vars = yaml.load(fd, Loader=SafeLoader)
# ensure that strings are marked as unsafe
for name in JOB_VARIABLE_PREFIXES:
for name in get_job_variable_prefixes():
for variable_name in ['_job_template_name', '_user_name', '_job_launch_type', '_project_revision', '_inventory_name']:
assert hasattr(extra_vars['{}{}'.format(name, variable_name)], '__UNSAFE__')
# ensure that non-strings are marked as safe
for name in JOB_VARIABLE_PREFIXES:
for name in get_job_variable_prefixes():
for variable_name in ['_job_template_id', '_job_id', '_user_id', '_inventory_id']:
assert not hasattr(extra_vars['{}{}'.format(name, variable_name)], '__UNSAFE__')
@@ -524,7 +524,7 @@ class TestGenericRun:
call_args, _ = task._write_extra_vars_file.call_args_list[0]
private_data_dir, extra_vars, safe_dict = call_args
for name in JOB_VARIABLE_PREFIXES:
for name in get_job_variable_prefixes():
assert extra_vars['{}_user_id'.format(name)] == 123
assert extra_vars['{}_user_name'.format(name)] == "angry-spud"
@@ -615,7 +615,7 @@ class TestAdhocRun(TestJobExecution):
call_args, _ = task._write_extra_vars_file.call_args_list[0]
private_data_dir, extra_vars = call_args
for name in JOB_VARIABLE_PREFIXES:
for name in get_job_variable_prefixes():
assert extra_vars['{}_user_id'.format(name)] == 123
assert extra_vars['{}_user_name'.format(name)] == "angry-spud"

View File

@@ -93,6 +93,7 @@ __all__ = [
'get_event_partition_epoch',
'cleanup_new_process',
'unified_job_class_to_event_table_name',
'get_job_variable_prefixes',
]
@@ -773,6 +774,21 @@ def get_cpu_effective_capacity(cpu_count, is_control_node=False):
return max(1, int(cpu_count * forkcpu))
def get_job_variable_prefixes():
"""Return the list of active job variable prefixes based on INCLUDE_DEPRECATED_AWX_VAR_PREFIX setting.
When True (default), returns both 'awx' and 'tower' prefixes for backward compatibility.
When False, returns only 'tower'. The 'awx' prefix is deprecated and this setting
will default to False in a future release.
"""
from django.conf import settings
include_awx = getattr(settings, 'INCLUDE_DEPRECATED_AWX_VAR_PREFIX', True)
if include_awx:
return ['awx', 'tower']
return ['tower']
def convert_mem_str_to_bytes(mem_str):
"""Convert string with suffix indicating units to memory in bytes (base 2)