mirror of
https://github.com/ansible/awx.git
synced 2026-02-26 07:26:03 -03:30
Merge pull request #7629 from ryanpetrello/k8s-creds
add the ability to specify K8S/OCP credentials on a Job Template Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
@@ -4100,7 +4100,8 @@ class JobLaunchSerializer(BaseSerializer):
|
||||
errors.setdefault('credentials', []).append(_(
|
||||
'Cannot assign multiple {} credentials.'
|
||||
).format(cred.unique_hash(display=True)))
|
||||
if cred.credential_type.kind not in ('ssh', 'vault', 'cloud', 'net'):
|
||||
if cred.credential_type.kind not in ('ssh', 'vault', 'cloud',
|
||||
'net', 'kubernetes'):
|
||||
errors.setdefault('credentials', []).append(_(
|
||||
'Cannot assign a Credential of kind `{}`'
|
||||
).format(cred.credential_type.kind))
|
||||
|
||||
@@ -2657,7 +2657,7 @@ class JobTemplateCredentialsList(SubListCreateAttachDetachAPIView):
|
||||
return {"error": _("Cannot assign multiple {credential_type} credentials.").format(
|
||||
credential_type=sub.unique_hash(display=True))}
|
||||
kind = sub.credential_type.kind
|
||||
if kind not in ('ssh', 'vault', 'cloud', 'net'):
|
||||
if kind not in ('ssh', 'vault', 'cloud', 'net', 'kubernetes'):
|
||||
return {'error': _('Cannot assign a Credential of kind `{}`.').format(kind)}
|
||||
|
||||
return super(JobTemplateCredentialsList, self).is_valid_relation(parent, sub, created)
|
||||
|
||||
@@ -101,3 +101,17 @@ def openstack(cred, env, private_data_dir):
|
||||
f.close()
|
||||
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
|
||||
env['OS_CLIENT_CONFIG_FILE'] = path
|
||||
|
||||
|
||||
def kubernetes_bearer_token(cred, env, private_data_dir):
|
||||
env['K8S_AUTH_HOST'] = cred.get_input('host', default='')
|
||||
env['K8S_AUTH_API_KEY'] = cred.get_input('bearer_token', default='')
|
||||
if cred.get_input('verify_ssl') and 'ssl_ca_cert' in cred.inputs:
|
||||
env['K8S_AUTH_VERIFY_SSL'] = 'True'
|
||||
handle, path = tempfile.mkstemp(dir=private_data_dir)
|
||||
with os.fdopen(handle, 'w') as f:
|
||||
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
|
||||
f.write(cred.get_input('ssl_ca_cert'))
|
||||
env['K8S_AUTH_SSL_CA_CERT'] = path
|
||||
else:
|
||||
env['K8S_AUTH_VERIFY_SSL'] = 'False'
|
||||
|
||||
@@ -220,7 +220,7 @@ def test_create_valid_kind(kind, get, post, admin):
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize('kind', ['ssh', 'vault', 'scm', 'insights'])
|
||||
@pytest.mark.parametrize('kind', ['ssh', 'vault', 'scm', 'insights', 'kubernetes'])
|
||||
def test_create_invalid_kind(kind, get, post, admin):
|
||||
response = post(reverse('api:credential_type_list'), {
|
||||
'kind': kind,
|
||||
|
||||
@@ -483,25 +483,26 @@ def test_job_launch_pass_with_prompted_vault_password(machine_credential, vault_
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_job_launch_JT_with_credentials(machine_credential, credential, net_credential, deploy_jobtemplate):
|
||||
def test_job_launch_JT_with_credentials(machine_credential, credential, net_credential, kube_credential, deploy_jobtemplate):
|
||||
deploy_jobtemplate.ask_credential_on_launch = True
|
||||
deploy_jobtemplate.save()
|
||||
|
||||
kv = dict(credentials=[credential.pk, net_credential.pk, machine_credential.pk])
|
||||
kv = dict(credentials=[credential.pk, net_credential.pk, machine_credential.pk, kube_credential.pk])
|
||||
serializer = JobLaunchSerializer(data=kv, context={'template': deploy_jobtemplate})
|
||||
validated = serializer.is_valid()
|
||||
assert validated, serializer.errors
|
||||
|
||||
kv['credentials'] = [credential, net_credential, machine_credential] # convert to internal value
|
||||
kv['credentials'] = [credential, net_credential, machine_credential, kube_credential] # convert to internal value
|
||||
prompted_fields, ignored_fields, errors = deploy_jobtemplate._accept_or_ignore_job_kwargs(
|
||||
_exclude_errors=['required', 'prompts'], **kv)
|
||||
job_obj = deploy_jobtemplate.create_unified_job(**prompted_fields)
|
||||
|
||||
creds = job_obj.credentials.all()
|
||||
assert len(creds) == 3
|
||||
assert len(creds) == 4
|
||||
assert credential in creds
|
||||
assert net_credential in creds
|
||||
assert machine_credential in creds
|
||||
assert kube_credential in creds
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
||||
@@ -1037,6 +1037,43 @@ class TestJobCredentials(TestJobExecution):
|
||||
assert '--vault-id dev@prompt' in ' '.join(args)
|
||||
assert '--vault-id prod@prompt' in ' '.join(args)
|
||||
|
||||
@pytest.mark.parametrize("verify", (True, False))
|
||||
def test_k8s_credential(self, job, private_data_dir, verify):
|
||||
k8s = CredentialType.defaults['kubernetes_bearer_token']()
|
||||
inputs = {
|
||||
'host': 'https://example.org/',
|
||||
'bearer_token': 'token123',
|
||||
}
|
||||
if verify:
|
||||
inputs['verify_ssl'] = True
|
||||
inputs['ssl_ca_cert'] = 'CERTDATA'
|
||||
credential = Credential(
|
||||
pk=1,
|
||||
credential_type=k8s,
|
||||
inputs = inputs,
|
||||
)
|
||||
credential.inputs['bearer_token'] = encrypt_field(credential, 'bearer_token')
|
||||
job.credentials.add(credential)
|
||||
|
||||
env = {}
|
||||
safe_env = {}
|
||||
credential.credential_type.inject_credential(
|
||||
credential, env, safe_env, [], private_data_dir
|
||||
)
|
||||
|
||||
assert env['K8S_AUTH_HOST'] == 'https://example.org/'
|
||||
assert env['K8S_AUTH_API_KEY'] == 'token123'
|
||||
|
||||
if verify:
|
||||
assert env['K8S_AUTH_VERIFY_SSL'] == 'True'
|
||||
cert = open(env['K8S_AUTH_SSL_CA_CERT'], 'r').read()
|
||||
assert cert == 'CERTDATA'
|
||||
else:
|
||||
assert env['K8S_AUTH_VERIFY_SSL'] == 'False'
|
||||
assert 'K8S_AUTH_SSL_CA_CERT' not in env
|
||||
|
||||
assert safe_env['K8S_AUTH_API_KEY'] == tasks.HIDDEN_PASSWORD
|
||||
|
||||
def test_aws_cloud_credential(self, job, private_data_dir):
|
||||
aws = CredentialType.defaults['aws']()
|
||||
credential = Credential(
|
||||
|
||||
@@ -67,6 +67,10 @@
|
||||
&--external:before {
|
||||
content: '\f14c'
|
||||
}
|
||||
|
||||
&--kubernetes:before, &--kubernetes_bearer_token:before {
|
||||
content: '\f0c2';
|
||||
}
|
||||
}
|
||||
|
||||
.TagComponent-button {
|
||||
|
||||
@@ -111,7 +111,7 @@ function multiCredentialModalController(GetBasePath, qs, MultiCredentialService)
|
||||
|
||||
scope.credentialTypes.forEach((credentialType => {
|
||||
if(credentialType.kind
|
||||
.match(/^(machine|cloud|net|ssh|vault)$/)) {
|
||||
.match(/^(machine|cloud|net|ssh|vault|kubernetes)$/)) {
|
||||
scope.displayedCredentialTypes.push(credentialType);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<i class="fa fa-code-fork MultiCredential-tagIcon" ng-switch-when="scm"></i>
|
||||
<i class="fa fa-key MultiCredential-tagIcon" ng-switch-when="ssh"></i>
|
||||
<i class="fa fa-archive MultiCredential-tagIcon" ng-switch-when="vault"></i>
|
||||
<i class="fa fa-cloud MultiCredential-tagIcon" ng-switch-when="kubernetes"></i>
|
||||
</div>
|
||||
<div class="MultiCredential-tag MultiCredential-tag--deletable">
|
||||
<span ng-if="!tag.info" class="MultiCredential-name--label ng-binding">
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<i class="fa fa-code-fork MultiCredential-tagIcon" ng-switch-when="scm"></i>
|
||||
<i class="fa fa-key MultiCredential-tagIcon" ng-switch-when="ssh"></i>
|
||||
<i class="fa fa-archive MultiCredential-tagIcon" ng-switch-when="vault"></i>
|
||||
<i class="fa fa-cloud MultiCredential-tagIcon" ng-switch-when="kubernetes"></i>
|
||||
</div>
|
||||
<div class="MultiCredential-iconContainer" ng-switch="tag.kind" ng-if="!fieldIsDisabled">
|
||||
<i class="fa fa-cloud MultiCredential-tagIcon" ng-switch-when="cloud"></i>
|
||||
@@ -34,6 +35,7 @@
|
||||
<i class="fa fa-code-fork MultiCredential-tagIcon" ng-switch-when="scm"></i>
|
||||
<i class="fa fa-key MultiCredential-tagIcon" ng-switch-when="ssh"></i>
|
||||
<i class="fa fa-archive MultiCredential-tagIcon" ng-switch-when="vault"></i>
|
||||
<i class="fa fa-cloud MultiCredential-tagIcon" ng-switch-when="kubernetes"></i>
|
||||
</div>
|
||||
<div class="MultiCredential-tag"
|
||||
ng-class="{'MultiCredential-tag--deletable': !fieldIsDisabled, 'MultiCredential-tag--disabled': fieldIsDisabled}">
|
||||
|
||||
@@ -55,7 +55,7 @@ export default [ 'ProcessErrors', 'CredentialTypeModel', 'TemplatesStrings', '$f
|
||||
vm.promptDataClone.prompts.credentials.credentialTypeOptions = [];
|
||||
response.data.results.forEach((credentialTypeRow => {
|
||||
vm.promptDataClone.prompts.credentials.credentialTypes[credentialTypeRow.id] = credentialTypeRow.kind;
|
||||
if(credentialTypeRow.kind.match(/^(cloud|net|ssh|vault)$/)) {
|
||||
if(credentialTypeRow.kind.match(/^(cloud|net|ssh|vault|kubernetes)$/)) {
|
||||
if(credentialTypeRow.kind === 'ssh') {
|
||||
vm.promptDataClone.prompts.credentials.credentialKind = credentialTypeRow.id.toString();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ class CredentialTypes extends Base {
|
||||
}
|
||||
|
||||
async loadAllTypes(
|
||||
acceptableKinds = ['machine', 'cloud', 'net', 'ssh', 'vault']
|
||||
acceptableKinds = ['machine', 'cloud', 'net', 'ssh', 'vault', 'kubernetes']
|
||||
) {
|
||||
const pageSize = 200;
|
||||
// The number of credential types a user can have is unlimited. In practice, it is unlikely for
|
||||
|
||||
Reference in New Issue
Block a user