mirror of
https://github.com/ansible/awx.git
synced 2026-03-21 19:07:39 -02:30
Merge remote-tracking branch 'origin/master' into auditlog
* origin/master: AC-637 Credential now requires scm_key_unlock when saving encrypted ssh_key_data. AC-626 Removed support for prompting for password and ssh_key_unlock for scm/cloud credentials. AC-613 Change rackspace to rax for inventory source field value. AC-613 Change rackspace to rax for inventory source field value. AC-624 Fix options docs for project update view. AC-632 Fix escaping for ansible-playbook command line when also using ssh-agent. Update CONTRIBUTING.md AC-630 Expose cloud_credentials field for job template and job. AC-641 Added pattern to respond to key unlock prompt for project update. Updated all vendored third-party packages. AC-636 Fix existing projects with scm_type=null to always use empty string. Update validation and tests to ensure None gets automatically coerced to an empty string on saving a project. AC-633 js error fixed. AC-633 fixed a sort of unrelated js error. The capitalize filter directive attempted to act on a 'null' input error. Added a test to ignore empty/null input. AC-633 Fixed 'hast' typo. AC-617 changed callback generation icon to a magic wand, which will hopefully satiate jlaska. AC-627 Fixed password/ssh_password collision in Credentials.js form. This was also fixed in auditlog branch. AC-628 applied credential changes made in add controller to edit controller so that credential/cloud_credential lookups display context-aware credential lists. Moved credentials in tab order. It now follows teams and precedes projects. Based on a suggestion from jlaska. AC-609 Fixed issue with help button not displaying correctly on 'select' pages where user can pick an existing object (i.e. users, credentials, etc) to add to a parent object. Conflicts: awx/api/serializers.py awx/main/migrations/0025_v14_changes.py
This commit is contained in:
@@ -96,6 +96,7 @@ class ChoiceField(fields.ChoiceField):
|
||||
# ModelSerializer.
|
||||
serializers.ChoiceField = ChoiceField
|
||||
|
||||
|
||||
class BaseSerializer(serializers.ModelSerializer):
|
||||
|
||||
# add the URL and related resources
|
||||
@@ -187,10 +188,6 @@ class BaseSerializer(serializers.ModelSerializer):
|
||||
else:
|
||||
return obj.active
|
||||
|
||||
def validate_description(self, attrs, source):
|
||||
# Description should always be empty string, never null.
|
||||
attrs[source] = attrs.get(source, None) or ''
|
||||
return attrs
|
||||
|
||||
class UserSerializer(BaseSerializer):
|
||||
|
||||
@@ -278,6 +275,7 @@ class UserSerializer(BaseSerializer):
|
||||
def validate_is_superuser(self, attrs, source):
|
||||
return self._validate_ldap_managed_field(attrs, source)
|
||||
|
||||
|
||||
class OrganizationSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
@@ -299,6 +297,7 @@ class OrganizationSerializer(BaseSerializer):
|
||||
))
|
||||
return res
|
||||
|
||||
|
||||
class ProjectSerializer(BaseSerializer):
|
||||
|
||||
playbooks = serializers.Field(source='playbooks', help_text='Array of playbooks available within this project.')
|
||||
@@ -334,27 +333,32 @@ class ProjectSerializer(BaseSerializer):
|
||||
args=(obj.last_update.pk,))
|
||||
return res
|
||||
|
||||
def _get_scm_type(self, attrs, source=None):
|
||||
if self.object:
|
||||
return attrs.get(source or 'scm_type', self.object.scm_type) or u''
|
||||
else:
|
||||
return attrs.get(source or 'scm_type', u'') or u''
|
||||
|
||||
def validate_local_path(self, attrs, source):
|
||||
# Don't allow assigning a local_path used by another project.
|
||||
# Don't allow assigning a local_path when scm_type is set.
|
||||
valid_local_paths = Project.get_local_path_choices()
|
||||
if self.object:
|
||||
scm_type = attrs.get('scm_type', self.object.scm_type)
|
||||
if not scm_type:
|
||||
valid_local_paths.append(self.object.local_path)
|
||||
else:
|
||||
scm_type = attrs.get('scm_type', '')
|
||||
scm_type = self._get_scm_type(attrs)
|
||||
if self.object and not scm_type:
|
||||
valid_local_paths.append(self.object.local_path)
|
||||
if scm_type:
|
||||
attrs.pop(source, None)
|
||||
if source in attrs and attrs[source] not in valid_local_paths:
|
||||
raise serializers.ValidationError('Invalid path choice')
|
||||
return attrs
|
||||
|
||||
def validate_scm_type(self, attrs, source):
|
||||
scm_type = self._get_scm_type(attrs, source)
|
||||
attrs[source] = scm_type
|
||||
return attrs
|
||||
|
||||
def validate_scm_url(self, attrs, source):
|
||||
if self.object:
|
||||
scm_type = attrs.get('scm_type', self.object.scm_type) or ''
|
||||
else:
|
||||
scm_type = attrs.get('scm_type', '') or ''
|
||||
scm_type = self._get_scm_type(attrs)
|
||||
scm_url = unicode(attrs.get(source, None) or '')
|
||||
if not scm_type:
|
||||
return attrs
|
||||
@@ -413,6 +417,7 @@ class ProjectSerializer(BaseSerializer):
|
||||
|
||||
# FIXME: Validate combination of SCM URL and credential!
|
||||
|
||||
|
||||
class ProjectPlaybooksSerializer(ProjectSerializer):
|
||||
|
||||
class Meta:
|
||||
@@ -423,6 +428,7 @@ class ProjectPlaybooksSerializer(ProjectSerializer):
|
||||
ret = super(ProjectPlaybooksSerializer, self).to_native(obj)
|
||||
return ret.get('playbooks', [])
|
||||
|
||||
|
||||
class ProjectUpdateSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
@@ -441,6 +447,7 @@ class ProjectUpdateSerializer(BaseSerializer):
|
||||
))
|
||||
return res
|
||||
|
||||
|
||||
class BaseSerializerWithVariables(BaseSerializer):
|
||||
|
||||
def validate_variables(self, attrs, source):
|
||||
@@ -453,6 +460,7 @@ class BaseSerializerWithVariables(BaseSerializer):
|
||||
raise serializers.ValidationError('Must be valid JSON or YAML')
|
||||
return attrs
|
||||
|
||||
|
||||
class InventorySerializer(BaseSerializerWithVariables):
|
||||
|
||||
class Meta:
|
||||
@@ -481,6 +489,7 @@ class InventorySerializer(BaseSerializerWithVariables):
|
||||
))
|
||||
return res
|
||||
|
||||
|
||||
class HostSerializer(BaseSerializerWithVariables):
|
||||
|
||||
class Meta:
|
||||
@@ -611,6 +620,7 @@ class GroupSerializer(BaseSerializerWithVariables):
|
||||
raise serializers.ValidationError('Invalid group name')
|
||||
return attrs
|
||||
|
||||
|
||||
class GroupTreeSerializer(GroupSerializer):
|
||||
|
||||
children = serializers.SerializerMethodField('get_children')
|
||||
@@ -628,6 +638,7 @@ class GroupTreeSerializer(GroupSerializer):
|
||||
children_qs = obj.children.filter(active=True)
|
||||
return GroupTreeSerializer(children_qs, many=True).data
|
||||
|
||||
|
||||
class BaseVariableDataSerializer(BaseSerializer):
|
||||
|
||||
def to_native(self, obj):
|
||||
@@ -643,24 +654,28 @@ class BaseVariableDataSerializer(BaseSerializer):
|
||||
data = {'variables': json.dumps(data)}
|
||||
return super(BaseVariableDataSerializer, self).from_native(data, files)
|
||||
|
||||
|
||||
class InventoryVariableDataSerializer(BaseVariableDataSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Inventory
|
||||
fields = ('variables',)
|
||||
|
||||
|
||||
class HostVariableDataSerializer(BaseVariableDataSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Host
|
||||
fields = ('variables',)
|
||||
|
||||
|
||||
class GroupVariableDataSerializer(BaseVariableDataSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = ('variables',)
|
||||
|
||||
|
||||
class InventorySourceSerializer(BaseSerializer):
|
||||
|
||||
#source_password = serializers.WritableField(required=False, default='')
|
||||
@@ -730,6 +745,7 @@ class InventorySourceSerializer(BaseSerializer):
|
||||
# FIXME
|
||||
return attrs
|
||||
|
||||
|
||||
class InventoryUpdateSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
@@ -749,6 +765,7 @@ class InventoryUpdateSerializer(BaseSerializer):
|
||||
))
|
||||
return res
|
||||
|
||||
|
||||
class TeamSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
@@ -768,6 +785,7 @@ class TeamSerializer(BaseSerializer):
|
||||
))
|
||||
return res
|
||||
|
||||
|
||||
class PermissionSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
@@ -789,6 +807,7 @@ class PermissionSerializer(BaseSerializer):
|
||||
res['inventory'] = reverse('api:inventory_detail', args=(obj.inventory.pk,))
|
||||
return res
|
||||
|
||||
|
||||
def validate(self, attrs):
|
||||
# Can only set either user or team.
|
||||
if attrs['user'] and attrs['team']:
|
||||
@@ -804,6 +823,7 @@ class PermissionSerializer(BaseSerializer):
|
||||
'assigning deployment permissions')
|
||||
return attrs
|
||||
|
||||
|
||||
class CredentialSerializer(BaseSerializer):
|
||||
|
||||
# FIXME: may want to make some of these filtered based on user accessing
|
||||
@@ -845,13 +865,15 @@ class CredentialSerializer(BaseSerializer):
|
||||
res['team'] = reverse('api:team_detail', args=(obj.team.pk,))
|
||||
return res
|
||||
|
||||
|
||||
class JobTemplateSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = JobTemplate
|
||||
fields = BASE_FIELDS + ('job_type', 'inventory', 'project', 'playbook',
|
||||
'credential', 'forks', 'limit', 'verbosity',
|
||||
'extra_vars', 'job_tags', 'host_config_key')
|
||||
'credential', 'cloud_credential', 'forks',
|
||||
'limit', 'verbosity', 'extra_vars', 'job_tags',
|
||||
'host_config_key')
|
||||
|
||||
def get_related(self, obj):
|
||||
if obj is None:
|
||||
@@ -864,6 +886,9 @@ class JobTemplateSerializer(BaseSerializer):
|
||||
))
|
||||
if obj.credential:
|
||||
res['credential'] = reverse('api:credential_detail', args=(obj.credential.pk,))
|
||||
if obj.cloud_credential:
|
||||
res['cloud_credential'] = reverse('api:credential_detail',
|
||||
args=(obj.cloud_credential.pk,))
|
||||
if obj.host_config_key:
|
||||
res['callback'] = reverse('api:job_template_callback', args=(obj.pk,))
|
||||
return res
|
||||
@@ -875,6 +900,7 @@ class JobTemplateSerializer(BaseSerializer):
|
||||
raise serializers.ValidationError('Playbook not found for project')
|
||||
return attrs
|
||||
|
||||
|
||||
class JobSerializer(BaseSerializer):
|
||||
|
||||
passwords_needed_to_start = serializers.Field(source='passwords_needed_to_start')
|
||||
@@ -883,7 +909,7 @@ class JobSerializer(BaseSerializer):
|
||||
model = Job
|
||||
fields = ('id', 'url', 'related', 'summary_fields', 'created',
|
||||
'modified', 'job_template', 'job_type', 'inventory',
|
||||
'project', 'playbook', 'credential',
|
||||
'project', 'playbook', 'credential', 'cloud_credential',
|
||||
'forks', 'limit', 'verbosity', 'extra_vars',
|
||||
'job_tags', 'launch_type', 'status', 'failed',
|
||||
'result_stdout', 'result_traceback',
|
||||
@@ -903,6 +929,9 @@ class JobSerializer(BaseSerializer):
|
||||
))
|
||||
if obj.job_template:
|
||||
res['job_template'] = reverse('api:job_template_detail', args=(obj.job_template.pk,))
|
||||
if obj.cloud_credential:
|
||||
res['cloud_credential'] = reverse('api:credential_detail',
|
||||
args=(obj.cloud_credential.pk,))
|
||||
if obj.can_start or True:
|
||||
res['start'] = reverse('api:job_start', args=(obj.pk,))
|
||||
if obj.can_cancel or True:
|
||||
@@ -925,6 +954,8 @@ class JobSerializer(BaseSerializer):
|
||||
data.setdefault('playbook', job_template.playbook)
|
||||
if job_template.credential:
|
||||
data.setdefault('credential', job_template.credential.pk)
|
||||
if job_template.cloud_credential:
|
||||
data.setdefault('cloud_credential', job_template.cloud_credential.pk)
|
||||
data.setdefault('forks', job_template.forks)
|
||||
data.setdefault('limit', job_template.limit)
|
||||
data.setdefault('verbosity', job_template.verbosity)
|
||||
@@ -932,6 +963,7 @@ class JobSerializer(BaseSerializer):
|
||||
data.setdefault('job_tags', job_template.job_tags)
|
||||
return super(JobSerializer, self).from_native(data, files)
|
||||
|
||||
|
||||
class JobHostSummarySerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
@@ -961,6 +993,7 @@ class JobHostSummarySerializer(BaseSerializer):
|
||||
pass
|
||||
return d
|
||||
|
||||
|
||||
class JobEventSerializer(BaseSerializer):
|
||||
|
||||
event_display = serializers.Field(source='get_event_display2')
|
||||
@@ -1055,7 +1088,6 @@ class ActivityStreamSerializer(BaseSerializer):
|
||||
pass
|
||||
return d
|
||||
|
||||
|
||||
class AuthTokenSerializer(serializers.Serializer):
|
||||
|
||||
username = serializers.CharField()
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
# Update Inventory Source
|
||||
|
||||
Make a GET request to this resource to determine if the group can be updated
|
||||
from its inventory source and whether any passwords are required for the
|
||||
update. The response will include the following fields:
|
||||
from its inventory source. The response will include the following field:
|
||||
|
||||
* `can_start`: Flag indicating if this job can be started (boolean, read-only)
|
||||
* `passwords_needed_to_update`: Password names required to update from the
|
||||
inventory source (array, read-only)
|
||||
* `can_update`: Flag indicating if this inventory source can be updated
|
||||
(boolean, read-only)
|
||||
|
||||
Make a POST request to this resource to update the inventory source. If any
|
||||
passwords are required, they must be passed via POST data.
|
||||
|
||||
If successful, the response status code will be 202. If any required passwords
|
||||
are not provided, a 400 status code will be returned. If the inventory source
|
||||
is not defined or cannot be updated, a 405 status code will be returned.
|
||||
Make a POST request to this resource to update the inventory source. If
|
||||
successful, the response status code will be 202. If the inventory source is
|
||||
not defined or cannot be updated, a 405 status code will be returned.
|
||||
|
||||
{% include "api/_new_in_awx.md" %}
|
||||
|
||||
@@ -5,7 +5,8 @@ whether any passwords are required to start the job. The response will include
|
||||
the following fields:
|
||||
|
||||
* `can_start`: Flag indicating if this job can be started (boolean, read-only)
|
||||
* `passwords_needed_to_start`: Password names required to start the job (array, read-only)
|
||||
* `passwords_needed_to_start`: Password names required to start the job (array,
|
||||
read-only)
|
||||
|
||||
Make a POST request to this resource to start the job. If any passwords are
|
||||
required, they must be passed via POST data.
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
# Update Project
|
||||
|
||||
Make a GET request to this resource to determine if the project can be updated
|
||||
from its SCM source and whether any passwords are required for the update. The
|
||||
response will include the following fields:
|
||||
from its SCM source. The response will include the following field:
|
||||
|
||||
* `can_start`: Flag indicating if this job can be started (boolean, read-only)
|
||||
* `passwords_needed_to_update`: Password names required to update the project
|
||||
(array, read-only)
|
||||
* `can_update`: Flag indicating if this project can be updated (boolean,
|
||||
read-only)
|
||||
|
||||
Make a POST request to this resource to update the project. If any passwords
|
||||
are required, they must be passed via POST data.
|
||||
|
||||
If successful, the response status code will be 202. If any required passwords
|
||||
are not provided, a 400 status code will be returned. If the project cannot be
|
||||
updated, a 405 status code will be returned.
|
||||
Make a POST request to this resource to update the project. If the project
|
||||
cannot be updated, a 405 status code will be returned.
|
||||
|
||||
{% include "api/_new_in_awx.md" %}
|
||||
|
||||
@@ -300,8 +300,6 @@ class ProjectUpdateView(GenericAPIView):
|
||||
data = dict(
|
||||
can_update=obj.can_update,
|
||||
)
|
||||
if obj.scm_type:
|
||||
data['passwords_needed_to_update'] = obj.scm_passwords_needed
|
||||
return Response(data)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
@@ -309,8 +307,7 @@ class ProjectUpdateView(GenericAPIView):
|
||||
if obj.can_update:
|
||||
project_update = obj.update(**request.DATA)
|
||||
if not project_update:
|
||||
data = dict(passwords_needed_to_update=obj.scm_passwords_needed)
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response({}, status=status.HTTP_400_BAD_REQUEST)
|
||||
else:
|
||||
headers = {'Location': project_update.get_absolute_url()}
|
||||
return Response(status=status.HTTP_202_ACCEPTED, headers=headers)
|
||||
|
||||
Reference in New Issue
Block a user