mirror of
https://github.com/ansible/awx.git
synced 2026-01-10 15:32:07 -03:30
Merge pull request #13267 from philipsd6/feature/complex_extra_vars
Enable support for injecting complex extra vars
This commit is contained in:
commit
1af955d28c
@ -788,7 +788,8 @@ class CredentialTypeInjectorField(JSONSchemaField):
|
||||
'type': 'object',
|
||||
'patternProperties': {
|
||||
# http://docs.ansible.com/ansible/playbooks_variables.html#what-makes-a-valid-variable-name
|
||||
'^[a-zA-Z_]+[a-zA-Z0-9_]*$': {'type': 'string'},
|
||||
# plus, add ability to template
|
||||
r'^[a-zA-Z_\{\}]+[a-zA-Z0-9_\{\}]*$': {"anyOf": [{'type': 'string'}, {'type': 'array'}, {'$ref': '#/properties/extra_vars'}]}
|
||||
},
|
||||
'additionalProperties': False,
|
||||
},
|
||||
@ -855,27 +856,44 @@ class CredentialTypeInjectorField(JSONSchemaField):
|
||||
template_name = template_name.split('.')[1]
|
||||
setattr(valid_namespace['tower'].filename, template_name, 'EXAMPLE_FILENAME')
|
||||
|
||||
def validate_template_string(type_, key, tmpl):
|
||||
try:
|
||||
sandbox.ImmutableSandboxedEnvironment(undefined=StrictUndefined).from_string(tmpl).render(valid_namespace)
|
||||
except UndefinedError as e:
|
||||
raise django_exceptions.ValidationError(
|
||||
_('{sub_key} uses an undefined field ({error_msg})').format(sub_key=key, error_msg=e),
|
||||
code='invalid',
|
||||
params={'value': value},
|
||||
)
|
||||
except SecurityError as e:
|
||||
raise django_exceptions.ValidationError(_('Encountered unsafe code execution: {}').format(e))
|
||||
except TemplateSyntaxError as e:
|
||||
raise django_exceptions.ValidationError(
|
||||
_('Syntax error rendering template for {sub_key} inside of {type} ({error_msg})').format(sub_key=key, type=type_, error_msg=e),
|
||||
code='invalid',
|
||||
params={'value': value},
|
||||
)
|
||||
|
||||
def validate_extra_vars(key, node):
|
||||
if isinstance(node, dict):
|
||||
for k, v in node.items():
|
||||
validate_template_string("extra_vars", 'a key' if key is None else key, k)
|
||||
validate_extra_vars(k if key is None else "{key}.{k}".format(key=key, k=k), v)
|
||||
elif isinstance(node, list):
|
||||
for i, x in enumerate(node):
|
||||
validate_extra_vars("{key}[{i}]".format(key=key, i=i), x)
|
||||
else:
|
||||
validate_template_string("extra_vars", key, node)
|
||||
|
||||
for type_, injector in value.items():
|
||||
if type_ == 'env':
|
||||
for key in injector.keys():
|
||||
self.validate_env_var_allowed(key)
|
||||
for key, tmpl in injector.items():
|
||||
try:
|
||||
sandbox.ImmutableSandboxedEnvironment(undefined=StrictUndefined).from_string(tmpl).render(valid_namespace)
|
||||
except UndefinedError as e:
|
||||
raise django_exceptions.ValidationError(
|
||||
_('{sub_key} uses an undefined field ({error_msg})').format(sub_key=key, error_msg=e),
|
||||
code='invalid',
|
||||
params={'value': value},
|
||||
)
|
||||
except SecurityError as e:
|
||||
raise django_exceptions.ValidationError(_('Encountered unsafe code execution: {}').format(e))
|
||||
except TemplateSyntaxError as e:
|
||||
raise django_exceptions.ValidationError(
|
||||
_('Syntax error rendering template for {sub_key} inside of {type} ({error_msg})').format(sub_key=key, type=type_, error_msg=e),
|
||||
code='invalid',
|
||||
params={'value': value},
|
||||
)
|
||||
if type_ == 'extra_vars':
|
||||
validate_extra_vars(None, injector)
|
||||
else:
|
||||
for key, tmpl in injector.items():
|
||||
validate_template_string(type_, key, tmpl)
|
||||
|
||||
|
||||
class AskForField(models.BooleanField):
|
||||
|
||||
@ -528,9 +528,13 @@ class CredentialType(CommonModelNameNotUnique):
|
||||
|
||||
if 'INVENTORY_UPDATE_ID' not in env:
|
||||
# awx-manage inventory_update does not support extra_vars via -e
|
||||
extra_vars = {}
|
||||
for var_name, tmpl in self.injectors.get('extra_vars', {}).items():
|
||||
extra_vars[var_name] = sandbox_env.from_string(tmpl).render(**namespace)
|
||||
def build_extra_vars(node):
|
||||
if isinstance(node, dict):
|
||||
return {build_extra_vars(k): build_extra_vars(v) for k, v in node.items()}
|
||||
elif isinstance(node, list):
|
||||
return [build_extra_vars(x) for x in node]
|
||||
else:
|
||||
return sandbox_env.from_string(node).render(**namespace)
|
||||
|
||||
def build_extra_vars_file(vars, private_dir):
|
||||
handle, path = tempfile.mkstemp(dir=os.path.join(private_dir, 'env'))
|
||||
@ -540,6 +544,7 @@ class CredentialType(CommonModelNameNotUnique):
|
||||
os.chmod(path, stat.S_IRUSR)
|
||||
return path
|
||||
|
||||
extra_vars = build_extra_vars(self.injectors.get('extra_vars', {}))
|
||||
if extra_vars:
|
||||
path = build_extra_vars_file(extra_vars, private_data_dir)
|
||||
container_path = to_container_path(path, private_data_dir)
|
||||
|
||||
@ -1209,6 +1209,42 @@ class TestJobCredentials(TestJobExecution):
|
||||
assert extra_vars["turbo_button"] == "True"
|
||||
return ['successful', 0]
|
||||
|
||||
def test_custom_environment_injectors_with_nested_extra_vars(self, private_data_dir, job, mock_me):
|
||||
task = jobs.RunJob()
|
||||
some_cloud = CredentialType(
|
||||
kind='cloud',
|
||||
name='SomeCloud',
|
||||
managed=False,
|
||||
inputs={'fields': [{'id': 'host', 'label': 'Host', 'type': 'string'}]},
|
||||
injectors={'extra_vars': {'auth': {'host': '{{host}}'}}},
|
||||
)
|
||||
credential = Credential(pk=1, credential_type=some_cloud, inputs={'host': 'example.com'})
|
||||
job.credentials.add(credential)
|
||||
|
||||
args = task.build_args(job, private_data_dir, {})
|
||||
credential.credential_type.inject_credential(credential, {}, {}, args, private_data_dir)
|
||||
extra_vars = parse_extra_vars(args, private_data_dir)
|
||||
|
||||
assert extra_vars["auth"]["host"] == "example.com"
|
||||
|
||||
def test_custom_environment_injectors_with_templated_extra_vars_key(self, private_data_dir, job, mock_me):
|
||||
task = jobs.RunJob()
|
||||
some_cloud = CredentialType(
|
||||
kind='cloud',
|
||||
name='SomeCloud',
|
||||
managed=False,
|
||||
inputs={'fields': [{'id': 'environment', 'label': 'Environment', 'type': 'string'}, {'id': 'host', 'label': 'Host', 'type': 'string'}]},
|
||||
injectors={'extra_vars': {'{{environment}}_auth': {'host': '{{host}}'}}},
|
||||
)
|
||||
credential = Credential(pk=1, credential_type=some_cloud, inputs={'environment': 'test', 'host': 'example.com'})
|
||||
job.credentials.add(credential)
|
||||
|
||||
args = task.build_args(job, private_data_dir, {})
|
||||
credential.credential_type.inject_credential(credential, {}, {}, args, private_data_dir)
|
||||
extra_vars = parse_extra_vars(args, private_data_dir)
|
||||
|
||||
assert extra_vars["test_auth"]["host"] == "example.com"
|
||||
|
||||
def test_custom_environment_injectors_with_complicated_boolean_template(self, job, private_data_dir, mock_me):
|
||||
task = jobs.RunJob()
|
||||
some_cloud = CredentialType(
|
||||
|
||||
@ -172,7 +172,11 @@ of the [Jinja templating language](https://jinja.palletsprojects.com/en/2.10.x/)
|
||||
"THIRD_PARTY_CLOUD_API_TOKEN": "{{api_token}}"
|
||||
},
|
||||
"extra_vars": {
|
||||
"some_extra_var": "{{username}}:{{password}"
|
||||
"some_extra_var": "{{username}}:{{password}}",
|
||||
"auth": {
|
||||
"username": "{{username}}",
|
||||
"password": "{{password}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user