mirror of
https://github.com/ansible/awx.git
synced 2026-03-27 13:55:04 -02:30
Merge pull request #696 from jladdjr/awx_349_custom_cred_write_multiple_files
Feature: Multi-file support for Credential Types
This commit is contained in:
@@ -695,11 +695,10 @@ class CredentialTypeInjectorField(JSONSchemaField):
|
|||||||
'properties': {
|
'properties': {
|
||||||
'file': {
|
'file': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'patternProperties': {
|
||||||
'template': {'type': 'string'},
|
'^template(\.[a-zA-Z_]+[a-zA-Z0-9_]*)?$': {'type': 'string'},
|
||||||
},
|
},
|
||||||
'additionalProperties': False,
|
'additionalProperties': False,
|
||||||
'required': ['template'],
|
|
||||||
},
|
},
|
||||||
'env': {
|
'env': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
@@ -749,8 +748,22 @@ class CredentialTypeInjectorField(JSONSchemaField):
|
|||||||
|
|
||||||
class TowerNamespace:
|
class TowerNamespace:
|
||||||
filename = None
|
filename = None
|
||||||
|
|
||||||
valid_namespace['tower'] = TowerNamespace()
|
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()
|
||||||
|
for template_name in template_names:
|
||||||
|
template_name = template_name.split('.')[1]
|
||||||
|
setattr(valid_namespace['tower'].filename, template_name, 'EXAMPLE')
|
||||||
|
|
||||||
for type_, injector in value.items():
|
for type_, injector in value.items():
|
||||||
for key, tmpl in injector.items():
|
for key, tmpl in injector.items():
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -594,7 +594,7 @@ class CredentialType(CommonModelNameNotUnique):
|
|||||||
return
|
return
|
||||||
|
|
||||||
class TowerNamespace:
|
class TowerNamespace:
|
||||||
filename = None
|
pass
|
||||||
|
|
||||||
tower_namespace = TowerNamespace()
|
tower_namespace = TowerNamespace()
|
||||||
|
|
||||||
@@ -622,17 +622,25 @@ class CredentialType(CommonModelNameNotUnique):
|
|||||||
if len(value):
|
if len(value):
|
||||||
namespace[field_name] = value
|
namespace[field_name] = value
|
||||||
|
|
||||||
file_tmpl = self.injectors.get('file', {}).get('template')
|
file_tmpls = self.injectors.get('file', {})
|
||||||
if file_tmpl is not None:
|
# If any file templates are provided, render the files and update the
|
||||||
# If a file template is provided, render the file and update the
|
# special `tower` template namespace so the filename can be
|
||||||
# special `tower` template namespace so the filename can be
|
# referenced in other injectors
|
||||||
# referenced in other injectors
|
for file_label, file_tmpl in file_tmpls.items():
|
||||||
data = Template(file_tmpl).render(**namespace)
|
data = Template(file_tmpl).render(**namespace)
|
||||||
_, path = tempfile.mkstemp(dir=private_data_dir)
|
_, path = tempfile.mkstemp(dir=private_data_dir)
|
||||||
with open(path, 'w') as f:
|
with open(path, 'w') as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
|
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
|
||||||
namespace['tower'].filename = path
|
|
||||||
|
# determine if filename indicates single file or many
|
||||||
|
if file_label.find('.') == -1:
|
||||||
|
tower_namespace.filename = path
|
||||||
|
else:
|
||||||
|
if not hasattr(tower_namespace, 'filename'):
|
||||||
|
tower_namespace.filename = TowerNamespace()
|
||||||
|
file_label = file_label.split('.')[1]
|
||||||
|
setattr(tower_namespace.filename, file_label, path)
|
||||||
|
|
||||||
for env_var, tmpl in self.injectors.get('env', {}).items():
|
for env_var, tmpl in self.injectors.get('env', {}).items():
|
||||||
if env_var.startswith('ANSIBLE_') or env_var in self.ENV_BLACKLIST:
|
if env_var.startswith('ANSIBLE_') or env_var in self.ENV_BLACKLIST:
|
||||||
|
|||||||
@@ -107,8 +107,11 @@ def test_cred_type_input_schema_validity(input_, valid):
|
|||||||
({}, True),
|
({}, True),
|
||||||
({'invalid-injector': {}}, False),
|
({'invalid-injector': {}}, False),
|
||||||
({'file': 123}, False),
|
({'file': 123}, False),
|
||||||
({'file': {}}, False),
|
({'file': {}}, True),
|
||||||
({'file': {'template': '{{username}}'}}, True),
|
({'file': {'template': '{{username}}'}}, True),
|
||||||
|
({'file': {'template.username': '{{username}}'}}, True),
|
||||||
|
({'file': {'template.username': '{{username}}', 'template.password': '{{pass}}'}}, True),
|
||||||
|
({'file': {'template': '{{username}}', 'template.password': '{{pass}}'}}, False),
|
||||||
({'file': {'foo': 'bar'}}, False),
|
({'file': {'foo': 'bar'}}, False),
|
||||||
({'env': 123}, False),
|
({'env': 123}, False),
|
||||||
({'env': {}}, True),
|
({'env': {}}, True),
|
||||||
|
|||||||
@@ -1227,6 +1227,50 @@ class TestJobCredentials(TestJobExecution):
|
|||||||
self.run_pexpect.side_effect = run_pexpect_side_effect
|
self.run_pexpect.side_effect = run_pexpect_side_effect
|
||||||
self.task.run(self.pk)
|
self.task.run(self.pk)
|
||||||
|
|
||||||
|
def test_custom_environment_injectors_with_files(self):
|
||||||
|
some_cloud = CredentialType(
|
||||||
|
kind='cloud',
|
||||||
|
name='SomeCloud',
|
||||||
|
managed_by_tower=False,
|
||||||
|
inputs={
|
||||||
|
'fields': [{
|
||||||
|
'id': 'cert',
|
||||||
|
'label': 'Certificate',
|
||||||
|
'type': 'string'
|
||||||
|
}, {
|
||||||
|
'id': 'key',
|
||||||
|
'label': 'Key',
|
||||||
|
'type': 'string'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
injectors={
|
||||||
|
'file': {
|
||||||
|
'template.cert': '[mycert]\n{{cert}}',
|
||||||
|
'template.key': '[mykey]\n{{key}}'
|
||||||
|
},
|
||||||
|
'env': {
|
||||||
|
'MY_CERT_INI_FILE': '{{tower.filename.cert}}',
|
||||||
|
'MY_KEY_INI_FILE': '{{tower.filename.key}}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
credential = Credential(
|
||||||
|
pk=1,
|
||||||
|
credential_type=some_cloud,
|
||||||
|
inputs = {'cert': 'CERT123', 'key': 'KEY123'}
|
||||||
|
)
|
||||||
|
self.instance.credentials.add(credential)
|
||||||
|
self.task.run(self.pk)
|
||||||
|
|
||||||
|
def run_pexpect_side_effect(*args, **kwargs):
|
||||||
|
args, cwd, env, stdout = args
|
||||||
|
assert open(env['MY_CERT_INI_FILE'], 'rb').read() == '[mycert]\nCERT123'
|
||||||
|
assert open(env['MY_KEY_INI_FILE'], 'rb').read() == '[mykey]\nKEY123'
|
||||||
|
return ['successful', 0]
|
||||||
|
|
||||||
|
self.run_pexpect.side_effect = run_pexpect_side_effect
|
||||||
|
self.task.run(self.pk)
|
||||||
|
|
||||||
def test_multi_cloud(self):
|
def test_multi_cloud(self):
|
||||||
gce = CredentialType.defaults['gce']()
|
gce = CredentialType.defaults['gce']()
|
||||||
gce_credential = Credential(
|
gce_credential = Credential(
|
||||||
|
|||||||
@@ -194,7 +194,8 @@ certificate/key data:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Note that the single and multi-file syntax cannot be mixed within the same
|
||||||
|
``Credential Type``.
|
||||||
|
|
||||||
Job and Job Template Credential Assignment
|
Job and Job Template Credential Assignment
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
@@ -326,6 +327,8 @@ When verifying acceptance we should ensure the following statements are true:
|
|||||||
* Custom `Credential Types` should support injecting both single and
|
* Custom `Credential Types` should support injecting both single and
|
||||||
multiple files. (Furthermore, the new syntax for injecting multiple files
|
multiple files. (Furthermore, the new syntax for injecting multiple files
|
||||||
should work properly even if only a single file is injected).
|
should work properly even if only a single file is injected).
|
||||||
|
* Users should not be able to use the syntax for injecting single and
|
||||||
|
multiple files in the same custom credential.
|
||||||
* The default `Credential Types` included with Tower in 3.2 should be
|
* The default `Credential Types` included with Tower in 3.2 should be
|
||||||
non-editable/readonly and cannot be deleted by any user.
|
non-editable/readonly and cannot be deleted by any user.
|
||||||
* Stored `Credential` values for _all_ types should be consistent before and
|
* Stored `Credential` values for _all_ types should be consistent before and
|
||||||
|
|||||||
Reference in New Issue
Block a user