Allow assigning instance groups to inventory sources - #600
Conversation
An inventory update runs inside an execution environment on whatever node the scheduler picks, so that node is the one that needs network access to the inventory source. Until now the only routing control was Inventory.instance_groups, which also decides where playbook jobs using that inventory run. Pinning an inventory to a remote execution node so jobs can reach its hosts also dragged the source syncs onto that node, which often has no route back to the system the inventory pulls from (forum thread d/17, upstream awx issue 13995). InventorySource already inherits the instance_groups relation from UnifiedJobTemplate, so no schema change is needed; the API simply never exposed it and InventoryUpdate.preferred_instance_groups ignored it. - InventoryUpdate.preferred_instance_groups now consults the source first, then falls back to the existing inventory -> organization -> global chain, matching the job template precedence semantics - new /api/v2/inventory_sources/N/instance_groups/ endpoint, wired into related links, association order preserved - attaching requires use permission on the instance group plus admin on the inventory, same rule as job templates and inventories - instance group lookup on the inventory source form, association on add/edit, labels on the detail view - docs note explaining that syncs run on execution nodes Sources with no instance groups keep the exact previous behaviour.
There was a problem hiding this comment.
Pull request overview
This PR adds first-class support for assigning Instance Groups directly to Inventory Sources so inventory sync jobs can be routed independently from playbook jobs that use the inventory. This closes a long-standing gap where Inventory.instance_groups controlled both playbook execution placement and inventory update placement.
Changes:
- Inventory update scheduling now prefers
inventory_source.instance_groupsahead of the existing inventory/org/global fallback chain. - New REST API sublist endpoint
/api/v2/inventory_sources/:id/instance_groups/is exposed (with related-link wiring) and enforced via RBAC. - UI adds an Instance Groups selector to inventory source add/edit and displays selected groups on the detail view; test suite updated accordingly.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| docs/inventory_source_instance_groups.md | Documents the new per-inventory-source routing behavior and API usage. |
| awx/ui/src/screens/Inventory/shared/InventorySourceForm.js | Adds an Instance Groups lookup field to the Inventory Source form. |
| awx/ui/src/screens/Inventory/InventorySourceEdit/InventorySourceEdit.js | Fetches existing instance groups and orders them on save. |
| awx/ui/src/screens/Inventory/InventorySourceEdit/InventorySourceEdit.test.js | Updates edit flow tests to include instance group ordering. |
| awx/ui/src/screens/Inventory/InventorySourceAdd/InventorySourceAdd.js | Associates instance groups after inventory source creation. |
| awx/ui/src/screens/Inventory/InventorySourceAdd/InventorySourceAdd.test.js | Adds coverage for associating instance groups on create. |
| awx/ui/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.js | Displays instance groups on the inventory source detail screen. |
| awx/ui/src/api/models/InventorySources.js | Extends InventorySources API model with instance-group endpoints via mixin. |
| awx/main/models/inventory.py | Updates InventoryUpdate.preferred_instance_groups precedence to consult source first. |
| awx/main/access.py | Adds attach/unattach permission checks for InventorySource.instance_groups. |
| awx/api/views/init.py | Introduces InventorySourceInstanceGroupsList sublist attach/detach view. |
| awx/api/urls/inventory_source.py | Routes /instance_groups/ under inventory sources. |
| awx/api/serializers.py | Exposes the new related link for inventory source instance groups. |
| awx/main/tests/functional/test_instances.py | Updates precedence tests for inventory update instance groups. |
| awx/main/tests/functional/test_rbac_instance_groups.py | Adds RBAC tests for inventory source attach/unattach behavior. |
| awx/main/tests/functional/api/test_instance_group.py | Extends order persistence tests and adds endpoint permission coverage. |
| @check_superuser | ||
| def can_attach(self, obj, sub_obj, relationship, data, skip_sub_obj_read_check=False): | ||
| if relationship == 'instance_groups': | ||
| if not obj.inventory: | ||
| return False | ||
| return self.user in sub_obj.use_role and self.user in obj.inventory.admin_role | ||
| return super(InventorySourceAccess, self).can_attach(obj, sub_obj, relationship, data, skip_sub_obj_read_check=skip_sub_obj_read_check) | ||
|
|
||
| @check_superuser | ||
| def can_unattach(self, obj, sub_obj, relationship, *args, **kwargs): | ||
| if relationship == 'instance_groups': | ||
| return self.can_attach(obj, sub_obj, relationship, *args, **kwargs) | ||
| return super(InventorySourceAccess, self).can_unattach(obj, sub_obj, relationship, *args, **kwargs) |
There was a problem hiding this comment.
Fixed in d42d734, though worth noting the origin: can_unattach() forwarding *args into a can_attach() that takes data as a required positional is the existing convention here, copied from JobTemplateAccess (access.py lines 1706 to 1717), so this is not something the PR introduces and no real caller hits it today. SubListAttachDetachAPIView always passes request.data positionally, and get_method_capability() passes data={} and only ever for the members/parents relationships.
Still, it costs two lines, so while we are here: the signature is now spelled out as can_unattach(self, obj, sub_obj, relationship, data=None, skip_sub_obj_read_check=False) and the delegation passes data explicitly. That also fixes the other half of the same problem, a skip_sub_obj_read_check kwarg for a non instance_groups relationship used to land in BaseAccess.can_unattach(), which does not accept it. Added an assertion in test_ig_inventory_source_associability that calls can_unattach() with no data so it stays that way.
InventorySourceAccess.can_unattach() forwarded *args to can_attach(), where data is a required positional argument, so a caller that omitted it (BaseAccess.can_unattach defaults it to None) got a TypeError instead of a permission decision. Same for skip_sub_obj_read_check, which ended up in BaseAccess.can_unattach, which does not take it. Spell out the signature instead and add a check that exercises the no-data call.
This comes from the forum thread https://forum.ascender-automation.org/d/17-feature-request-set-instance-group-of-inventory-sync (and upstream ansible/awx#13995, open since 2023 with no movement).
The problem
An inventory sync is a real job, the execution node spawns an EE and runs the inventory plugin inside it. So the node that runs the sync is the one that needs network access to the inventory source, not the control plane.
Today the only routing knob is
Inventory.instance_groups, and it does double duty: it decides where playbook jobs using that inventory run AND where the syncs of that inventory run. The failure mode from the forum: your hosts live in a remote DC only reachable from an execution node registered there, so you pin the remote instance group on the inventory. As a side effect the syncs also get scheduled on that remote node, which has no route back to the central system the source pulls from, and the sync fails. There is no way to decouple the two.You can work around it by setting the instance group on every job template and leaving the inventory alone, but that has to be repeated on every JT and a missed one silently falls back to a group that cannot reach the hosts.
What this does
Lets you assign instance groups directly to an inventory source. The resolution order for an inventory update becomes:
I went with per source rather than per inventory (which is what the forum post originally asked for) because one inventory can have several sources pointing at systems in different network segments. Per source degrades to exactly the per inventory behaviour when left empty, so it covers both.
A source with no instance groups behaves exactly as before, nothing changes for existing deployments. Playbook jobs ignore the new field entirely, it only affects inventory updates.
Implementation notes
The nice surprise is that
InventorySourcealready inheritsinstance_groupsfromUnifiedJobTemplate, the field and the through table have been in the schema all along. The API just never exposed it andInventoryUpdate.preferred_instance_groupsignored it (there was even a comment intest_instances.pysaying "API does not allow setting IGs on inventory source, so ignore those"). So this PR needs no migration at all.InventoryUpdate.preferred_instance_groupsnow consults the source first, then falls back to the existing chain. Same prepend semantics as job templates, andprevent_instance_group_fallbackon the inventory keeps working as it did./api/v2/inventory_sources/N/instance_groups/sublist, mirroringJobTemplateInstanceGroupsList, wired into the related links. Association order is preserved.docs/inventory_source_instance_groups.md.Tests
test_instances.py(source set, source empty, and withprevent_instance_group_fallback).InventorySourceAccessattach/unattach intest_rbac_instance_groups.py.inventory_sourceto the order persistence test inapi/test_instance_group.py, plus an endpoint test checking the 403 without use permission and the 204 with it.Full functional suite and the UI tests pass, black/flake8 clean,
check_migrationsreports no changes.