Make the bulk endpoint templates work in API browser

Various fixes

- Don't skip checking resource RBAC permissions for admins
Necessary to handle bad input, e.g. providing a
unified_job_template id that doesn't exit

- In awxkit, only "walk" if we get 'url' in the result

- Bulk host create should return url pointing to inventory,
not inventory/hosts

dont do org check for superuser
This commit is contained in:
Alan Rominger
2023-02-17 09:39:39 -05:00
committed by Elijah DeLee
parent 9358d59f20
commit 7cb16ef91d
5 changed files with 71 additions and 76 deletions

View File

@@ -2077,7 +2077,7 @@ class BulkHostCreateSerializer(serializers.Serializer):
item['url'] = reverse('api:host_detail', kwargs={'pk': r.id}) item['url'] = reverse('api:host_detail', kwargs={'pk': r.id})
item['inventory'] = reverse('api:inventory_detail', kwargs={'pk': validated_data['inventory'].id}) item['inventory'] = reverse('api:inventory_detail', kwargs={'pk': validated_data['inventory'].id})
host_data.append(item) host_data.append(item)
return_data['url'] = reverse('api:inventory_hosts_list', kwargs={'pk': validated_data['inventory'].id}) return_data['url'] = reverse('api:inventory_detail', kwargs={'pk': validated_data['inventory'].id})
return_data['hosts'] = host_data return_data['hosts'] = host_data
return return_data return return_data
@@ -4640,21 +4640,19 @@ class BulkJobLaunchSerializer(BaseSerializer):
if 'instance_groups' in job: if 'instance_groups' in job:
[requested_use_instance_groups.add(instance_group) for instance_group in job['instance_groups']] [requested_use_instance_groups.add(instance_group) for instance_group in job['instance_groups']]
# If we are not a superuser, check we have permissions self.check_organization_permission(attrs, request)
if request and not request.user.is_superuser: self.check_unified_job_permission(request, requested_ujts)
self.check_organization_permission(attrs, request) if requested_use_inventories or 'inventory' in attrs:
self.check_unified_job_permission(request, requested_ujts) self.check_inventory_permission(attrs, request, requested_use_inventories)
if requested_use_inventories or 'inventory' in attrs:
self.check_inventory_permission(attrs, request, requested_use_inventories)
if requested_use_labels: if requested_use_labels:
self.check_label_permission(requested_use_labels) self.check_label_permission(requested_use_labels)
if requested_use_instance_groups: if requested_use_instance_groups:
self.check_instance_group_permission(request, requested_use_instance_groups) self.check_instance_group_permission(request, requested_use_instance_groups)
if requested_use_execution_environments: if requested_use_execution_environments:
self.check_instance_group_permission(request, requested_use_instance_groups) self.check_instance_group_permission(request, requested_use_instance_groups)
# all of the unified job templates and related items have now been checked, we can now grab the objects from the DB # all of the unified job templates and related items have now been checked, we can now grab the objects from the DB
jobs_object = self.get_objectified_jobs( jobs_object = self.get_objectified_jobs(
@@ -4688,7 +4686,6 @@ class BulkJobLaunchSerializer(BaseSerializer):
nodes = [] nodes = []
node_m2m_objects = {} node_m2m_objects = {}
node_m2m_object_types_to_through_model = { node_m2m_object_types_to_through_model = {
'credentials': WorkflowJobNode.credentials.through, 'credentials': WorkflowJobNode.credentials.through,
'labels': WorkflowJobNode.labels.through, 'labels': WorkflowJobNode.labels.through,
'instance_groups': WorkflowJobNode.instance_groups.through, 'instance_groups': WorkflowJobNode.instance_groups.through,
@@ -4759,21 +4756,22 @@ class BulkJobLaunchSerializer(BaseSerializer):
# validate Organization # validate Organization
# - If the orgs is not set, set it to the org of the launching user # - If the orgs is not set, set it to the org of the launching user
# - If the user is part of multiple orgs, throw a validation error saying user is part of multiple orgs, please provide one # - If the user is part of multiple orgs, throw a validation error saying user is part of multiple orgs, please provide one
if 'organization' not in attrs or attrs['organization'] == None or attrs['organization'] == '': if not request.user.is_superuser:
if Organization.accessible_pk_qs(request.user, 'read_role').count() == 1: if 'organization' not in attrs or attrs['organization'] == None or attrs['organization'] == '':
for tup in Organization.accessible_pk_qs(request.user, 'read_role').all(): if Organization.accessible_pk_qs(request.user, 'read_role').count() == 1:
attrs['organization'] = Organization.objects.filter(id__in=str(tup[0])).first() for tup in Organization.accessible_pk_qs(request.user, 'read_role').all():
elif Organization.accessible_pk_qs(request.user, 'read_role').count() > 1: attrs['organization'] = Organization.objects.filter(id__in=str(tup[0])).first()
raise serializers.ValidationError("User has permission to multiple Organizations, please set one of them in the request") elif Organization.accessible_pk_qs(request.user, 'read_role').count() > 1:
raise serializers.ValidationError("User has permission to multiple Organizations, please set one of them in the request")
else:
raise serializers.ValidationError("User not part of any organization, please assign an organization to assign to the bulk job")
else: else:
raise serializers.ValidationError("User not part of any organization, please assign an organization to assign to the bulk job") allowed_orgs = set()
else: requested_org = attrs['organization']
allowed_orgs = set() if request and not request.user.is_superuser:
requested_org = attrs['organization'] [allowed_orgs.add(tup[0]) for tup in Organization.accessible_pk_qs(request.user, 'read_role').all()]
if request and not request.user.is_superuser: if requested_org.id not in allowed_orgs:
[allowed_orgs.add(tup[0]) for tup in Organization.accessible_pk_qs(request.user, 'read_role').all()] raise ValidationError(_(f"Organization {requested_org.id} not found or you don't have permissions to access it"))
if requested_org.id not in allowed_orgs:
raise ValidationError(_(f"Organization {requested_org.id} not found or you don't have permissions to access it"))
def check_unified_job_permission(self, request, requested_ujts): def check_unified_job_permission(self, request, requested_ujts):
allowed_ujts = set() allowed_ujts = set()
@@ -4786,6 +4784,7 @@ class BulkJobLaunchSerializer(BaseSerializer):
if requested_ujts - allowed_ujts: if requested_ujts - allowed_ujts:
not_allowed = requested_ujts - allowed_ujts not_allowed = requested_ujts - allowed_ujts
raise serializers.ValidationError(_(f"Unified Job Templates {not_allowed} not found or you don't have permissions to access it")) raise serializers.ValidationError(_(f"Unified Job Templates {not_allowed} not found or you don't have permissions to access it"))
def check_inventory_permission(self, attrs, request, requested_use_inventories): def check_inventory_permission(self, attrs, request, requested_use_inventories):
accessible_use_inventories = {tup[0] for tup in Inventory.accessible_pk_qs(request.user, 'use_role')} accessible_use_inventories = {tup[0] for tup in Inventory.accessible_pk_qs(request.user, 'use_role')}
if requested_use_inventories - accessible_use_inventories: if requested_use_inventories - accessible_use_inventories:

View File

@@ -4,43 +4,38 @@ This endpoint allows the client to create multiple hosts and associate them with
Example: Example:
``` {
{ "inventory": 1,
"inventory": 1, "hosts": [
"hosts": [ {"name": "example1.com", "variables": "ansible_connection: local"},
{"name": "example1.com", "variables": "ansible_connection: local"}, {"name": "example2.com"}
{"name": "example2.com"} ]
] }
}
```
Return data: Return data:
```commandline {
{ "url": "/api/v2/inventories/3/hosts/",
"url": "/api/v2/inventories/3/hosts/", "hosts": [
"hosts": [ {
{ "name": "example1.com",
"name": "example1.com", "enabled": true,
"enabled": true, "instance_id": "",
"instance_id": "", "description": "",
"description": "", "variables": "ansible_connection: local",
"variables": "ansible_connection: local", "id": 1255,
"id": 1255, "url": "/api/v2/hosts/1255/",
"url": "/api/v2/hosts/1255/", "inventory": "/api/v2/inventories/3/"
"inventory": "/api/v2/inventories/3/" },
}, {
{ "name": "example2.com",
"name": "example2.com", "enabled": true,
"enabled": true, "instance_id": "",
"instance_id": "", "description": "",
"description": "", "variables": "",
"variables": "", "id": 1256,
"id": 1256, "url": "/api/v2/hosts/1256/",
"url": "/api/v2/hosts/1256/", "inventory": "/api/v2/inventories/3/"
"inventory": "/api/v2/inventories/3/" }
} ]
] }
}
```

View File

@@ -4,13 +4,10 @@ This endpoint allows the client to launch multiple UnifiedJobTemplates at a time
Example: Example:
``` {
{ "name": "my bulk job",
"name": "my bulk job", "jobs": [
"jobs": [ {"unified_job_template": 7, "inventory": 2},
{"unified_job_template": 7, "inventory": 2}, {"unified_job_template": 7, "credentials": [3]}
{"unified_job_template": 7, "credentials": [3]} ]
] }
}
```

View File

@@ -6,6 +6,7 @@ from rest_framework.reverse import reverse
from rest_framework import status from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
from awx.main.models import UnifiedJob, Host
from awx.api.generics import ( from awx.api.generics import (
GenericAPIView, GenericAPIView,
APIView, APIView,

View File

@@ -15,7 +15,10 @@ page.register_page([resources.bulk, (resources.bulk, 'get')], Bulk)
class BulkJobLaunch(base.Base): class BulkJobLaunch(base.Base):
def post(self, payload={}): def post(self, payload={}):
result = self.connection.post(self.endpoint, payload) result = self.connection.post(self.endpoint, payload)
return self.walk(result.json()['url']) if 'url' in result.json():
return self.walk(result.json()['url'])
else:
return self.page_identity(result, request_json={})
page.register_page(resources.bulk_job_launch, BulkJobLaunch) page.register_page(resources.bulk_job_launch, BulkJobLaunch)