From 7673a6fe491ae0edd3a0eda01d3eaf7c220d9488 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Fri, 30 Jun 2017 12:51:34 -0400 Subject: [PATCH] fix a bug that prevents boolean inputs from being used in injectors when used as environment variables, boolean credential values are stringified; when used in extra_vars, they are treated as actual JSON boolean values (where possible) see: #6776 --- awx/main/models/credential.py | 15 ++++- awx/main/tests/unit/test_tasks.py | 93 +++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/awx/main/models/credential.py b/awx/main/models/credential.py index 585d57ae8e..b187f5934e 100644 --- a/awx/main/models/credential.py +++ b/awx/main/models/credential.py @@ -30,7 +30,7 @@ from awx.main.models.rbac import ( ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, ROLE_SINGLETON_SYSTEM_AUDITOR, ) -from awx.main.utils import encrypt_field +from awx.main.utils import encrypt_field, to_python_boolean __all__ = ['Credential', 'CredentialType', 'V1Credential'] @@ -535,6 +535,12 @@ class CredentialType(CommonModelNameNotUnique): # ansible-playbook) and a safe namespace with secret values hidden (for # DB storage) for field_name, value in credential.inputs.items(): + + if type(value) is bool: + # boolean values can't be secret/encrypted + safe_namespace[field_name] = namespace[field_name] = value + continue + if field_name in self.secret_fields: value = decrypt_field(credential, field_name) safe_namespace[field_name] = '**********' @@ -567,6 +573,13 @@ class CredentialType(CommonModelNameNotUnique): extra_vars[var_name] = Template(tmpl).render(**namespace) safe_extra_vars[var_name] = Template(tmpl).render(**safe_namespace) + # If the template renders to a stringified Boolean, they've _probably_ + # set up an extra_var injection with a boolean field; extra_vars supports JSON, + # so give them the actual boolean type they want + for v in (extra_vars, safe_extra_vars): + if v[var_name] in ('True', 'False'): + v[var_name] = to_python_boolean(v[var_name]) + if extra_vars: args.extend(['-e', json.dumps(extra_vars)]) diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index c7abfe52cb..e9f0b67c2e 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -724,6 +724,37 @@ class TestJobCredentials(TestJobExecution): assert env['MY_CLOUD_API_TOKEN'] == 'ABC123' + def test_custom_environment_injectors_with_boolean_env_var(self): + some_cloud = CredentialType( + kind='cloud', + name='SomeCloud', + managed_by_tower=False, + inputs={ + 'fields': [{ + 'id': 'turbo_button', + 'label': 'Turbo Button', + 'type': 'boolean' + }] + }, + injectors={ + 'env': { + 'TURBO_BUTTON': '{{turbo_button}}' + } + } + ) + credential = Credential( + pk=1, + credential_type=some_cloud, + inputs={'turbo_button': True} + ) + self.instance.extra_credentials.add(credential) + self.task.run(self.pk) + + assert self.run_pexpect.call_count == 1 + call_args, _ = self.run_pexpect.call_args_list[0] + args, cwd, env, stdout = call_args + assert env['TURBO_BUTTON'] == str(True) + def test_custom_environment_injectors_with_reserved_env_var(self): some_cloud = CredentialType( kind='cloud', @@ -823,6 +854,68 @@ class TestJobCredentials(TestJobExecution): assert '-e {"api_token": "ABC123"}' in ' '.join(args) + def test_custom_environment_injectors_with_boolean_extra_vars(self): + some_cloud = CredentialType( + kind='cloud', + name='SomeCloud', + managed_by_tower=False, + inputs={ + 'fields': [{ + 'id': 'turbo_button', + 'label': 'Turbo Button', + 'type': 'boolean' + }] + }, + injectors={ + 'extra_vars': { + 'turbo_button': '{{turbo_button}}' + } + } + ) + credential = Credential( + pk=1, + credential_type=some_cloud, + inputs={'turbo_button': True} + ) + self.instance.extra_credentials.add(credential) + self.task.run(self.pk) + + assert self.run_pexpect.call_count == 1 + call_args, _ = self.run_pexpect.call_args_list[0] + args, cwd, env, stdout = call_args + assert '-e {"turbo_button": true}' in ' '.join(args) + + def test_custom_environment_injectors_with_complicated_boolean_template(self): + some_cloud = CredentialType( + kind='cloud', + name='SomeCloud', + managed_by_tower=False, + inputs={ + 'fields': [{ + 'id': 'turbo_button', + 'label': 'Turbo Button', + 'type': 'boolean' + }] + }, + injectors={ + 'extra_vars': { + 'turbo_button': '{% if turbo_button %}FAST!{% else %}SLOW!{% endif %}' + } + } + ) + credential = Credential( + pk=1, + credential_type=some_cloud, + inputs={'turbo_button': True} + ) + self.instance.extra_credentials.add(credential) + self.task.run(self.pk) + + assert self.run_pexpect.call_count == 1 + call_args, _ = self.run_pexpect.call_args_list[0] + args, cwd, env, stdout = call_args + assert '-e {"turbo_button": "FAST!"}' in ' '.join(args) + def test_custom_environment_injectors_with_secret_extra_vars(self): """ extra_vars that contain secret field values should be censored in the DB