Skip to content

Ansible content assignment system V2#91

Draft
Thorben-D wants to merge 6 commits into
mainfrom
assignment_redesign
Draft

Ansible content assignment system V2#91
Thorben-D wants to merge 6 commits into
mainfrom
assignment_redesign

Conversation

@Thorben-D
Copy link
Copy Markdown
Contributor

@Thorben-D Thorben-D commented Apr 30, 2026

The current implementation assigns a polymorphic AnsibleContentUnit to a polymorphic Consumer using the ID of the content unit and the ID of the consumer.
This works and is easy to implement, however, it is too rigid and inflexible.

The changes that will be made aim to adress this issue. Instead of an ID-ID mapping, the new implementation uses a fuzzy matching system, which will resolve the exact content dynamically.

Steps:

  • Core resolution algorithm
  • Structured error handling
  • Logging integration
  • Integration into the existing flows
  • Adaptation of Apidoc
  • Adaptation / Improvement of assignment UI
    • Base UI
    • Implementation on Hostgroups page
    • Implementation on Hostgroup create page
    • Implementation on Host create page
    • Implementation on Host details page
image image
  • Adaptation / Addition of tests
  • String review
  • Self review

Before this commit, Ansible content assignments used a
polymorphic association between an Ansible content unit
assignable (AnsibleCollectionRole, AnsibleRole) and a
consumable (Host, Hostgroup).
This was very rigid and incompatible with the dynamic
nature of LifecycleEnvironments.
When content in a LifecycleEnvironment changed, consumers
of said environment were not updated and tried to use
the content that was originally assigned.

In this commit, the assignment logic is redesigned to use
fuzzy matching and dynamic resolution of concrete Ansible
content for consumers.

Content can now be assigned to ContentResolutionNodes,
which represent objects in the "classic" inheritance chain
used elsewhere. Current ContentResolutionNodes are:
"Hostgroup" -> "Host".

AnsibleContentAssignment objects have been adapted to store
the following data:

- polymorphic consumable, representing the consumer
- assignable_namespace
- assignable_name
- assnignable_role_name
- assignable_type
- subtractive

This new AnsibleContentAssignment implementation, as
opposed to the previous one, which used IDs, uses the key
fact that these value only lack a version value to form a
composite primary key, which can uniquely identifiy an
assignable.

This fact is leveraged by the two-step resolution algorithm:

Step 1:

Resolve set of AnsibleContentAssignments for a
ContentResolutionNode.
In this step, the hierarchical structure of the content
inheritance chain is traversed from top (lowest rank) to
bottom (ContentResolutionNode we are trying to resolve
content for).
In this step, a set of unique AnsibleContentAssignments
is built, taking the "subtractive" attribute into account.

Step 2:

Map previously resolved set of AnsibleContentAssignments to
actual AnsibleContentUnit (and children) objects.
The first step in this step resolves the actual content source
used by the ContentResolutionNode, as this too can be
inherited from a parent. Here, the algorithm traverses the
chain from bottom (highest priority) to top (lowest priority),
and uses the first (transitively highest priority) content
source it finds.
It is an integral condition of a ContentSource, that it may
only supply Ansible content in a single version.
Mapping the assignable_* attributes to the AnsibleContentUnit
object in the set of content provided by ContentSource,
gives the version, which, in combination with the other
attributes, forms the composite primary key, giving a
single, non-ambiguous ContentUnitVersion.

ContentResolutionNode and ContentSource are abstract classes,
which may be implemented by any arbitrary object.
By implementing cr_immediate_predecessor, the resolution
hierarchy is implemented by the nodes themselves and can be
made dynamic with ease.
See for example Hostgroups, which can inherit attributes
in themselves.
Before this commit, select actions raised exceptions
on failure, which was caught by the global exception
handler.
While this was sufficient for some of the actions,
certain others, such as content resolution, can
(and sometimes do) fail with warnings or multiple
errors.

To address the need for multiple errors/warnings,
a per-request context, RequestContext, is added here.
This context uses the fact that the underlying
server, Puma, spawns a new thread per request.
As such, the context can be considered thread-global.

This context is available to the entire code
downstream of the called action, as long as it is
being executed on the same thread. This is the case
for the majority of the code and as such, the context
only requires special attention for DynFlow actions,
which was not done in this commit.

This context allows developers to add warnings and
errors to the current request in a similar fashion
to ActiveModel::Errors.

Downstream of the business logic, the RequestContext
is further used to construct the response for the
current request.
To unify response shapes across all actions of this
plugin, the following shape has been defined:

  {
    "status": "",
    "errors": [],
    "warnings": [],
    "logs": [],
    "changed": [],
    "created": [],
    "deleted": [],
    "results": {}
  }

The "status" value is determined automatically, based
on the logged errors/warnings and is either "success",
"error", or "warning".

This provides a consistent interface for the API, which
can be used by the UI to display the result of an action
in great detail.

TODO:

- use response.json.rabl across all actions
- gate changed, logs, created, deleted beind loglevel
@Thorben-D Thorben-D force-pushed the assignment_redesign branch from 142eef6 to 46d1c91 Compare May 4, 2026 14:49
Thorben-D added 2 commits May 11, 2026 16:07
Before this commit, the "Identifiable" interface typed
"id" as string when, in reality, id is a number.

This did not cause issues for most usages, as the
value was used for string interpolation, however
id-id comparisons failed because they tried comparing
"1" to 1, i.e. a number to a string.

This is fixed by correctly typing "id" as number.
@Thorben-D Thorben-D force-pushed the assignment_redesign branch from 46d1c91 to b4cda99 Compare May 13, 2026 16:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant