Ansible content assignment system V2#91
Draft
Thorben-D wants to merge 6 commits into
Draft
Conversation
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
142eef6 to
46d1c91
Compare
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.
46d1c91 to
b4cda99
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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: