diff --git a/awx/main/fields.py b/awx/main/fields.py index 14e1cc6ad0..5f1761277d 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -802,23 +802,33 @@ class CredentialTypeInjectorField(JSONSchemaField): for field in model_instance.defined_fields ) + class ExplodingNamespace: + def __unicode__(self): + raise UndefinedError(_('Must define unnamed file injector in order to reference `tower.filename`.')) + class TowerNamespace: - filename = None + def __init__(self): + self.filename = ExplodingNamespace() + + def __unicode__(self): + raise UndefinedError(_('Cannot directly reference reserved `tower` namespace container.')) + valid_namespace['tower'] = TowerNamespace() # ensure either single file or multi-file syntax is used (but not both) template_names = [x for x in value.get('file', {}).keys() if x.startswith('template')] - if 'template' in template_names and len(template_names) > 1: - raise django_exceptions.ValidationError( - _('Must use multi-file syntax when injecting multiple files'), - code='invalid', - params={'value': value}, - ) - if 'template' not in template_names: - valid_namespace['tower'].filename = TowerNamespace() + if 'template' in template_names: + valid_namespace['tower'].filename = 'EXAMPLE_FILENAME' + if len(template_names) > 1: + raise django_exceptions.ValidationError( + _('Must use multi-file syntax when injecting multiple files'), + code='invalid', + params={'value': value}, + ) + elif template_names: for template_name in template_names: template_name = template_name.split('.')[1] - setattr(valid_namespace['tower'].filename, template_name, 'EXAMPLE') + setattr(valid_namespace['tower'].filename, template_name, 'EXAMPLE_FILENAME') for type_, injector in value.items(): for key, tmpl in injector.items(): diff --git a/awx/main/tests/unit/test_fields.py b/awx/main/tests/unit/test_fields.py index bec0c4de2f..79a163b840 100644 --- a/awx/main/tests/unit/test_fields.py +++ b/awx/main/tests/unit/test_fields.py @@ -96,10 +96,26 @@ def test_cred_type_input_schema_validity(input_, valid): ({'invalid-injector': {}}, False), ({'file': 123}, False), ({'file': {}}, True), + # Uses credential inputs inside of unnamed file contents ({'file': {'template': '{{username}}'}}, True), + # Uses named file ({'file': {'template.username': '{{username}}'}}, True), + # Uses multiple named files ({'file': {'template.username': '{{username}}', 'template.password': '{{pass}}'}}, True), + # Use of unnamed file mutually exclusive with use of named files ({'file': {'template': '{{username}}', 'template.password': '{{pass}}'}}, False), + # References non-existant named file + ({'env': {'FROM_FILE': "{{tower.filename.cert}}"}}, False), + # References unnamed file, but a file was never defined + ({'env': {'FROM_FILE': "{{tower.filename}}"}}, False), + # Cannot reference tower namespace itself (what would this return??) + ({'env': {'FROM_FILE': "{{tower}}"}}, False), + # References filename of a named file + ({'file': {'template.cert': '{{awx_secret}}'}, 'env': {'FROM_FILE': "{{tower.filename.cert}}"}}, True), + # With named files, `tower.filename` is another namespace, so it cannot be referenced + ({'file': {'template.cert': '{{awx_secret}}'}, 'env': {'FROM_FILE': "{{tower.filename}}"}}, False), + # With an unnamed file, `tower.filename` is just the filename + ({'file': {'template': '{{awx_secret}}'}, 'env': {'THE_FILENAME': "{{tower.filename}}"}}, True), ({'file': {'foo': 'bar'}}, False), ({'env': 123}, False), ({'env': {}}, True),