##### SUMMARY
Replaces #13358 and #13369
##### ISSUE TYPE
- New or Enha…nced Feature
##### COMPONENT NAME
- API
##### ADDITIONAL INFORMATION
<!---
Include additional information to help people understand the change here.
For bugs that don't have a linked bug report, a step-by-step reproduction
of the problem is helpful.
-->
- With this change you could launch a bulk job as following:
Post to `/api/v2/bulk/job_launch`
```
{
"name": "Bulk Job Launch",
"jobs": [
{"unified_job_template": 9, "identifier":"foo", "limit": "kansas", "credentials": [1], "inventory": 1, "execution_environment": 3},
{"unified_job_template": 7, "identifier":"bar", "limit": "kansas", "credentials": [1], "inventory": 1, "execution_environment": 3},
{"unified_job_template": 7, "limit": "kansas", "credentials": [1], "inventory": 1, "execution_environment": 3}
],
"inventory": 2
}
```
The above will launch a workflow job with 3 nodes in it.
- One could also bulk add host as following:
Post to `/api/v2/bulk/host_create`
```
{"inventory": 1, "hosts": [{"name": "foo-0-2022-12-22 01:29:08.995393"}, {"name": "foo-1-2022-12-22 01:29:08.995393"}, {"name": "foo-2-2022-12-22 01:29:08.995393"}, {"name": "foo-3-2022-12-22 01:29:08.995393"}, {"name": "foo-4-2022-12-22 01:29:08.995393"}, {"name": "foo-5-2022-12-22 01:29:08.995393"}, {"name": "foo-6-2022-12-22 01:29:08.995393"}, {"name": "foo-7-2022-12-22 01:29:08.995393"}, {"name": "foo-8-2022-12-22 01:29:08.995393"}, {"name": "foo-9-2022-12-22 01:29:08.995393"}, {"name": "foo-10-2022-12-22 01:29:08.995393"}, {"name": "foo-11-2022-12-22 01:29:08.995393"}, {"name": "foo-12-2022-12-22 01:29:08.995393"}, {"name": "foo-13-2022-12-22 01:29:08.995393"}, {"name": "foo-14-2022-12-22 01:29:08.995393"}, {"name": "foo-15-2022-12-22 01:29:08.995393"}, {"name": "foo-16-2022-12-22 01:29:08.995393"}, {"name": "foo-17-2022-12-22 01:29:08.995393"}, {"name": "foo-18-2022-12-22 01:29:08.995393"}, {"name": "foo-19-2022-12-22 01:29:08.995393"}, {"name": "foo-20-2022-12-22 01:29:08.995393"}, {"name": "foo-21-2022-12-22 01:29:08.995393"}, {"name": "foo-22-2022-12-22 01:29:08.995393"}, {"name": "foo-23-2022-12-22 01:29:08.995393"}, {"name": "foo-24-2022-12-22 01:29:08.995393"}, {"name": "foo-25-2022-12-22 01:29:08.995393"}, {"name": "foo-26-2022-12-22 01:29:08.995393"}, {"name": "foo-27-2022-12-22 01:29:08.995393"}, {"name": "foo-28-2022-12-22 01:29:08.995393"}, {"name": "foo-29-2022-12-22 01:29:08.995393"}, {"name": "foo-30-2022-12-22 01:29:08.995393"}, {"name": "foo-31-2022-12-22 01:29:08.995393"}, {"name": "foo-32-2022-12-22 01:29:08.995393"}, {"name": "foo-33-2022-12-22 01:29:08.995393"}, {"name": "foo-34-2022-12-22 01:29:08.995393"}, {"name": "foo-35-2022-12-22 01:29:08.995393"}, {"name": "foo-36-2022-12-22 01:29:08.995393"}, {"name": "foo-37-2022-12-22 01:29:08.995393"}, {"name": "foo-38-2022-12-22 01:29:08.995393"}, {"name": "foo-39-2022-12-22 01:29:08.995393"}, {"name": "foo-40-2022-12-22 01:29:08.995393"}, {"name": "foo-41-2022-12-22 01:29:08.995393"}, {"name": "foo-42-2022-12-22 01:29:08.995393"}, {"name": "foo-43-2022-12-22 01:29:08.995393"}, {"name": "foo-44-2022-12-22 01:29:08.995393"}, {"name": "foo-45-2022-12-22 01:29:08.995393"}, {"name": "foo-46-2022-12-22 01:29:08.995393"}, {"name": "foo-47-2022-12-22 01:29:08.995393"}, {"name": "foo-48-2022-12-22 01:29:08.995393"}, {"name": "foo-49-2022-12-22 01:29:08.995393"}, {"name": "foo-50-2022-12-22 01:29:08.995393"}, {"name": "foo-51-2022-12-22 01:29:08.995393"}, {"name": "foo-52-2022-12-22 01:29:08.995393"}, {"name": "foo-53-2022-12-22 01:29:08.995393"}, {"name": "foo-54-2022-12-22 01:29:08.995393"}, {"name": "foo-55-2022-12-22 01:29:08.995393"}, {"name": "foo-56-2022-12-22 01:29:08.995393"}, {"name": "foo-57-2022-12-22 01:29:08.995393"}, {"name": "foo-58-2022-12-22 01:29:08.995393"}, {"name": "foo-59-2022-12-22 01:29:08.995393"}, {"name": "foo-60-2022-12-22 01:29:08.995393"}, {"name": "foo-61-2022-12-22 01:29:08.995393"}, {"name": "foo-62-2022-12-22 01:29:08.995393"}, {"name": "foo-63-2022-12-22 01:29:08.995393"}, {"name": "foo-64-2022-12-22 01:29:08.995393"}, {"name": "foo-65-2022-12-22 01:29:08.995393"}, {"name": "foo-66-2022-12-22 01:29:08.995393"}, {"name": "foo-67-2022-12-22 01:29:08.995393"}, {"name": "foo-68-2022-12-22 01:29:08.995393"}, {"name": "foo-69-2022-12-22 01:29:08.995393"}, {"name": "foo-70-2022-12-22 01:29:08.995393"}, {"name": "foo-71-2022-12-22 01:29:08.995393"}, {"name": "foo-72-2022-12-22 01:29:08.995393"}, {"name": "foo-73-2022-12-22 01:29:08.995393"}, {"name": "foo-74-2022-12-22 01:29:08.995393"}, {"name": "foo-75-2022-12-22 01:29:08.995393"}, {"name": "foo-76-2022-12-22 01:29:08.995393"}, {"name": "foo-77-2022-12-22 01:29:08.995393"}, {"name": "foo-78-2022-12-22 01:29:08.995393"}, {"name": "foo-79-2022-12-22 01:29:08.995393"}, {"name": "foo-80-2022-12-22 01:29:08.995393"}, {"name": "foo-81-2022-12-22 01:29:08.995393"}, {"name": "foo-82-2022-12-22 01:29:08.995393"}, {"name": "foo-83-2022-12-22 01:29:08.995393"}, {"name": "foo-84-2022-12-22 01:29:08.995393"}, {"name": "foo-85-2022-12-22 01:29:08.995393"}, {"name": "foo-86-2022-12-22 01:29:08.995393"}, {"name": "foo-87-2022-12-22 01:29:08.995393"}, {"name": "foo-88-2022-12-22 01:29:08.995393"}, {"name": "foo-89-2022-12-22 01:29:08.995393"}, {"name": "foo-90-2022-12-22 01:29:08.995393"}, {"name": "foo-91-2022-12-22 01:29:08.995393"}, {"name": "foo-92-2022-12-22 01:29:08.995393"}, {"name": "foo-93-2022-12-22 01:29:08.995393"}, {"name": "foo-94-2022-12-22 01:29:08.995393"}, {"name": "foo-95-2022-12-22 01:29:08.995393"}, {"name": "foo-96-2022-12-22 01:29:08.995393"}, {"name": "foo-97-2022-12-22 01:29:08.995393"}, {"name": "foo-98-2022-12-22 01:29:08.995393"}, {"name": "foo-99-2022-12-22 01:29:08.995393"}]}
```
## Definition of Done
### Bulk Add Host
#### General API:
- [x] OPTIONS request responds with description of schema accepted
- [x] POST accepts inventory and hosts desired to create, responds with how many hosts created, and link to inventory
- [x] Have POST return id's of the created hosts
- [x] Create ActivityStream entry/entries to create
#### RBAC
- [x] org admin for org on inventory can add hosts
- [x] inventory admin at org level for org of inventory can add hosts
- [x] superuser can add hosts, bypass rbac
- [x] inventory admin of particular inventory can add hosts
#### CLI/collection/awxkit
- [x] add collection module for bulk inventory add
- [x] add cli support for bulk inventory add
- [x] add awxkit support for bulk inventory add
#### Testing
- [x] add awx functional test asserting on number of queries it takes
- [x] add awx functional test for RBAC
- [x] add integration tests for api
- [x] add integration tests for collection
- [x] add integration tests for cli
### Bulk Job Launch
#### At WorkflowJob level:
- [x] reach feature parity with things you can normally set on a WorkflowJobTemplate that get inherited by the WorkflowJob
- [x] "name" (done)
- [x] "description": "",
- [x] "extra_vars": "",
- [x] "organization": (see below in RBAC section)
- [x] "inventory": null,
- [x] "limit": "",
- [x] "scm_branch": ""
- [x] "skip_tags": "",
- [x] "job_tags": ""
#### At workflow job node level:
- [x] implement RBAC and appropriate relating of other related objects
- [x] inventory
- [x] credentials
- [x] instance groups
- [x] execution environment
- [x] labels
- [x] implement schema check and assignment of other character/integer fields
- [x] 'limit'
- [x] 'char_prompts',
- [x] 'diff_mode',
- [x] 'extra_data',
- [x] 'forks',
- [x] 'job_slice_count',
- [x] 'job_tags',
- [x] 'job_type'
- [x] 'scm_branch',
- [x] 'skip_tags',
- [x] 'survey_passwords',
- [x] 'timeout',
- [x] 'verbosity',
#### RBAC
- [x] add "is_bulk_job" field to WorkflowJob model
- [x] associate the user organization to the WorkflowJob if they only have one org, or allow them to specify an organization they belong to as the org (unless they are sysadmin, in which case they can assocaite any org). If user has multiple organizations, return a validation error and tell them they need to pick one
- [x] Anyone who launched a bulk job can see the one they launched
- [x] An org admin for the Organization on a bulk job can see that bulk job
- [x] open up RBAC access for WorkflowJob and WorkflowJobNodes to allow normal users view bulk jobs they launched in detail view. They won't see them in the general job list view
- [x] check that relaunch is prohibited except for super user. we get this for free since its "orphaned" (has no wfjt)
- [x] record workflow job launch in activity stream.
#### Maintainablity
- [x] figure out if there is a way we can share some code between access.py and the validation method to reduce risk of drift between the two
- [x] figure out if we can chunk up the validate method into some more sensible helper methods to make it a bit more readable/have logical sections. E.g. "_validate_permissions", "_objectify_attrs_for_create"
- [x] Add a configuration for the MAX bulk host and MAX bulk job launch
#### General API functionality
- [x] provide /api/v2/bulk/job_launch
- [x] list of individual nodes will be accessed via workflow_job_nodes related item on the workflow job returned
- [x] return something sensible to a GET to /api/v2/bulk/job_launch. Use other launch endpoints as a reference. They appear to return a json blob with all the default values of the items you could launch
- [x] in the DjangoRestFramework API browser thing, in the "POST" form, it appears there is someway you can populate it with sample POST data. Provide something sensible for /api/v2/bulk_jobs/launch
- [x] Provide some sensible OPTIONS response to /api/v2/bulk/job_launch
#### Tests
- [x] awxkit part to expose the api/v2/bulk
- [x] awxkit part to expose api/v2/bulk/job_launch
- [x] Add tests that do some validation that # of queries stays reasonable
- [x] Test with LOTS of unified_job_templates in existence in the system and regular users. Concern is RBAC check queries could return a lot of pk's -- question is, where does this become a problem. I did nominal testing with ~ 10k job templates, didn't appear to be an issue but worth poking at some more.
- [x] Add tests that do some validation of RBAC access
- [x] Verify current behavior of launching jobs with "promptable" items that are either not allowed by the job template or you don't have access to.