remove /api/v1 and deprecated credential fields

This commit is contained in:
Ryan Petrello
2019-03-12 17:12:16 -04:00
parent 176f8632e5
commit 6da445f7c0
43 changed files with 271 additions and 2211 deletions

View File

@@ -16,7 +16,7 @@ from awx.main.models.organization import ( # noqa
Organization, Profile, Team, UserSessionMembership
)
from awx.main.models.credential import ( # noqa
Credential, CredentialType, CredentialInputSource, ManagedCredentialType, V1Credential, build_safe_env
Credential, CredentialType, CredentialInputSource, ManagedCredentialType, build_safe_env
)
from awx.main.models.projects import Project, ProjectUpdate # noqa
from awx.main.models.inventory import ( # noqa
@@ -174,9 +174,6 @@ User.add_to_class('is_in_enterprise_category', user_is_in_enterprise_category)
def o_auth2_application_get_absolute_url(self, request=None):
# this page does not exist in v1
if request.version == 'v1':
return reverse('api:o_auth2_application_detail', kwargs={'pk': self.pk}) # use default version
return reverse('api:o_auth2_application_detail', kwargs={'pk': self.pk}, request=request)
@@ -184,9 +181,6 @@ OAuth2Application.add_to_class('get_absolute_url', o_auth2_application_get_absol
def o_auth2_token_get_absolute_url(self, request=None):
# this page does not exist in v1
if request.version == 'v1':
return reverse('api:o_auth2_token_detail', kwargs={'pk': self.pk}) # use default version
return reverse('api:o_auth2_token_detail', kwargs={'pk': self.pk}, request=request)

View File

@@ -42,7 +42,7 @@ from awx.main.models.rbac import (
from awx.main.utils import encrypt_field
from . import injectors as builtin_injectors
__all__ = ['Credential', 'CredentialType', 'CredentialInputSource', 'V1Credential', 'build_safe_env']
__all__ = ['Credential', 'CredentialType', 'CredentialInputSource', 'build_safe_env']
logger = logging.getLogger('awx.main.models.credential')
credential_plugins = dict(
@@ -73,164 +73,6 @@ def build_safe_env(env):
return safe_env
class V1Credential(object):
#
# API v1 backwards compat; as long as we continue to support the
# /api/v1/credentials/ endpoint, we'll keep these definitions around.
# The credential serializers are smart enough to detect the request
# version and use *these* fields for constructing the serializer if the URL
# starts with /api/v1/
#
PASSWORD_FIELDS = ('password', 'security_token', 'ssh_key_data',
'ssh_key_unlock', 'become_password',
'vault_password', 'secret', 'authorize_password')
KIND_CHOICES = [
('ssh', 'Machine'),
('net', 'Network'),
('scm', 'Source Control'),
('aws', 'Amazon Web Services'),
('vmware', 'VMware vCenter'),
('satellite6', 'Red Hat Satellite 6'),
('cloudforms', 'Red Hat CloudForms'),
('gce', 'Google Compute Engine'),
('azure_rm', 'Microsoft Azure Resource Manager'),
('openstack', 'OpenStack'),
('rhv', 'Red Hat Virtualization'),
('insights', 'Insights'),
('tower', 'Ansible Tower'),
]
FIELDS = {
'kind': models.CharField(
max_length=32,
choices=[
(kind[0], _(kind[1]))
for kind in KIND_CHOICES
],
default='ssh',
),
'cloud': models.BooleanField(
default=False,
editable=False,
),
'host': models.CharField(
blank=True,
default='',
max_length=1024,
verbose_name=_('Host'),
help_text=_('The hostname or IP address to use.'),
),
'username': models.CharField(
blank=True,
default='',
max_length=1024,
verbose_name=_('Username'),
help_text=_('Username for this credential.'),
),
'password': models.CharField(
blank=True,
default='',
max_length=1024,
verbose_name=_('Password'),
help_text=_('Password for this credential (or "ASK" to prompt the '
'user for machine credentials).'),
),
'security_token': models.CharField(
blank=True,
default='',
max_length=1024,
verbose_name=_('Security Token'),
help_text=_('Security Token for this credential'),
),
'project': models.CharField(
blank=True,
default='',
max_length=100,
verbose_name=_('Project'),
help_text=_('The identifier for the project.'),
),
'domain': models.CharField(
blank=True,
default='',
max_length=100,
verbose_name=_('Domain'),
help_text=_('The identifier for the domain.'),
),
'ssh_key_data': models.TextField(
blank=True,
default='',
verbose_name=_('SSH private key'),
help_text=_('RSA or DSA private key to be used instead of password.'),
),
'ssh_key_unlock': models.CharField(
max_length=1024,
blank=True,
default='',
verbose_name=_('SSH key unlock'),
help_text=_('Passphrase to unlock SSH private key if encrypted (or '
'"ASK" to prompt the user for machine credentials).'),
),
'become_method': models.CharField(
max_length=32,
blank=True,
default='',
help_text=_('Privilege escalation method.')
),
'become_username': models.CharField(
max_length=1024,
blank=True,
default='',
help_text=_('Privilege escalation username.'),
),
'become_password': models.CharField(
max_length=1024,
blank=True,
default='',
help_text=_('Password for privilege escalation method.')
),
'vault_password': models.CharField(
max_length=1024,
blank=True,
default='',
help_text=_('Vault password (or "ASK" to prompt the user).'),
),
'authorize': models.BooleanField(
default=False,
help_text=_('Whether to use the authorize mechanism.'),
),
'authorize_password': models.CharField(
max_length=1024,
blank=True,
default='',
help_text=_('Password used by the authorize mechanism.'),
),
'client': models.CharField(
max_length=128,
blank=True,
default='',
help_text=_('Client Id or Application Id for the credential'),
),
'secret': models.CharField(
max_length=1024,
blank=True,
default='',
help_text=_('Secret Token for this credential'),
),
'subscription': models.CharField(
max_length=1024,
blank=True,
default='',
help_text=_('Subscription identifier for this credential'),
),
'tenant': models.CharField(
max_length=1024,
blank=True,
default='',
help_text=_('Tenant identifier for this credential'),
)
}
class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
'''
A credential contains information about how to talk to a remote resource
@@ -286,34 +128,9 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
'admin_role',
])
def __getattr__(self, item):
if item != 'inputs':
if item in V1Credential.FIELDS:
return self.inputs.get(item, V1Credential.FIELDS[item].default)
elif item in self.inputs:
return self.inputs[item]
raise AttributeError(item)
def __setattr__(self, item, value):
if item in V1Credential.FIELDS and item in self.credential_type.defined_fields:
if value:
self.inputs[item] = value
elif item in self.inputs:
del self.inputs[item]
return
super(Credential, self).__setattr__(item, value)
@property
def kind(self):
# TODO 3.3: remove the need for this helper property by removing its
# usage throughout the codebase
type_ = self.credential_type
if type_.kind != 'cloud':
return type_.kind
for field in V1Credential.KIND_CHOICES:
kind, name = field
if name == type_.name:
return kind
return self.credential_type.namespace
@property
def cloud(self):
@@ -330,7 +147,7 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
#
@property
def needs_ssh_password(self):
return self.credential_type.kind == 'ssh' and self.password == 'ASK'
return self.credential_type.kind == 'ssh' and self.inputs.get('password') == 'ASK'
@property
def has_encrypted_ssh_key_data(self):
@@ -350,17 +167,17 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
@property
def needs_ssh_key_unlock(self):
if self.credential_type.kind == 'ssh' and self.ssh_key_unlock in ('ASK', ''):
if self.credential_type.kind == 'ssh' and self.inputs.get('ssh_key_unlock') in ('ASK', ''):
return self.has_encrypted_ssh_key_data
return False
@property
def needs_become_password(self):
return self.credential_type.kind == 'ssh' and self.become_password == 'ASK'
return self.credential_type.kind == 'ssh' and self.inputs.get('become_password') == 'ASK'
@property
def needs_vault_password(self):
return self.credential_type.kind == 'vault' and self.vault_password == 'ASK'
return self.credential_type.kind == 'vault' and self.inputs.get('vault_password') == 'ASK'
@property
def passwords_needed(self):
@@ -396,6 +213,10 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
super(Credential, self).save(*args, **kwargs)
def mark_field_for_save(self, update_fields, field):
if 'inputs' not in update_fields:
update_fields.append('inputs')
def encrypt_field(self, field, ask):
if field not in self.inputs:
return None
@@ -405,13 +226,6 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
elif field in self.inputs:
del self.inputs[field]
def mark_field_for_save(self, update_fields, field):
if field in self.credential_type.secret_fields:
# If we've encrypted a v1 field, we actually want to persist
# self.inputs
field = 'inputs'
super(Credential, self).mark_field_for_save(update_fields, field)
def display_inputs(self):
field_val = self.inputs.copy()
for k, v in field_val.items():
@@ -429,7 +243,7 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
type_alias = self.credential_type.name
else:
type_alias = self.credential_type_id
if self.kind == 'vault' and self.has_input('vault_id'):
if self.credential_type.kind == 'vault' and self.has_input('vault_id'):
if display:
fmt_str = '{} (id={})'
else:
@@ -456,7 +270,7 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
:param field_name(str): The name of the input field.
:param default(optional[str]): A default return value to use.
"""
if self.kind != 'external' and field_name in self.dynamic_input_fields:
if self.credential_type.kind != 'external' and field_name in self.dynamic_input_fields:
return self._get_dynamic_input(field_name)
if field_name in self.credential_type.secret_fields:
try:
@@ -552,15 +366,8 @@ class CredentialType(CommonModelNameNotUnique):
return instance
def get_absolute_url(self, request=None):
# Page does not exist in API v1
if request.version == 'v1':
return reverse('api:credential_type_detail', kwargs={'pk': self.pk})
return reverse('api:credential_type_detail', kwargs={'pk': self.pk}, request=request)
@property
def unique_by_kind(self):
return self.kind != 'cloud'
@property
def defined_fields(self):
return [field.get('id') for field in self.inputs.get('fields', [])]
@@ -629,29 +436,6 @@ class CredentialType(CommonModelNameNotUnique):
inputs=plugin.inputs
)
@classmethod
def from_v1_kind(cls, kind, data={}):
match = None
kind = kind or 'ssh'
kind_choices = dict(V1Credential.KIND_CHOICES)
requirements = {}
if kind == 'ssh':
if data.get('vault_password'):
requirements['kind'] = 'vault'
else:
requirements['kind'] = 'ssh'
elif kind in ('net', 'scm', 'insights'):
requirements['kind'] = kind
elif kind in kind_choices:
requirements.update(dict(
kind='cloud',
name=kind_choices[kind]
))
if requirements:
requirements['managed_by_tower'] = True
match = cls.objects.filter(**requirements)[:1].get()
return match
def inject_credential(self, credential, env, safe_env, args, private_data_dir):
"""
Inject credential data into the environment variables and arguments
@@ -678,9 +462,11 @@ class CredentialType(CommonModelNameNotUnique):
files)
"""
if not self.injectors:
if self.managed_by_tower and credential.kind in dir(builtin_injectors):
if self.managed_by_tower and credential.credential_type.namespace in dir(builtin_injectors):
injected_env = {}
getattr(builtin_injectors, credential.kind)(credential, injected_env, private_data_dir)
getattr(builtin_injectors, credential.credential_type.namespace)(
credential, injected_env, private_data_dir
)
env.update(injected_env)
safe_env.update(build_safe_env(injected_env))
return
@@ -1335,12 +1121,12 @@ class CredentialInputSource(PrimordialModel):
)
def clean_target_credential(self):
if self.target_credential.kind == 'external':
if self.target_credential.credential_type.kind == 'external':
raise ValidationError(_('Target must be a non-external credential'))
return self.target_credential
def clean_source_credential(self):
if self.source_credential.kind != 'external':
if self.source_credential.credential_type.kind != 'external':
raise ValidationError(_('Source must be an external credential'))
return self.source_credential

View File

@@ -18,7 +18,7 @@ from django.db import models
from django.utils.encoding import smart_str
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError, FieldDoesNotExist
from django.core.exceptions import FieldDoesNotExist
# REST Framework
from rest_framework.exceptions import ParseError
@@ -152,21 +152,9 @@ class JobOptions(BaseModel):
extra_vars_dict = VarsDictProperty('extra_vars', True)
def clean_credential(self):
cred = self.credential
if cred and cred.kind != 'ssh':
raise ValidationError(
_('You must provide an SSH credential.'),
)
return cred
def clean_vault_credential(self):
cred = self.vault_credential
if cred and cred.kind != 'vault':
raise ValidationError(
_('You must provide a Vault credential.'),
)
return cred
@property
def machine_credential(self):
return self.credentials.filter(credential_type__kind='ssh').first()
@property
def network_credentials(self):
@@ -180,41 +168,6 @@ class JobOptions(BaseModel):
def vault_credentials(self):
return list(self.credentials.filter(credential_type__kind='vault'))
@property
def credential(self):
cred = self.get_deprecated_credential('ssh')
if cred is not None:
return cred.pk
@property
def vault_credential(self):
cred = self.get_deprecated_credential('vault')
if cred is not None:
return cred.pk
def get_deprecated_credential(self, kind):
for cred in self.credentials.all():
if cred.credential_type.kind == kind:
return cred
else:
return None
# TODO: remove when API v1 is removed
@property
def cloud_credential(self):
try:
return self.cloud_credentials[-1].pk
except IndexError:
return None
# TODO: remove when API v1 is removed
@property
def network_credential(self):
try:
return self.network_credentials[-1].pk
except IndexError:
return None
@property
def passwords_needed_to_start(self):
'''Return list of password field names needed to start the job.'''
@@ -707,7 +660,7 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskMana
data.update(dict(inventory=self.inventory.name if self.inventory else None,
project=self.project.name if self.project else None,
playbook=self.playbook,
credential=getattr(self.get_deprecated_credential('ssh'), 'name', None),
credential=getattr(self.machine_credential, 'name', None),
limit=self.limit,
extra_vars=self.display_extra_vars(),
hosts=all_hosts))