Skip to content

Commit

Permalink
Merge pull request #5651 from opsmill/pog-stable-into-develop-20250202
Browse files Browse the repository at this point in the history
Merge stable back into develop with resolved pyproject conflicts
  • Loading branch information
dgarros authored Feb 3, 2025
2 parents 73ae42d + f67c876 commit 0fc9497
Show file tree
Hide file tree
Showing 42 changed files with 351 additions and 569 deletions.
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,37 @@ This project uses [*towncrier*](https://towncrier.readthedocs.io/) and the chang

<!-- towncrier release notes start -->

## [Infrahub - v1.1.6](https://github.com/opsmill/infrahub/tree/infrahub-v1.1.6) - 2025-01-30

### Artifact improvements

As part of our ongoing efforts to enhance the integrations and capabilities of Infrahub, the Artifact detail page has been redesigned.

This redesign focused on allowing a richer and more powerful Artifact experience.
Enhancements include support for additional content-types (as listed below), colorized syntax highlighting, and easier access to download or copy artifacts.

**Supported Artifact Content Types**:

- Markdown
- YAML
- JSON
- Text
- SVG

### Added

- Allow Default Address Type quick selection in the Resource Manager form ([#3489](https://github.com/opsmill/infrahub/issues/3489))
- Added code viewer for new content-types, preview of raw markdown content, one-click file download or cop, and redesign of artifact details view ([#5452](https://github.com/opsmill/infrahub/issues/5452))

### Fixed

- Automatically mark hierarchical nodes `parent` relationship as optional if the parent is of the same kind or mandatory if the parent is of a different kind ([#3682](https://github.com/opsmill/infrahub/issues/3682))
- Revert back to `state=open` from `state=merging` if the merge of a proposed change fails.
This fixes the possibility of leaving a proposed change in an unexpected state. ([#5563](https://github.com/opsmill/infrahub/issues/5563))
- Fixes an issue with retrieving object from S3 storage backend. ([#5573](https://github.com/opsmill/infrahub/issues/5573))
- Loosened requirement for group discovery using OIDC and id_token. This will probably be reverted or presented as a configuration option in the future. ([#5623](https://github.com/opsmill/infrahub/issues/5623))
- Significant improvements to diff calculation performance.

## [Infrahub - v1.1.5](https://github.com/opsmill/infrahub/tree/infrahub-v1.1.5) - 2025-01-24

### Added
Expand Down
1 change: 1 addition & 0 deletions backend/infrahub/api/oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ async def _get_id_token_groups(
algorithms=oidc_config.id_token_signing_alg_values_supported,
audience=client_id,
issuer=str(oidc_config.issuer),
options={"verify_signature": False, "verify_aud": False, "verify_iss": False},
)

return decoded_token.get("groups", [])
4 changes: 4 additions & 0 deletions backend/infrahub/core/diff/model/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,10 @@ def root_id(self) -> str:
def node_id(self) -> str:
return str(self.node_node.get("uuid"))

@property
def node_db_id(self) -> str:
return self.node_node.element_id

@property
def node_kind(self) -> str:
return str(self.node_node.get("kind"))
Expand Down
19 changes: 19 additions & 0 deletions backend/infrahub/core/diff/query_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,9 @@ class DiffNodeIntermediate(TrackedStatusUpdates):
force_action: DiffAction | None
uuid: str
kind: str
db_id: str
from_time: Timestamp
status: RelationshipStatus
attributes_by_name: dict[str, DiffAttributeIntermediate] = field(default_factory=dict)
relationships_by_name: dict[str, DiffRelationshipIntermediate] = field(default_factory=dict)

Expand Down Expand Up @@ -567,11 +570,27 @@ def _get_diff_node(self, database_path: DatabasePath, diff_root: DiffRootInterme
diff_root.nodes_by_id[node_id] = DiffNodeIntermediate(
uuid=node_id,
kind=database_path.node_kind,
db_id=database_path.node_db_id,
from_time=database_path.node_changed_at,
status=database_path.node_status,
force_action=DiffAction.UPDATED
if database_path.node_branch_support is BranchSupportType.AGNOSTIC
else None,
)
diff_node = diff_root.nodes_by_id[node_id]
# special handling for nodes that have their kind updated, which results in 2 nodes with the same uuid
if diff_node.db_id != database_path.node_db_id and (
database_path.node_changed_at > diff_node.from_time
or (
database_path.node_changed_at >= diff_node.from_time
and (diff_node.status, database_path.node_status)
== (RelationshipStatus.DELETED, RelationshipStatus.ACTIVE)
)
):
diff_node.kind = database_path.node_kind
diff_node.db_id = database_path.node_db_id
diff_node.from_time = database_path.node_changed_at
diff_node.status = database_path.node_status
diff_node.track_database_path(database_path=database_path)
return diff_node

Expand Down
41 changes: 39 additions & 2 deletions backend/infrahub/core/query/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ def __init__(
r in relationships(latest_base_path)
WHERE r.from < $branch_from_time
)
// ------------------------
// special handling for nodes that had their kind updated,
// the migration leaves two nodes with the same UUID linked to the same Relationship
// ------------------------
AND (
n.uuid IS NULL OR base_prop.uuid IS NULL OR n.uuid <> base_prop.uuid
OR type(base_r_node) <> "IS_RELATED" OR type(base_r_prop) <> "IS_RELATED"
)
WITH latest_base_path, base_r_root, base_r_node, base_r_prop
ORDER BY base_r_prop.from DESC, base_r_node.from DESC, base_r_root.from DESC
LIMIT 1
Expand Down Expand Up @@ -175,8 +183,13 @@ def __init__(
OR peer_r_node.to IS NULL
OR peer_r_node.to >= r_peer.from
)
// ------------------------
// special handling for nodes that had their kind updated,
// the migration leaves two nodes with the same UUID linked to the same Relationship
// ------------------------
AND (n.uuid IS NULL OR peer.uuid IS NULL OR n.uuid <> peer.uuid)
WITH peer_path, r_peer, r_prop
ORDER BY r_peer.branch = r_prop.branch DESC, r_peer.from DESC
ORDER BY r_peer.branch = r_prop.branch DESC, r_peer.from DESC, r_peer.status ASC
LIMIT 1
RETURN peer_path
}
Expand Down Expand Up @@ -309,6 +322,14 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa
AND [%(id_func)s(p), type(r_node)] <> [%(id_func)s(prop), type(r_prop)]
AND top_diff_rel.status = r_node.status
AND top_diff_rel.status = r_prop.status
// ------------------------
// special handling for nodes that had their kind updated,
// the migration leaves two nodes with the same UUID linked to the same Relationship
// ------------------------
AND (
p.uuid IS NULL OR prop.uuid IS NULL OR p.uuid <> prop.uuid
OR type(r_node) <> "IS_RELATED" OR type(r_prop) <> "IS_RELATED"
)
WITH path, p, node, prop, r_prop, r_node, type(r_node) AS rel_type, row_from_time
// -------------------------------------
// Exclude attributes/relationships added then removed on branch within timeframe
Expand Down Expand Up @@ -483,6 +504,14 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa
AND [%(id_func)s(p), type(mid_diff_rel)] <> [%(id_func)s(prop), type(r_prop)]
// exclude paths where an active edge is below a deleted edge
AND (mid_diff_rel.status = "active" OR r_prop.status = "deleted")
// ------------------------
// special handling for nodes that had their kind updated,
// the migration leaves two nodes with the same UUID linked to the same Relationship
// ------------------------
AND (
p.uuid IS NULL OR prop.uuid IS NULL OR p.uuid <> prop.uuid
OR type(mid_diff_rel) <> "IS_RELATED" OR type(r_prop) <> "IS_RELATED"
)
WITH path, prop, r_prop, mid_r_root
ORDER BY
type(r_prop),
Expand All @@ -493,7 +522,7 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa
RETURN latest_prop_path
}
// -------------------------------------
// Exclude properties within the timeframe
// Exclude properties added and deleted within the timeframe
// -------------------------------------
WITH q, nodes(latest_prop_path)[3] AS prop, type(relationships(latest_prop_path)[2]) AS rel_type, latest_prop_path, has_more_data, row_from_time
CALL {
Expand Down Expand Up @@ -594,6 +623,14 @@ async def query_init(self, db: InfrahubDatabase, **kwargs: Any) -> None: # noqa
)
)
)
// ------------------------
// special handling for nodes that had their kind updated,
// the migration leaves two nodes with the same UUID linked to the same Relationship
// ------------------------
AND (
n.uuid IS NULL OR q.uuid IS NULL OR n.uuid <> q.uuid
OR type(r_node) <> "IS_RELATED" OR type(diff_rel) <> "IS_RELATED"
)
AND ALL(
r_pair IN [[r_root, r_node], [r_node, diff_rel]]
// filter out paths where a base branch edge follows a branch edge
Expand Down
31 changes: 31 additions & 0 deletions backend/tests/unit/api/test_oidc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import time
import uuid
from copy import deepcopy
from typing import Any

import httpx
Expand Down Expand Up @@ -40,6 +41,36 @@ async def test_get_id_token_groups_for_oidc() -> None:
assert groups == ["operators"]


async def test_get_id_token_groups_for_oidc_invalid_issuer() -> None:
memory_http = MemoryHTTP()
service = await InfrahubServices.new(http=memory_http)
client_id = "testing-oicd-1234"

helper = OIDCTestHelper()
token_response = helper.generate_token_response(
username="testuser",
groups=["operators"],
client_id=client_id,
issuer=str(OIDC_CONFIG.issuer),
)

memory_http.add_get_response(
url=str(OIDC_CONFIG.jwks_uri),
response=httpx.Response(status_code=200, content=json.dumps(helper.jwks_payload)),
)
config = deepcopy(OIDC_CONFIG)
config.issuer = Url("https://something-incorrect.example.com")

groups = await _get_id_token_groups(
oidc_config=config,
service=service,
payload=token_response,
client_id=client_id,
)

assert groups == ["operators"]


async def test_get_id_token_groups_for_oidc_no_id_token() -> None:
memory_http = MemoryHTTP()
service = await InfrahubServices.new(http=memory_http)
Expand Down
74 changes: 72 additions & 2 deletions backend/tests/unit/core/diff/test_diff_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
from infrahub import config
from infrahub.core import registry
from infrahub.core.branch import Branch
from infrahub.core.constants import DiffAction, InfrahubKind, RelationshipCardinality
from infrahub.core.constants import DiffAction, InfrahubKind, RelationshipCardinality, SchemaPathType
from infrahub.core.constants.database import DatabaseEdgeType
from infrahub.core.diff.calculator import DiffCalculator
from infrahub.core.initialization import create_branch
from infrahub.core.manager import NodeManager
from infrahub.core.migrations.schema.node_kind_update import NodeKindUpdateMigration
from infrahub.core.node import Node
from infrahub.core.path import SchemaPath
from infrahub.core.schema.schema_branch import SchemaBranch
from infrahub.core.timestamp import Timestamp
from infrahub.database import InfrahubDatabase
Expand Down Expand Up @@ -1308,7 +1310,6 @@ async def test_relationship_property_owner_conflicting_updates(
# check branch
branch_root_path = calculated_diffs.diff_branch_diff
assert branch_root_path.branch == branch.name
assert len(branch_root_path.nodes) == 2
nodes_by_id = {n.uuid: n for n in branch_root_path.nodes}
assert set(nodes_by_id.keys()) == {person_john_main.get_id(), car_accord_main.get_id()}
# john node on branch
Expand Down Expand Up @@ -3077,3 +3078,72 @@ async def test_diff_relationship_property_update_on_main(
assert diff_rel.name == "owner"
assert diff_rel.action is DiffAction.UPDATED
assert {elem.action for elem in diff_rel.relationships} == {DiffAction.ADDED, DiffAction.REMOVED}


async def test_calculate_with_migrated_kind_node(
db: InfrahubDatabase, default_branch: Branch, car_accord_main, car_camry_main, person_john_main, person_jane_main
):
"""Test that the diff can correctly handle a schema kind migration, which results in 2 nodes with the same UUID"""
branch = await create_branch(db=db, branch_name="branch")
branch_car = await Node.init(db=db, schema="TestCar", branch=branch)
await branch_car.new(db=db, name="nova", nbr_seats=2, is_electric=False, owner=person_jane_main.id)
await branch_car.save(db=db)
schema = registry.schema.get_schema_branch(name=default_branch.name)
car_schema = schema.get(name="TestCar")
car_schema.name = "NewCar"
car_schema.namespace = "Test2"
assert car_schema.kind == "Test2NewCar"
owner_rel_schema = car_schema.get_relationship(name="owner")
registry.schema.set(name="Test2NewCar", schema=car_schema, branch=branch.name)

migration = NodeKindUpdateMigration(
previous_node_schema=schema.get(name="TestCar"),
new_node_schema=car_schema,
schema_path=SchemaPath(path_type=SchemaPathType.ATTRIBUTE, schema_kind="Test2NewCar", field_name="namespace"),
)
execution_result = await migration.execute(db=db, branch=branch)
assert not execution_result.errors

diff_calculator = DiffCalculator(db=db)

calculated_diffs = await diff_calculator.calculate_diff(
base_branch=default_branch,
diff_branch=branch,
from_time=Timestamp(branch.get_branched_from()),
to_time=Timestamp(),
previous_node_specifiers={
car_accord_main.id: {owner_rel_schema.get_identifier()},
car_camry_main.id: {owner_rel_schema.get_identifier()},
},
include_unchanged=True,
)

branch_diff = calculated_diffs.diff_branch_diff
assert len(branch_diff.nodes) == 2
nodes_by_id = {n.uuid: n for n in branch_diff.nodes}
assert set(nodes_by_id.keys()) == {branch_car.id, person_jane_main.id}
# validate relationship on person is correct
jane_diff = nodes_by_id[person_jane_main.id]
assert jane_diff.action is DiffAction.UPDATED
assert len(jane_diff.attributes) == 0
rels_by_name = {r.name: r for r in jane_diff.relationships}
assert set(rels_by_name.keys()) == {"cars"}
cars_rel_diff = rels_by_name["cars"]
assert cars_rel_diff.action is DiffAction.UPDATED
elements_by_peer_id = {e.peer_id: e for e in cars_rel_diff.relationships}
assert set(elements_by_peer_id) == {branch_car.id}
element_diff = elements_by_peer_id[branch_car.id]
assert element_diff.action is DiffAction.ADDED
props_by_type = {p.property_type: p for p in element_diff.properties}
for property_type, value in (
(DatabaseEdgeType.IS_RELATED, branch_car.id),
(DatabaseEdgeType.IS_VISIBLE, True),
(DatabaseEdgeType.IS_PROTECTED, False),
):
property_diff = props_by_type[property_type]
assert property_diff.action is DiffAction.ADDED
assert property_diff.new_value == value
assert property_diff.previous_value is None

base_diff = calculated_diffs.base_branch_diff
assert len(base_diff.nodes) == 0
1 change: 0 additions & 1 deletion changelog/+diff-perf.fixed.md

This file was deleted.

1 change: 0 additions & 1 deletion changelog/3489.added.md

This file was deleted.

1 change: 0 additions & 1 deletion changelog/3682.fixed.md

This file was deleted.

1 change: 0 additions & 1 deletion changelog/5452.added.md

This file was deleted.

3 changes: 0 additions & 3 deletions changelog/5563.fixed.md

This file was deleted.

1 change: 0 additions & 1 deletion changelog/5573.fixed.md

This file was deleted.

4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ services:
retries: 5

infrahub-server:
image: "${INFRAHUB_DOCKER_IMAGE:-registry.opsmill.io/opsmill/infrahub}:${VERSION:-1.1.5}"
image: "${INFRAHUB_DOCKER_IMAGE:-registry.opsmill.io/opsmill/infrahub}:${VERSION:-1.1.6}"
restart: unless-stopped
command: >
gunicorn --config backend/infrahub/serve/gunicorn_config.py
Expand Down Expand Up @@ -251,7 +251,7 @@ services:
deploy:
mode: replicated
replicas: 2
image: "${INFRAHUB_DOCKER_IMAGE:-registry.opsmill.io/opsmill/infrahub}:${VERSION:-1.1.5}"
image: "${INFRAHUB_DOCKER_IMAGE:-registry.opsmill.io/opsmill/infrahub}:${VERSION:-1.1.6}"
command: prefect worker start --type infrahubasync --pool infrahub-worker --with-healthcheck
restart: unless-stopped
depends_on:
Expand Down
2 changes: 0 additions & 2 deletions docs/docs/faq/faq.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,6 @@ Yes! Infrahub Enterprise is deployed in diverse infrastructure environments with

Infrahub is designed to be the source of truth for your infrastructure data and configurations. It does not directly connect to or deploy configurations to your infrastructure devices. Instead, Infrahub integrates with tools like Nornir, Terraform and Ansible, which can be used to deploy the generated configurations to your infrastructure.

Please see the documentation on [integrations](../integrations) for more information.

### Does Infrahub send telemetry? And how can I disable it?

Infrahub collects anonymous usage metrics and sends them to OpsMill servers for analysis and improvement purposes. No personally identifiable information is collected. If you prefer to opt-out of telemetry collection, you can do so by setting the `INFRAHUB_TELEMETRY_OPTOUT` environment variable to `true`:
Expand Down
13 changes: 0 additions & 13 deletions docs/docs/integrations/infrahub-ansible/readme.mdx

This file was deleted.

11 changes: 0 additions & 11 deletions docs/docs/integrations/nornir-infrahub/readme.mdx

This file was deleted.

Loading

0 comments on commit 0fc9497

Please sign in to comment.