Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge stable into develop #5635

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -270,15 +270,15 @@ jobs:
with:
flag-name: backend-unit
parallel: true
- name: Generate tracing spans
if: always() && github.event.pull_request.head.repo.fork == false && github.actor != 'dependabot[bot]'
uses: inception-health/otel-upload-test-artifact-action@v1
with:
jobName: "backend-tests-unit"
stepName: "Unit Tests"
path: "pytest-junit.xml"
type: "junit"
githubToken: ${{ secrets.GH_TRACING_REPO_TOKEN }}
# - name: Generate tracing spans
# if: always() && github.event.pull_request.head.repo.fork == false && github.actor != 'dependabot[bot]'
# uses: inception-health/otel-upload-test-artifact-action@v1
# with:
# jobName: "backend-tests-unit"
# stepName: "Unit Tests"
# path: "pytest-junit.xml"
# type: "junit"
# githubToken: ${{ secrets.GH_TRACING_REPO_TOKEN }}

backend-tests-integration:
if: |
Expand Down Expand Up @@ -797,15 +797,15 @@ jobs:
- name: Run Playwright tests
run: npm run ci:test:e2e

- name: Generate tracing spans
if: always() && github.event.pull_request.head.repo.fork == false && github.actor != 'dependabot[bot]'
uses: inception-health/otel-upload-test-artifact-action@v1
with:
jobName: "E2E-testing-playwright"
stepName: "Run Playwright tests"
path: "frontend/app/playwright-junit.xml"
type: "junit"
githubToken: ${{ secrets.GH_TRACING_REPO_TOKEN }}
# - name: Generate tracing spans
# if: always() && github.event.pull_request.head.repo.fork == false && github.actor != 'dependabot[bot]'
# uses: inception-health/otel-upload-test-artifact-action@v1
# with:
# jobName: "E2E-testing-playwright"
# stepName: "Run Playwright tests"
# path: "frontend/app/playwright-junit.xml"
# type: "junit"
# githubToken: ${{ secrets.GH_TRACING_REPO_TOKEN }}

- name: playwright-report
if: always()
Expand Down
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 @@ -210,6 +210,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:
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:
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:
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:
)
)
)
// ------------------------
// 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 = InfrahubServices(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 = InfrahubServices(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.

Loading
Loading