summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--awx/api/serializers.py75
-rw-r--r--awx/api/templates/api/bulk_host_create_view.md32
-rw-r--r--awx/main/tests/functional/test_bulk.py26
-rw-r--r--awx_collection/plugins/modules/bulk_host_create.py5
-rw-r--r--awx_collection/plugins/modules/bulk_job_launch.py3
-rw-r--r--awx_collection/test/awx/test_bulk.py4
-rw-r--r--awx_collection/tests/integration/targets/bulk_host_create/main.yml6
-rw-r--r--awx_collection/tests/integration/targets/bulk_job_launch/main.yml4
-rw-r--r--docs/bulk_api.md39
9 files changed, 123 insertions, 71 deletions
diff --git a/awx/api/serializers.py b/awx/api/serializers.py
index db4888b31f..4e49226711 100644
--- a/awx/api/serializers.py
+++ b/awx/api/serializers.py
@@ -1958,6 +1958,7 @@ class BulkHostSerializer(HostSerializer):
variables = serializers.CharField(write_only=True, required=False)
class Meta:
+ model = Host
fields = (
'name',
'enabled',
@@ -1977,10 +1978,11 @@ class BulkHostCreateSerializer(serializers.Serializer):
)
class Meta:
+ model = Inventory
fields = ('inventory', 'hosts')
read_only_fields = ()
- def raise_if_cannot_add_hosts(self, attrs):
+ def raise_if_host_counts_violated(self, attrs):
validation_info = get_licenser().validate()
org = attrs['inventory'].organization
@@ -1988,7 +1990,7 @@ class BulkHostCreateSerializer(serializers.Serializer):
if org:
org_active_count = Host.objects.org_active_count(org.id)
new_hosts = [h['name'] for h in attrs['hosts']]
- org_net_new_host_count = Host.objects.filter(inventory__organization=org.id).exclude(name__in=new_hosts).count()
+ org_net_new_host_count = len(new_hosts) - Host.objects.filter(inventory__organization=1, name__in=new_hosts).values('name').distinct().count()
if org.max_hosts > 0 and org_active_count + org_net_new_host_count > org.max_hosts:
raise PermissionDenied(
_(
@@ -2019,15 +2021,12 @@ class BulkHostCreateSerializer(serializers.Serializer):
inv = attrs['inventory']
if request and not request.user.is_superuser:
if inv.organization:
- org_admin_orgs = {tup[0] for tup in Organization.accessible_pk_qs(request.user, 'admin_role')}
- inv_admin_orgs = {tup[0] for tup in Organization.accessible_pk_qs(request.user, 'inventory_admin_role')}
- is_org_admin = inv.organization.id in org_admin_orgs
- is_org_inv_admin = inv.organization.id in inv_admin_orgs
+ is_org_admin = request.user in inv.organization.admin_role
+ is_org_inv_admin = request.user in inv.organization.inventory_admin_role
else:
is_org_admin = False
is_org_inv_admin = False
- # This may not work, need to figure out what the role is called
- is_inventory_admin = inv.admin_role.members.filter(id=request.user.id).exists()
+ is_inventory_admin = request.user in inv.admin_role
if not any([is_inventory_admin, is_org_admin, is_org_inv_admin]):
raise serializers.ValidationError(_(f'Inventory with id {inv.id} not found or lack permissions to add hosts.'))
current_hostnames = {h[0] for h in Host.objects.filter(inventory=inv).values_list('name').all()}
@@ -2036,7 +2035,7 @@ class BulkHostCreateSerializer(serializers.Serializer):
if duplicate_new_names:
raise serializers.ValidationError(_(f'Hostnames must be unique in an inventory. Duplicates found: {duplicate_new_names}'))
- self.raise_if_cannot_add_hosts(attrs)
+ self.raise_if_host_counts_violated(attrs)
_now = now()
for host in attrs['hosts']:
@@ -2068,7 +2067,8 @@ class BulkHostCreateSerializer(serializers.Serializer):
# This actually updates the cached "total_hosts" field on the inventory
update_inventory_computed_fields.delay(validated_data['inventory'].id)
return_keys = [k for k in BulkHostSerializer().fields.keys()] + ['id']
- return_data = []
+ return_data = {}
+ host_data = []
for r in result:
item = {k: getattr(r, k) for k in return_keys}
if not settings.IS_TESTING_MODE:
@@ -2076,7 +2076,9 @@ class BulkHostCreateSerializer(serializers.Serializer):
# to get it, you have to do an additional query, which is not useful for our tests
item['url'] = reverse('api:host_detail', kwargs={'pk': r.id})
item['inventory'] = reverse('api:inventory_detail', kwargs={'pk': validated_data['inventory'].id})
- return_data.append(item)
+ host_data.append(item)
+ return_data['url'] = reverse('api:inventory_hosts_list', kwargs={'pk': validated_data['inventory'].id})
+ return_data['hosts'] = host_data
return return_data
@@ -4541,7 +4543,8 @@ class WorkflowJobLaunchSerializer(BaseSerializer):
class BulkJobNodeSerializer(serializers.Serializer):
- # if we can find out the user, we can filter down the UnifiedJobTemplate objects
+ # We don't do a PrimaryKeyRelatedField for unified_job_template and inventory, because that increases the number
+ # of database queries, rather we take them as integer and later convert them to objects in get_objectified_jobs
unified_job_template = serializers.IntegerField(
required=True,
min_value=1,
@@ -4552,22 +4555,20 @@ class BulkJobNodeSerializer(serializers.Serializer):
labels = serializers.ListField(child=serializers.IntegerField(min_value=1), required=False)
instance_groups = serializers.ListField(child=serializers.IntegerField(min_value=1), required=False)
execution_environment = serializers.IntegerField(required=False, min_value=1)
- #
limit = serializers.CharField(required=False, write_only=True, allow_blank=False)
scm_branch = serializers.CharField(required=False, write_only=True, allow_blank=False)
verbosity = serializers.IntegerField(required=False, min_value=1)
forks = serializers.IntegerField(required=False, min_value=1)
- char_prompts = serializers.CharField(required=False, write_only=True, allow_blank=False)
diff_mode = serializers.CharField(required=False, write_only=True, allow_blank=False)
job_tags = serializers.CharField(required=False, write_only=True, allow_blank=False)
job_type = serializers.CharField(required=False, write_only=True, allow_blank=False)
skip_tags = serializers.CharField(required=False, write_only=True, allow_blank=False)
- survey_passwords = serializers.CharField(required=False, write_only=True, allow_blank=False)
job_slice_count = serializers.IntegerField(required=False, min_value=1)
timeout = serializers.IntegerField(required=False, min_value=1)
extra_data = serializers.JSONField(write_only=True, required=False)
class Meta:
+ model = WorkflowJobNode
fields = (
'unified_job_template',
'identifier',
@@ -4580,17 +4581,13 @@ class BulkJobNodeSerializer(serializers.Serializer):
'scm_branch',
'verbosity',
'forks',
- 'char_prompts',
'diff_mode',
'extra_data',
'job_slice_count',
'job_tags',
'job_type',
'skip_tags',
- 'survey_passwords',
'timeout',
- # these are related objects and we need to add extra validation for them in the parent BulkJobLaunchSerializer
- #
)
@@ -4610,9 +4607,6 @@ class BulkJobLaunchSerializer(BaseSerializer):
inventory = serializers.PrimaryKeyRelatedField(queryset=Inventory.objects.all(), required=False, write_only=True)
limit = serializers.CharField(write_only=True, required=False, allow_blank=False)
scm_branch = serializers.CharField(write_only=True, required=False, allow_blank=False)
- # not implemented yet
- # webhook_service: null, # Here we can use PrimaryKeyRelatedField so it will automagically do rbac/turn into object, I think, I'm actually not sure how to use this
- # webhook_credential: null, # Here we can use PrimaryKeyRelatedField so it will automagically do rbac/turn into object I think, I'm actually not sure how to use this
skip_tags = serializers.CharField(write_only=True, required=False, allow_blank=False)
job_tags = serializers.CharField(write_only=True, required=False, allow_blank=False)
@@ -4632,8 +4626,6 @@ class BulkJobLaunchSerializer(BaseSerializer):
else:
node['identifier'] = str(uuid4())
- # Build sets of all the requested resources
- # TODO: As we add other related items, we need to add them here
requested_ujts = {j['unified_job_template'] for j in attrs['jobs']}
requested_use_inventories = {job['inventory'] for job in attrs['jobs'] if 'inventory' in job}
requested_use_execution_environments = {job['execution_environment'] for job in attrs['jobs'] if 'execution_environment' in job}
@@ -4649,15 +4641,11 @@ class BulkJobLaunchSerializer(BaseSerializer):
[requested_use_instance_groups.add(instance_group) for instance_group in job['instance_groups']]
# If we are not a superuser, check we have permissions
- # TODO: As we add other related items, we need to add them here
if request and not request.user.is_superuser:
self.check_organization_permission(attrs, request)
self.check_unified_job_permission(request, requested_ujts)
- if requested_use_inventories:
- self.check_inventory_permission(request, requested_use_inventories)
-
- if requested_use_credentials:
- self.check_credential_permission(request, requested_use_credentials)
+ if requested_use_inventories or 'inventory' in attrs:
+ self.check_inventory_permission(attrs, request, requested_use_inventories)
if requested_use_labels:
self.check_label_permission(requested_use_labels)
@@ -4688,9 +4676,6 @@ class BulkJobLaunchSerializer(BaseSerializer):
def create(self, validated_data):
job_node_data = validated_data.pop('jobs')
- # FIXME: Need to set organization on the WorkflowJob in order for users to be able to see it --
- # normally their permission is sourced from the underlying WorkflowJobTemplate
- # maybe we need to add Organization to WorkflowJob
wfj_deferred_attr_names = ('skip_tags', 'limit', 'job_tags')
wfj_deferred_vals = {}
for item in wfj_deferred_attr_names:
@@ -4703,6 +4688,7 @@ class BulkJobLaunchSerializer(BaseSerializer):
nodes = []
node_m2m_objects = {}
node_m2m_object_types_to_through_model = {
+
'credentials': WorkflowJobNode.credentials.through,
'labels': WorkflowJobNode.labels.through,
'instance_groups': WorkflowJobNode.instance_groups.through,
@@ -4712,12 +4698,10 @@ class BulkJobLaunchSerializer(BaseSerializer):
'scm_branch',
'verbosity',
'forks',
- 'char_prompts',
'diff_mode',
'job_tags',
'job_type',
'skip_tags',
- 'survey_passwords',
'job_slice_count',
'timeout',
)
@@ -4789,7 +4773,7 @@ class BulkJobLaunchSerializer(BaseSerializer):
if request and not request.user.is_superuser:
[allowed_orgs.add(tup[0]) for tup in Organization.accessible_pk_qs(request.user, 'read_role').all()]
if requested_org.id not in allowed_orgs:
- raise ValidationError(_(f"Organization {requested_org.id} not found"))
+ 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):
allowed_ujts = set()
@@ -4801,25 +4785,28 @@ class BulkJobLaunchSerializer(BaseSerializer):
if requested_ujts - allowed_ujts:
not_allowed = requested_ujts - allowed_ujts
- raise serializers.ValidationError(_(f"Unified Job Templates {not_allowed} not found."))
-
- def check_inventory_permission(self, request, requested_use_inventories):
+ 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):
accessible_use_inventories = {tup[0] for tup in Inventory.accessible_pk_qs(request.user, 'use_role')}
if requested_use_inventories - accessible_use_inventories:
not_allowed = requested_use_inventories - accessible_use_inventories
- raise serializers.ValidationError(_(f"Inventories {not_allowed} not found."))
+ raise serializers.ValidationError(_(f"Inventories {not_allowed} not found or you don't have permissions to access it"))
+ if 'inventory' in attrs:
+ requested_workflow_inventory = attrs['inventory']
+ if requested_workflow_inventory.id not in accessible_use_inventories:
+ raise serializers.ValidationError(_(f"Inventories {requested_workflow_inventory.id} not found or you don't have permissions to access it"))
def check_credential_permission(self, request, requested_use_credentials):
accessible_use_credentials = {tup[0] for tup in Credential.accessible_pk_qs(request.user, 'use_role').all()}
if requested_use_credentials - accessible_use_credentials:
not_allowed = requested_use_credentials - accessible_use_credentials
- raise serializers.ValidationError(_(f"Credentials {not_allowed} not found."))
+ raise serializers.ValidationError(_(f"Credentials {not_allowed} not found or you don't have permissions to access it"))
def check_label_permission(self, requested_use_labels):
accessible_use_labels = {tup.id for tup in Label.objects.all()}
if requested_use_labels - accessible_use_labels:
not_allowed = requested_use_labels - accessible_use_labels
- raise serializers.ValidationError(_(f"Labels {not_allowed} not found"))
+ raise serializers.ValidationError(_(f"Labels {not_allowed} not found or you don't have permissions to access it"))
def check_instance_group_permission(self, request, requested_use_instance_groups):
# only org admins are allowed to see instance groups
@@ -4828,7 +4815,7 @@ class BulkJobLaunchSerializer(BaseSerializer):
accessible_use_instance_groups = {tup.id for tup in InstanceGroup.objects.all()}
if requested_use_instance_groups - accessible_use_instance_groups:
not_allowed = requested_use_instance_groups - accessible_use_instance_groups
- raise serializers.ValidationError(_(f"Instance Groups {not_allowed} not found"))
+ raise serializers.ValidationError(_(f"Instance Groups {not_allowed} not found or you don't have permissions to access it"))
def check_execution_environment_permission(self, request, requested_use_execution_environments):
accessible_execution_env = {
@@ -4839,7 +4826,7 @@ class BulkJobLaunchSerializer(BaseSerializer):
}
if requested_use_execution_environments - accessible_execution_env:
not_allowed = requested_use_execution_environments - accessible_execution_env
- raise serializers.ValidationError(_(f"Execution Environments {not_allowed} not found"))
+ raise serializers.ValidationError(_(f"Execution Environments {not_allowed} not found or you don't have permissions to access it"))
def get_objectified_jobs(
self,
diff --git a/awx/api/templates/api/bulk_host_create_view.md b/awx/api/templates/api/bulk_host_create_view.md
index 8ac56f4d6a..b5d5ab08fd 100644
--- a/awx/api/templates/api/bulk_host_create_view.md
+++ b/awx/api/templates/api/bulk_host_create_view.md
@@ -8,9 +8,39 @@ Example:
{
"inventory": 1,
"hosts": [
- {"name": "example1.com"},
+ {"name": "example1.com", "variables": "ansible_connection: local"},
{"name": "example2.com"}
]
}
```
+
+Return data:
+
+```commandline
+{
+ "url": "/api/v2/inventories/3/hosts/",
+ "hosts": [
+ {
+ "name": "example1.com",
+ "enabled": true,
+ "instance_id": "",
+ "description": "",
+ "variables": "ansible_connection: local",
+ "id": 1255,
+ "url": "/api/v2/hosts/1255/",
+ "inventory": "/api/v2/inventories/3/"
+ },
+ {
+ "name": "example2.com",
+ "enabled": true,
+ "instance_id": "",
+ "description": "",
+ "variables": "",
+ "id": 1256,
+ "url": "/api/v2/hosts/1256/",
+ "inventory": "/api/v2/inventories/3/"
+ }
+ ]
+}
+```
diff --git a/awx/main/tests/functional/test_bulk.py b/awx/main/tests/functional/test_bulk.py
index 022c2b99e2..f33ac5a99a 100644
--- a/awx/main/tests/functional/test_bulk.py
+++ b/awx/main/tests/functional/test_bulk.py
@@ -47,7 +47,7 @@ def test_bulk_host_create_num_queries(organization, inventory, post, get, user,
hosts = [{'name': uuid4()} for i in range(num_hosts)]
with withAssertNumQueriesLessThan(num_queries):
bulk_host_create_response = post(reverse('api:bulk_host_create'), {'inventory': inventory.id, 'hosts': hosts}, u, expect=201).data
- assert len(bulk_host_create_response) == len(hosts), f"unexpected number of hosts created for user {u}"
+ assert len(bulk_host_create_response['hosts']) == len(hosts), f"unexpected number of hosts created for user {u}"
@pytest.mark.django_db
@@ -80,7 +80,7 @@ def test_bulk_host_create_rbac(organization, inventory, post, get, user):
bulk_host_create_response = post(
reverse('api:bulk_host_create'), {'inventory': inventory.id, 'hosts': [{'name': f'foobar-{indx}'}]}, u, expect=201
).data
- assert len(bulk_host_create_response) == 1, f"unexpected number of hosts created for user {u}"
+ assert len(bulk_host_create_response['hosts']) == 1, f"unexpected number of hosts created for user {u}"
for indx, u in enumerate([member, auditor, use_inv_member]):
bulk_host_create_response = post(
@@ -91,8 +91,8 @@ def test_bulk_host_create_rbac(organization, inventory, post, get, user):
@pytest.mark.django_db
def test_bulk_job_launch(job_template, organization, inventory, project, credential, post, get, user):
'''
- if I don't have access to the unified job templare
- ... I can't launch the bulk job
+ if I have access to the unified job template
+ ... I can launch the bulk job
'''
normal_user = user('normal_user', False)
jt = JobTemplate.objects.create(name='my-jt', inventory=inventory, project=project, playbook='helloworld.yml')
@@ -102,6 +102,7 @@ def test_bulk_job_launch(job_template, organization, inventory, project, credent
bulk_job_launch_response = post(
reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': [{'unified_job_template': jt.id}]}, normal_user, expect=201
).data
+ assert bulk_job_launch_response['id'] == 1
@pytest.mark.django_db
@@ -114,9 +115,7 @@ def test_bulk_job_launch_no_access_to_job_template(job_template, organization, i
jt = JobTemplate.objects.create(name='my-jt', inventory=inventory, project=project, playbook='helloworld.yml')
jt.save()
organization.member_role.members.add(normal_user)
- bulk_job_launch_response = post(
- reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': [{'unified_job_template': jt.id}]}, normal_user, expect=400
- ).data
+ post(reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': [{'unified_job_template': jt.id}]}, normal_user, expect=400)
@pytest.mark.django_db
@@ -129,9 +128,7 @@ def test_bulk_job_launch_no_org_assigned(job_template, organization, inventory,
jt = JobTemplate.objects.create(name='my-jt', inventory=inventory, project=project, playbook='helloworld.yml')
jt.save()
jt.execute_role.members.add(normal_user)
- bulk_job_launch_response = post(
- reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': [{'unified_job_template': jt.id}]}, normal_user, expect=400
- ).data
+ post(reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': [{'unified_job_template': jt.id}]}, normal_user, expect=400)
@pytest.mark.django_db
@@ -149,9 +146,7 @@ def test_bulk_job_launch_multiple_org_assigned(job_template, organization, inven
jt = JobTemplate.objects.create(name='my-jt', inventory=inventory, project=project, playbook='helloworld.yml')
jt.save()
jt.execute_role.members.add(normal_user)
- bulk_job_launch_response = post(
- reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': [{'unified_job_template': jt.id}]}, normal_user, expect=400
- ).data
+ post(reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': [{'unified_job_template': jt.id}]}, normal_user, expect=400)
@pytest.mark.django_db
@@ -172,6 +167,7 @@ def test_bulk_job_launch_specific_org(job_template, organization, inventory, pro
bulk_job_launch_response = post(
reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': [{'unified_job_template': jt.id}], 'organization': org1.id}, normal_user, expect=201
).data
+ assert bulk_job_launch_response['id'] == 1
@pytest.mark.django_db
@@ -189,6 +185,4 @@ def test_bulk_job_launch_inventory_no_access(job_template, organization, invento
org1.member_role.members.add(normal_user)
inv = Inventory.objects.create(name='inv1', organization=org2)
jt.execute_role.members.add(normal_user)
- bulk_job_launch_response = post(
- reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': [{'unified_job_template': jt.id, 'inventory': inv.id}]}, normal_user, expect=400
- ).data
+ post(reverse('api:bulk_job_launch'), {'name': 'Bulk Job Launch', 'jobs': [{'unified_job_template': jt.id, 'inventory': inv.id}]}, normal_user, expect=400)
diff --git a/awx_collection/plugins/modules/bulk_host_create.py b/awx_collection/plugins/modules/bulk_host_create.py
index 3e6ea91cc5..d848d999b5 100644
--- a/awx_collection/plugins/modules/bulk_host_create.py
+++ b/awx_collection/plugins/modules/bulk_host_create.py
@@ -31,8 +31,9 @@ options:
type: str
require: True
description:
- - The description to use for the host.
- type: str
+ description:
+ - The description to use for the host.
+ type: str
enabled:
description:
- If the host should be enabled.
diff --git a/awx_collection/plugins/modules/bulk_job_launch.py b/awx_collection/plugins/modules/bulk_job_launch.py
index 7a6e800823..9b7545605e 100644
--- a/awx_collection/plugins/modules/bulk_job_launch.py
+++ b/awx_collection/plugins/modules/bulk_job_launch.py
@@ -200,7 +200,6 @@ EXAMPLES = '''
'''
from ..module_utils.controller_api import ControllerAPIModule
-import json
def main():
@@ -208,6 +207,7 @@ def main():
argument_spec = dict(
jobs=dict(required=True, type='list'),
name=dict(),
+ description=dict(),
organization=dict(type='int'),
inventory=dict(type='int'),
limit=dict(),
@@ -226,6 +226,7 @@ def main():
post_data_names = (
'jobs',
'name',
+ 'description',
'organization',
'inventory',
'limit',
diff --git a/awx_collection/test/awx/test_bulk.py b/awx_collection/test/awx/test_bulk.py
index fbccf3d1d8..842e1a997f 100644
--- a/awx_collection/test/awx/test_bulk.py
+++ b/awx_collection/test/awx/test_bulk.py
@@ -9,7 +9,7 @@ from awx.main.models import WorkflowJob
@pytest.mark.django_db
def test_bulk_job_launch(run_module, admin_user, job_template):
jobs = [dict(unified_job_template=job_template.id)]
- result = run_module(
+ run_module(
'bulk_job_launch',
{
'name': "foo-bulk-job",
@@ -29,7 +29,7 @@ def test_bulk_job_launch(run_module, admin_user, job_template):
@pytest.mark.django_db
def test_bulk_host_create(run_module, admin_user, inventory):
hosts = [dict(name="127.0.0.1"), dict(name="foo.dns.org")]
- result = run_module(
+ run_module(
'bulk_host_create',
{
'inventory': inventory.id,
diff --git a/awx_collection/tests/integration/targets/bulk_host_create/main.yml b/awx_collection/tests/integration/targets/bulk_host_create/main.yml
index c5b0437f37..13e5eb2448 100644
--- a/awx_collection/tests/integration/targets/bulk_host_create/main.yml
+++ b/awx_collection/tests/integration/targets/bulk_host_create/main.yml
@@ -7,7 +7,7 @@
- name: Generate a unique name
set_fact:
bulk_host_name: "AWX-Collection-tests-bulk_host_create-{{ test_id }}"
-
+
- name: Get our collection package
controller_meta:
register: controller_meta
@@ -23,7 +23,7 @@
organization: Default
state: present
register: inventory_result
-
+
- name: Bulk Host Create
bulk_host_create:
@@ -48,4 +48,4 @@
inventory:
name: "{{ bulk_host_name }}"
organization: Default
- state: absent \ No newline at end of file
+ state: absent
diff --git a/awx_collection/tests/integration/targets/bulk_job_launch/main.yml b/awx_collection/tests/integration/targets/bulk_job_launch/main.yml
index 6c014e3732..37a63abbce 100644
--- a/awx_collection/tests/integration/targets/bulk_job_launch/main.yml
+++ b/awx_collection/tests/integration/targets/bulk_job_launch/main.yml
@@ -7,7 +7,7 @@
- name: Generate a unique name
set_fact:
bulk_job_name: "AWX-Collection-tests-bulk_job_launch-{{ test_id }}"
-
+
- name: Get our collection package
controller_meta:
register: controller_meta
@@ -66,4 +66,4 @@
- name: Delete Job Template
job_template:
name: "{{ bulk_job_name }}"
- state: absent \ No newline at end of file
+ state: absent
diff --git a/docs/bulk_api.md b/docs/bulk_api.md
new file mode 100644
index 0000000000..40f51d8776
--- /dev/null
+++ b/docs/bulk_api.md
@@ -0,0 +1,39 @@
+# Bulk API Overview
+
+Bulk API endpoints allows to perform bulk operations in single web request. There are currently following bulk api actions:
+- /api/v2/bulk/job_launch
+- /api/v2/bulk/host_create
+
+## Bulk Job Launch
+
+Provides feature in the API that allows a single web request to achieve multiple job launches. It creates a workflow job with individual jobs as nodes within the workflow job. It also supports providing promptable fields like inventory, credential etc.
+
+Following is an example of a post request at the /api/v2/bulk/job_launch
+
+```commandline
+{
+"name": "Bulk Job Launch",
+"jobs": [
+ {"unified_job_template": 7, "identifier":"foo", "limit": "kansas", "credentials": [1]},
+ {"unified_job_template": 8, "identifier":"bar", "inventory": 1, "execution_environment": 3},
+ {"unified_job_template": 9}
+]
+}
+```
+
+The above will launch a workflow job with 3 nodes in it.
+
+## Bulk Host Create
+
+Provides feature in the API that allows a single web request to create multiple hosts in an inventory.
+
+Following is an example of a post request at the /api/v2/bulk/host_create:
+
+```commandline
+{
+ "inventory": 1,
+ "hosts": [{"name": "host1", "variables": "ansible_connection: local"}, {"name": "host2"}, {"name": "host3"}, {"name": "host4"}, {"name": "host5"}, {"name": "host6"}]
+}
+```
+
+The above will add 6 hosts in the inventory. \ No newline at end of file