opsmill / infrahub

Infrahub - A new approach to Infrastructure Management
https://opsmill.com/
GNU Affero General Public License v3.0
185 stars 8 forks source link

bug: Attribute uniqueness constraints are not caught by constraint validators #4025

Closed ogenstad closed 1 month ago

ogenstad commented 1 month ago

Component

API Server / GraphQL, Git Integration

Infrahub version

0.15.2

Current Behavior

Uniqueness constraints for node attributes aren't properly handled within the CI for a proposed change, as a result it's possible to merge a proposed change where the change would introduce a violation within the main branch.

Expected Behavior

When you have a model with an attribute uniqueness constraint such as .name on BuiltinTag it should not be possible to merge a proposed change if you have created a new branch and then created the same tag both in that branch and in the main branch. However this is currently possible.

Steps to Reproduce

  1. Create a branch: branch1
  2. Within that branch create a BuiltinTag give it the name "blue".
  3. Switch to the main branch and create a BuiltinTag with the same name "blue".
  4. Create a proposed change
  5. Note that the pipeline doesn't report any checks failing
  6. Merge the branch (this should not be possible)
  7. Note that the main branch now has two tags called "blue".

Additional Information

The query that finds these nodes look like this:


MATCH (potential_node:Node)
WHERE $node_kind IN LABELS(potential_node)
CALL {
WITH potential_node
MATCH potential_path = (potential_node)-[:HAS_ATTRIBUTE]-(:Attribute { name: $attr_name })-[potential_value_relationship:HAS_VALUE]-(potential_value:AttributeValue)
WHERE all(r IN relationships(potential_path) WHERE (((r.branch IN $branch0 AND r.from <= $time0 AND r.to IS NULL)
OR (r.branch IN $branch0 AND r.from <= $time0 AND r.to >= $time0)
OR (r.branch IN $branch1 AND r.from <= $time1 AND r.to IS NULL)
OR (r.branch IN $branch1 AND r.from <= $time1 AND r.to >= $time1))))
WITH
potential_node,
potential_value,
potential_value_relationship,
potential_path,
reduce(br_lvl = 0, r in relationships(potential_path) | br_lvl + r.branch_level) AS branch_level,
[i IN relationships(potential_path) | i.from] AS from_times
RETURN potential_node as node, potential_value as attribute_value, potential_path as path, potential_value_relationship as value_relationship
ORDER BY branch_level DESC, from_times[-1] DESC, from_times[-2] DESC
LIMIT 1
}
WITH node, attribute_value, path, value_relationship
WHERE all(r IN relationships(path) WHERE (r.status = "active"))
WITH
collect([node, value_relationship]) as nodes_and_value_relationships,
count(*) as node_count,
attribute_value
ORDER BY attribute_value.value
UNWIND nodes_and_value_relationships as node_and_value_relationship
RETURN node_count,attribute_value.value as value,node_and_value_relationship[0] as node,node_and_value_relationship[1] as value_relationship

The problem seems to be that $time0 contains the timestamp from when branch1 was created as such we will never see any entries within the main branch that were created after the branch was created.

ogenstad commented 1 month ago

Fixed in #4026.