Skip to content

Allow assigning instance groups to inventory sources - #600

Merged
cigamit merged 2 commits into
ctrliq:mainfrom
fernandorocagonzalez:feat-invsrc-instance-groups
Jul 28, 2026
Merged

Allow assigning instance groups to inventory sources#600
cigamit merged 2 commits into
ctrliq:mainfrom
fernandorocagonzalez:feat-invsrc-instance-groups

Conversation

@fernandorocagonzalez

Copy link
Copy Markdown
Contributor

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:

inventory_source.instance_groups > inventory.instance_groups > organization.instance_groups > global default

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 InventorySource already inherits instance_groups from UnifiedJobTemplate, the field and the through table have been in the schema all along. The API just never exposed it and InventoryUpdate.preferred_instance_groups ignored it (there was even a comment in test_instances.py saying "API does not allow setting IGs on inventory source, so ignore those"). So this PR needs no migration at all.

  • InventoryUpdate.preferred_instance_groups now consults the source first, then falls back to the existing chain. Same prepend semantics as job templates, and prevent_instance_group_fallback on the inventory keeps working as it did.
  • New /api/v2/inventory_sources/N/instance_groups/ sublist, mirroring JobTemplateInstanceGroupsList, wired into the related links. Association order is preserved.
  • RBAC: attaching needs use permission on the instance group plus admin on the inventory, the same rule as job templates, inventories and organizations.
  • UI: instance groups lookup on the inventory source form (add and edit), labels on the detail view.
  • Docs: docs/inventory_source_instance_groups.md.

Tests

  • Updated the precedence test in test_instances.py (source set, source empty, and with prevent_instance_group_fallback).
  • New parametrized RBAC test for InventorySourceAccess attach/unattach in test_rbac_instance_groups.py.
  • Added inventory_source to the order persistence test in api/test_instance_group.py, plus an endpoint test checking the 403 without use permission and the 204 with it.
  • Jest tests for the form, add, edit and detail screens.

Full functional suite and the UI tests pass, black/flake8 clean, check_migrations reports no changes.

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.
@cigamit
cigamit requested a review from Copilot July 28, 2026 06:35
@cigamit cigamit self-assigned this Jul 28, 2026
@cigamit cigamit added the enhancement New feature or request label Jul 28, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_groups ahead 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.

Comment thread awx/main/access.py Outdated
Comment on lines +1120 to +1132
@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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@cigamit
cigamit merged commit ef905b6 into ctrliq:main Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Development

Successfully merging this pull request may close these issues.

3 participants