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:
Matthew Jones
2013-11-18 09:18:37 -05:00
797 changed files with 46286 additions and 28659 deletions

View File

@@ -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()

View File

@@ -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" %}

View File

@@ -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.

View File

@@ -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" %}

View File

@@ -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)