Skip to content

Merge stable into develop #442

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

Merged
merged 31 commits into from
Jun 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9249a6f
Add branch parameter to clone methods
ogenstad Apr 23, 2025
23d9f1a
Merge pull request #373 from opsmill/pog-client-clone-branch-param-IF…
ogenstad Apr 24, 2025
268ebed
Merge pull request #375 from opsmill/stable
ogenstad Apr 25, 2025
0a0b540
1.11.1 release (#378)
lykinsbd Apr 28, 2025
cfbb217
Add ability to use convert_query_response with Python Transforms
ogenstad Apr 24, 2025
5e3e379
Merge pull request #376 from opsmill/pog-transform-convert-query-resp…
ogenstad Apr 29, 2025
5aa8f84
add repository dispatch workflow triggering updates in other repos
wvandeun Apr 29, 2025
2002d38
v1.12 prep
lykinsbd Apr 29, 2025
dd86efa
Merge branch 'stable' into bdl-20250430-conflict-resolution
lykinsbd Apr 30, 2025
44a0f15
Merge Stable into Develop
lykinsbd Apr 30, 2025
21ba2f3
Merge pull request #388 from opsmill/stable
lykinsbd Apr 30, 2025
db6957b
Merge pull request #394 from opsmill/stable
ogenstad May 14, 2025
3a1d72f
Split node.py into multiple files
dgarros May 12, 2025
aa8c792
Expose everything from node module
dgarros May 12, 2025
5a27695
Move parse_human_friendly_id into parsers.py
dgarros May 17, 2025
216a6c6
Merge pull request #409 from opsmill/dga-20250512-node
dgarros May 17, 2025
d7ec69f
Bump DavidAnson/markdownlint-cli2-action from 19 to 20
dependabot[bot] May 19, 2025
4d0c684
Merge pull request #414 from opsmill/dependabot/github_actions/develo…
dgarros May 19, 2025
f6f7842
Trigger markdown lint on github action updates & fix rules
ogenstad May 21, 2025
95b2491
Merge pull request #419 from opsmill/pog-fix-markdownlint
ogenstad May 21, 2025
5ad92f6
Add NumberPool kind
ogenstad May 21, 2025
7bda36c
Merge pull request #416 from opsmill/pog-number-pool
ogenstad May 21, 2025
49116c8
Merge pull request #417 from opsmill/stable
ogenstad May 26, 2025
f414027
Add objects field within InfrahubConfig (#422)
LucasG0 May 26, 2025
0d8fca2
Add object import to docs
minitriga May 27, 2025
55d3d84
Refactor InfrahubNode to avoid dynamic class creation
ogenstad May 17, 2025
dd16f8d
Merge pull request #412 from opsmill/pog-avoid-dynamic-class-creation
ogenstad Jun 4, 2025
82b5e1c
Merge pull request #428 from opsmill/stable
ogenstad Jun 5, 2025
1efe5de
Loading objects keeps subfolders order (#426)
LucasG0 Jun 5, 2025
79f3e93
Merge pull request #424 from opsmill/atg-20250527-object-docs
wvandeun Jun 10, 2025
b8186de
Merge pull request #432 from opsmill/stable
ogenstad Jun 10, 2025
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
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,17 @@ jobs:


markdown-lint:
if: needs.files-changed.outputs.documentation == 'true'
if: |
needs.files-changed.outputs.documentation == 'true' ||
needs.files-changed.outputs.github_workflows == 'true'
needs: ["files-changed"]
runs-on: "ubuntu-latest"
timeout-minutes: 5
steps:
- name: "Check out repository code"
uses: "actions/checkout@v4"
- name: "Linting: markdownlint"
uses: DavidAnson/markdownlint-cli2-action@v19
uses: DavidAnson/markdownlint-cli2-action@v20
with:
config: .markdownlint.yaml
globs: |
Expand Down
2 changes: 2 additions & 0 deletions .markdownlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ MD034: false # no-bare-urls
MD041: false # allow 1st line to not be a top-level heading (required for Towncrier)
MD045: false # no alt text around images
MD047: false # single trailing newline
MD059: # Link descriptions that are prohibited
prohibited_texts: []
1 change: 1 addition & 0 deletions changelog/+040fc56b.housekeeping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Refactor InfrahubNode to avoid the creation of a dynamic Python class for each object defined
1 change: 1 addition & 0 deletions changelog/+543efbd1.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added NumberPool as a new attribute kind, for support in Infrahub 1.3
2 changes: 2 additions & 0 deletions docs/docs/python-sdk/topics/object_file.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Multiple object files can be loaded at once by specifying the path to multiple f
The `object load` command will create/update the objects using an `Upsert` operation. All objects previously loaded will NOT be deleted in the Infrahub instance.
Also, if some objects present in different files are identical and dependent on each other, the `object load` command will NOT calculate the dependencies between the objects and as such it's the responsibility of the users to execute the command in the right order.

> Object files can also be loaded into Infrahub when using external Git repositories. To see how to do this, please refer to the [.infrahub.yml](https://docs.infrahub.app/topics/infrahub-yml#objects) documentation.

### Validate the format of object files

The object file can be validated using the `infrahubctl object validate` command.
Expand Down
19 changes: 17 additions & 2 deletions infrahub_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,18 @@ def start_tracking(
params: dict[str, Any] | None = None,
delete_unused_nodes: bool = False,
group_type: str | None = None,
group_params: dict[str, Any] | None = None,
branch: str | None = None,
) -> Self:
self.mode = InfrahubClientMode.TRACKING
identifier = identifier or self.identifier or "python-sdk"
self.set_context_properties(
identifier=identifier, params=params, delete_unused_nodes=delete_unused_nodes, group_type=group_type
identifier=identifier,
params=params,
delete_unused_nodes=delete_unused_nodes,
group_type=group_type,
group_params=group_params,
branch=branch,
)
return self

Expand All @@ -187,14 +194,22 @@ def set_context_properties(
delete_unused_nodes: bool = True,
reset: bool = True,
group_type: str | None = None,
group_params: dict[str, Any] | None = None,
branch: str | None = None,
) -> None:
if reset:
if isinstance(self, InfrahubClient):
self.group_context = InfrahubGroupContext(self)
elif isinstance(self, InfrahubClientSync):
self.group_context = InfrahubGroupContextSync(self)

self.group_context.set_properties(
identifier=identifier, params=params, delete_unused_nodes=delete_unused_nodes, group_type=group_type
identifier=identifier,
params=params,
delete_unused_nodes=delete_unused_nodes,
group_type=group_type,
group_params=group_params,
branch=branch,
)

def _graphql_url(
Expand Down
8 changes: 4 additions & 4 deletions infrahub_sdk/ctl/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,20 +74,20 @@ async def run(
targets = await client.get(
kind="CoreGroup", branch=branch, include=["members"], name__value=generator_config.targets
)
await targets.members.fetch()
await targets._get_relationship_many(name="members").fetch()

if not targets.members.peers:
if not targets._get_relationship_many(name="members").peers:
console.print(
f"[red]No members found within '{generator_config.targets}', not running generator '{generator_name}'"
)
return

for member in targets.members.peers:
for member in targets._get_relationship_many(name="members").peers:
check_parameter = {}
if identifier:
attribute = getattr(member.peer, identifier)
check_parameter = {identifier: attribute.value}
params = {"name": member.peer.name.value}
params = {"name": member.peer._get_attribute(name="name").value}
generator = generator_class(
query=generator_config.query,
client=client,
Expand Down
39 changes: 39 additions & 0 deletions infrahub_sdk/node/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from __future__ import annotations

from .constants import (
ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE,
ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE,
ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE,
HFID_STR_SEPARATOR,
IP_TYPES,
PROPERTIES_FLAG,
PROPERTIES_OBJECT,
SAFE_VALUE,
)
from .node import InfrahubNode, InfrahubNodeBase, InfrahubNodeSync
from .parsers import parse_human_friendly_id
from .property import NodeProperty
from .related_node import RelatedNode, RelatedNodeBase, RelatedNodeSync
from .relationship import RelationshipManager, RelationshipManagerBase, RelationshipManagerSync

__all__ = [
"ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE",
"ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE",
"ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE",
"HFID_STR_SEPARATOR",
"IP_TYPES",
"PROPERTIES_FLAG",
"PROPERTIES_OBJECT",
"SAFE_VALUE",
"InfrahubNode",
"InfrahubNodeBase",
"InfrahubNodeSync",
"NodeProperty",
"RelatedNode",
"RelatedNodeBase",
"RelatedNodeSync",
"RelationshipManager",
"RelationshipManagerBase",
"RelationshipManagerSync",
"parse_human_friendly_id",
]
122 changes: 122 additions & 0 deletions infrahub_sdk/node/attribute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from __future__ import annotations

import ipaddress
from typing import TYPE_CHECKING, Any, Callable, get_args

from ..protocols_base import CoreNodeBase
from ..uuidt import UUIDT
from .constants import IP_TYPES, PROPERTIES_FLAG, PROPERTIES_OBJECT, SAFE_VALUE
from .property import NodeProperty

if TYPE_CHECKING:
from ..schema import AttributeSchemaAPI


class Attribute:
"""Represents an attribute of a Node, including its schema, value, and properties."""

def __init__(self, name: str, schema: AttributeSchemaAPI, data: Any | dict):
"""
Args:
name (str): The name of the attribute.
schema (AttributeSchema): The schema defining the attribute.
data (Union[Any, dict]): The data for the attribute, either in raw form or as a dictionary.
"""
self.name = name
self._schema = schema

if not isinstance(data, dict) or "value" not in data.keys():
data = {"value": data}

self._properties_flag = PROPERTIES_FLAG
self._properties_object = PROPERTIES_OBJECT
self._properties = self._properties_flag + self._properties_object

self._read_only = ["updated_at", "is_inherited"]

self.id: str | None = data.get("id", None)

self._value: Any | None = data.get("value", None)
self.value_has_been_mutated = False
self.is_default: bool | None = data.get("is_default", None)
self.is_from_profile: bool | None = data.get("is_from_profile", None)

if self._value:
value_mapper: dict[str, Callable] = {
"IPHost": ipaddress.ip_interface,
"IPNetwork": ipaddress.ip_network,
}
mapper = value_mapper.get(schema.kind, lambda value: value)
self._value = mapper(data.get("value"))

self.is_inherited: bool | None = data.get("is_inherited", None)
self.updated_at: str | None = data.get("updated_at", None)

self.is_visible: bool | None = data.get("is_visible", None)
self.is_protected: bool | None = data.get("is_protected", None)

self.source: NodeProperty | None = None
self.owner: NodeProperty | None = None

for prop_name in self._properties_object:
if data.get(prop_name):
setattr(self, prop_name, NodeProperty(data=data.get(prop_name))) # type: ignore[arg-type]

@property
def value(self) -> Any:
return self._value

@value.setter
def value(self, value: Any) -> None:
self._value = value
self.value_has_been_mutated = True

def _generate_input_data(self) -> dict | None:
data: dict[str, Any] = {}
variables: dict[str, Any] = {}

if self.value is None:
return data

if isinstance(self.value, str):
if SAFE_VALUE.match(self.value):
data["value"] = self.value
else:
var_name = f"value_{UUIDT.new().hex}"
variables[var_name] = self.value
data["value"] = f"${var_name}"
elif isinstance(self.value, get_args(IP_TYPES)):
data["value"] = self.value.with_prefixlen
elif isinstance(self.value, CoreNodeBase) and self.value.is_resource_pool():
data["from_pool"] = {"id": self.value.id}
else:
data["value"] = self.value

for prop_name in self._properties_flag:
if getattr(self, prop_name) is not None:
data[prop_name] = getattr(self, prop_name)

for prop_name in self._properties_object:
if getattr(self, prop_name) is not None:
data[prop_name] = getattr(self, prop_name)._generate_input_data()

return {"data": data, "variables": variables}

def _generate_query_data(self, property: bool = False) -> dict | None:
data: dict[str, Any] = {"value": None}

if property:
data.update({"is_default": None, "is_from_profile": None})

for prop_name in self._properties_flag:
data[prop_name] = None
for prop_name in self._properties_object:
data[prop_name] = {"id": None, "display_label": None, "__typename": None}

return data

def _generate_mutation_query(self) -> dict[str, Any]:
if isinstance(self.value, CoreNodeBase) and self.value.is_resource_pool():
# If it points to a pool, ask for the value of the pool allocated resource
return {self.name: {"value": None}}
return {}
21 changes: 21 additions & 0 deletions infrahub_sdk/node/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import ipaddress
import re
from typing import Union

PROPERTIES_FLAG = ["is_visible", "is_protected"]
PROPERTIES_OBJECT = ["source", "owner"]
SAFE_VALUE = re.compile(r"(^[\. /:a-zA-Z0-9_-]+$)|(^$)")

IP_TYPES = Union[ipaddress.IPv4Interface, ipaddress.IPv6Interface, ipaddress.IPv4Network, ipaddress.IPv6Network]

ARTIFACT_FETCH_FEATURE_NOT_SUPPORTED_MESSAGE = (
"calling artifact_fetch is only supported for nodes that are Artifact Definition target"
)
ARTIFACT_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE = (
"calling artifact_generate is only supported for nodes that are Artifact Definition targets"
)
ARTIFACT_DEFINITION_GENERATE_FEATURE_NOT_SUPPORTED_MESSAGE = (
"calling generate is only supported for CoreArtifactDefinition nodes"
)

HFID_STR_SEPARATOR = "__"
Loading