infrahub-server 1.2.2__py3-none-any.whl → 1.2.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. infrahub/cli/git_agent.py +4 -10
  2. infrahub/computed_attribute/tasks.py +8 -8
  3. infrahub/config.py +35 -0
  4. infrahub/core/constants/__init__.py +1 -0
  5. infrahub/core/constraint/node/runner.py +6 -5
  6. infrahub/core/graph/__init__.py +1 -1
  7. infrahub/core/migrations/graph/__init__.py +6 -1
  8. infrahub/core/migrations/graph/m018_uniqueness_nulls.py +68 -70
  9. infrahub/core/migrations/graph/m024_missing_hierarchy_backfill.py +69 -0
  10. infrahub/core/migrations/graph/m025_uniqueness_nulls.py +26 -0
  11. infrahub/core/migrations/schema/node_attribute_remove.py +16 -2
  12. infrahub/core/models.py +7 -1
  13. infrahub/core/node/__init__.py +4 -4
  14. infrahub/core/node/constraints/grouped_uniqueness.py +30 -10
  15. infrahub/core/query/ipam.py +1 -1
  16. infrahub/core/registry.py +18 -0
  17. infrahub/core/schema/basenode_schema.py +21 -1
  18. infrahub/core/schema/definitions/internal.py +2 -1
  19. infrahub/core/schema/generated/base_node_schema.py +1 -1
  20. infrahub/core/schema/manager.py +21 -0
  21. infrahub/core/schema/schema_branch.py +17 -9
  22. infrahub/database/__init__.py +10 -0
  23. infrahub/events/group_action.py +6 -1
  24. infrahub/events/node_action.py +5 -1
  25. infrahub/git/integrator.py +9 -7
  26. infrahub/graphql/mutations/main.py +10 -12
  27. infrahub/menu/repository.py +6 -6
  28. infrahub/message_bus/messages/__init__.py +0 -2
  29. infrahub/message_bus/operations/__init__.py +0 -1
  30. infrahub/message_bus/operations/event/__init__.py +2 -2
  31. infrahub/server.py +6 -11
  32. infrahub/services/adapters/cache/__init__.py +17 -0
  33. infrahub/services/adapters/cache/redis.py +11 -1
  34. infrahub/services/adapters/message_bus/__init__.py +20 -0
  35. infrahub/services/component.py +1 -2
  36. infrahub/tasks/registry.py +3 -7
  37. infrahub/workers/infrahub_async.py +4 -10
  38. infrahub_sdk/client.py +6 -6
  39. infrahub_sdk/ctl/cli_commands.py +32 -37
  40. infrahub_sdk/ctl/render.py +39 -0
  41. infrahub_sdk/exceptions.py +6 -2
  42. infrahub_sdk/generator.py +1 -1
  43. infrahub_sdk/node.py +38 -11
  44. infrahub_sdk/protocols_base.py +8 -1
  45. infrahub_sdk/pytest_plugin/items/jinja2_transform.py +22 -26
  46. infrahub_sdk/schema/__init__.py +10 -1
  47. infrahub_sdk/store.py +351 -75
  48. infrahub_sdk/template/__init__.py +209 -0
  49. infrahub_sdk/template/exceptions.py +38 -0
  50. infrahub_sdk/template/filters.py +151 -0
  51. infrahub_sdk/template/models.py +10 -0
  52. infrahub_sdk/utils.py +7 -0
  53. {infrahub_server-1.2.2.dist-info → infrahub_server-1.2.4.dist-info}/METADATA +2 -1
  54. {infrahub_server-1.2.2.dist-info → infrahub_server-1.2.4.dist-info}/RECORD +61 -59
  55. infrahub_testcontainers/container.py +6 -0
  56. infrahub_testcontainers/docker-compose.test.yml +1 -0
  57. infrahub_testcontainers/haproxy.cfg +3 -3
  58. infrahub_testcontainers/helpers.py +1 -1
  59. infrahub/message_bus/messages/event_worker_newprimaryapi.py +0 -9
  60. infrahub/message_bus/operations/event/worker.py +0 -9
  61. infrahub/support/__init__.py +0 -0
  62. infrahub/support/macro.py +0 -69
  63. {infrahub_server-1.2.2.dist-info → infrahub_server-1.2.4.dist-info}/LICENSE.txt +0 -0
  64. {infrahub_server-1.2.2.dist-info → infrahub_server-1.2.4.dist-info}/WHEEL +0 -0
  65. {infrahub_server-1.2.2.dist-info → infrahub_server-1.2.4.dist-info}/entry_points.txt +0 -0
@@ -11,7 +11,7 @@ from infrahub_sdk.utils import compare_lists, intersection
11
11
  from pydantic import field_validator
12
12
 
13
13
  from infrahub.core.constants import RelationshipCardinality, RelationshipKind
14
- from infrahub.core.models import HashableModelDiff
14
+ from infrahub.core.models import HashableModel, HashableModelDiff
15
15
 
16
16
  from .attribute_schema import AttributeSchema
17
17
  from .generated.base_node_schema import GeneratedBaseNodeSchema
@@ -27,6 +27,16 @@ if TYPE_CHECKING:
27
27
  NODE_METADATA_ATTRIBUTES = ["_source", "_owner"]
28
28
  INHERITED = "INHERITED"
29
29
 
30
+ OPTIONAL_TEXT_FIELDS = [
31
+ "default_filter",
32
+ "description",
33
+ "label",
34
+ "menu_placement",
35
+ "documentation",
36
+ "parent",
37
+ "children",
38
+ ]
39
+
30
40
 
31
41
  class BaseNodeSchema(GeneratedBaseNodeSchema):
32
42
  _exclude_from_hash: list[str] = ["attributes", "relationships"]
@@ -480,6 +490,16 @@ class BaseNodeSchema(GeneratedBaseNodeSchema):
480
490
  return UniquenessConstraintType.SUBSET_OF_HFID
481
491
  return UniquenessConstraintType.STANDARD
482
492
 
493
+ def update(self, other: HashableModel) -> Self:
494
+ super().update(other=other)
495
+
496
+ # Allow to specify empty string to remove existing fields values
497
+ for field_name in OPTIONAL_TEXT_FIELDS:
498
+ if getattr(other, field_name, None) == "": # noqa: PLC1901
499
+ setattr(self, field_name, None)
500
+
501
+ return self
502
+
483
503
 
484
504
  @dataclass
485
505
  class SchemaUniquenessConstraintPath:
@@ -18,6 +18,7 @@ from infrahub.core.constants import (
18
18
  DEFAULT_NAME_MIN_LENGTH,
19
19
  DEFAULT_REL_IDENTIFIER_LENGTH,
20
20
  NAME_REGEX,
21
+ NAME_REGEX_OR_EMPTY,
21
22
  NAMESPACE_REGEX,
22
23
  NODE_KIND_REGEX,
23
24
  NODE_NAME_REGEX,
@@ -269,7 +270,7 @@ base_node_schema = SchemaNode(
269
270
  SchemaAttribute(
270
271
  name="default_filter",
271
272
  kind="Text",
272
- regex=str(NAME_REGEX),
273
+ regex=str(NAME_REGEX_OR_EMPTY),
273
274
  description="Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead)",
274
275
  optional=True,
275
276
  extra={"update": UpdateSupport.ALLOWED},
@@ -50,7 +50,7 @@ class GeneratedBaseNodeSchema(HashableModel):
50
50
  default_filter: str | None = Field(
51
51
  default=None,
52
52
  description="Default filter used to search for a node in addition to its ID. (deprecated: please use human_friendly_id instead)",
53
- pattern=r"^[a-z0-9\_]+$",
53
+ pattern=r"^[a-z0-9\_]*$",
54
54
  json_schema_extra={"update": "allowed"},
55
55
  )
56
56
  human_friendly_id: list[str] | None = Field(
@@ -744,3 +744,24 @@ class SchemaManager(NodeManager):
744
744
  """Convert a schema_node object loaded from the database into GenericSchema object."""
745
745
  node_data = await cls._prepare_node_data(schema_node=schema_node, db=db)
746
746
  return GenericSchema(**node_data)
747
+
748
+ def purge_inactive_branches(self, active_branches: list[str]) -> list[str]:
749
+ """Return non active branches that were purged."""
750
+
751
+ hashes_to_keep: set[str] = set()
752
+ for active_branch in active_branches:
753
+ if branch := self._branches.get(active_branch):
754
+ nodes = branch.get_all(include_internal=True, duplicate=False)
755
+ hashes_to_keep.update([node.get_hash() for node in nodes.values()])
756
+
757
+ removed_branches: list[str] = []
758
+ for branch_name in list(self._branches.keys()):
759
+ if branch_name not in active_branches:
760
+ del self._branches[branch_name]
761
+ removed_branches.append(branch_name)
762
+
763
+ for hash_key in list(self._cache.keys()):
764
+ if hash_key not in hashes_to_keep:
765
+ del self._cache[hash_key]
766
+
767
+ return removed_branches
@@ -6,6 +6,8 @@ from collections import defaultdict
6
6
  from itertools import chain, combinations
7
7
  from typing import Any
8
8
 
9
+ from infrahub_sdk.template import Jinja2Template
10
+ from infrahub_sdk.template.exceptions import JinjaTemplateError, JinjaTemplateOperationViolationError
9
11
  from infrahub_sdk.topological_sort import DependencyCycleExistsError, topological_sort
10
12
  from infrahub_sdk.utils import compare_lists, deep_merge_dict, duplicates, intersection
11
13
  from typing_extensions import Self
@@ -51,7 +53,6 @@ from infrahub.core.schema.definitions.core import core_profile_schema_definition
51
53
  from infrahub.core.validators import CONSTRAINT_VALIDATOR_MAP
52
54
  from infrahub.exceptions import SchemaNotFoundError, ValidationError
53
55
  from infrahub.log import get_logger
54
- from infrahub.support.macro import MacroDefinition
55
56
  from infrahub.types import ATTRIBUTE_TYPES
56
57
  from infrahub.utils import format_label
57
58
  from infrahub.visuals import select_color
@@ -1037,14 +1038,22 @@ class SchemaBranch:
1037
1038
  | SchemaElementPathType.REL_ONE_MANDATORY_ATTR_WITH_PROP
1038
1039
  | SchemaElementPathType.REL_ONE_ATTR_WITH_PROP
1039
1040
  )
1041
+
1042
+ jinja_template = Jinja2Template(template=attribute.computed_attribute.jinja2_template)
1040
1043
  try:
1041
- macro = MacroDefinition(macro=attribute.computed_attribute.jinja2_template)
1042
- except ValueError as exc:
1044
+ variables = jinja_template.get_variables()
1045
+ jinja_template.validate(restricted=config.SETTINGS.security.restrict_untrusted_jinja2_filters)
1046
+ except JinjaTemplateOperationViolationError as exc:
1047
+ raise ValueError(
1048
+ f"{node.kind}: Attribute {attribute.name!r} is assigned by a jinja2 template, but has an invalid template: {exc.message}"
1049
+ ) from exc
1050
+
1051
+ except JinjaTemplateError as exc:
1043
1052
  raise ValueError(
1044
- f"{node.kind}: Attribute {attribute.name!r} is assigned by a jinja2 template, but has an invalid template"
1053
+ f"{node.kind}: Attribute {attribute.name!r} is assigned by a jinja2 template, but has an invalid template: : {exc.message}"
1045
1054
  ) from exc
1046
1055
 
1047
- for variable in macro.variables:
1056
+ for variable in variables:
1048
1057
  try:
1049
1058
  schema_path = self.validate_schema_path(
1050
1059
  node_schema=node, path=variable, allowed_path_types=allowed_path_types
@@ -1946,10 +1955,9 @@ class SchemaBranch:
1946
1955
  )
1947
1956
  )
1948
1957
 
1949
- if relationship.kind == RelationshipKind.PARENT:
1950
- template_schema.human_friendly_id = [
1951
- f"{relationship.name}__template_name__value"
1952
- ] + template_schema.human_friendly_id
1958
+ parent_hfid = f"{relationship.name}__template_name__value"
1959
+ if relationship.kind == RelationshipKind.PARENT and parent_hfid not in template_schema.human_friendly_id:
1960
+ template_schema.human_friendly_id = [parent_hfid] + template_schema.human_friendly_id
1953
1961
  template_schema.uniqueness_constraints[0].append(relationship.name)
1954
1962
 
1955
1963
  def generate_object_template_from_node(
@@ -205,6 +205,16 @@ class InfrahubDatabase:
205
205
  def add_schema(self, schema: SchemaBranch, name: str | None = None) -> None:
206
206
  self._schemas[name or schema.name] = schema
207
207
 
208
+ def purge_inactive_schemas(self, active_branches: list[str]) -> list[str]:
209
+ """Return non active schema branches that were purged."""
210
+ removed_branches: list[str] = []
211
+ for branch_name in list(self._schemas.keys()):
212
+ if branch_name not in active_branches:
213
+ del self._schemas[branch_name]
214
+ removed_branches.append(branch_name)
215
+
216
+ return removed_branches
217
+
208
218
  def start_session(self, read_only: bool = False, schemas: list[SchemaBranch] | None = None) -> InfrahubDatabase:
209
219
  """Create a new InfrahubDatabase object in Session mode."""
210
220
  session_mode = InfrahubDatabaseSessionMode.WRITE
@@ -2,7 +2,7 @@ from typing import ClassVar
2
2
 
3
3
  from pydantic import Field
4
4
 
5
- from infrahub.core.constants import MutationAction
5
+ from infrahub.core.constants import InfrahubKind, MutationAction
6
6
 
7
7
  from .constants import EVENT_NAMESPACE
8
8
  from .models import EventNode, InfrahubEvent
@@ -21,6 +21,11 @@ class GroupMutatedEvent(InfrahubEvent):
21
21
 
22
22
  def get_related(self) -> list[dict[str, str]]:
23
23
  related = super().get_related()
24
+
25
+ if self.kind == InfrahubKind.GRAPHQLQUERYGROUP:
26
+ # Temporary workaround to avoid too large payloads for the related field
27
+ return related
28
+
24
29
  related.append(
25
30
  {
26
31
  "prefect.resource.id": self.node_id,
@@ -7,7 +7,7 @@ from infrahub.core.changelog.models import (
7
7
  RelationshipCardinalityManyChangelog,
8
8
  RelationshipCardinalityOneChangelog,
9
9
  )
10
- from infrahub.core.constants import DiffAction, MutationAction
10
+ from infrahub.core.constants import DiffAction, InfrahubKind, MutationAction
11
11
 
12
12
  from .constants import EVENT_NAMESPACE
13
13
  from .models import InfrahubEvent
@@ -24,6 +24,10 @@ class NodeMutatedEvent(InfrahubEvent):
24
24
 
25
25
  def get_related(self) -> list[dict[str, str]]:
26
26
  related = super().get_related()
27
+ if self.kind == InfrahubKind.GRAPHQLQUERYGROUP:
28
+ # Temporary workaround to avoid too large payloads for the related field
29
+ return related
30
+
27
31
  for attribute in self.changelog.attributes.values():
28
32
  related.append(
29
33
  {
@@ -3,9 +3,9 @@ from __future__ import annotations
3
3
  import hashlib
4
4
  import importlib
5
5
  import sys
6
+ from pathlib import Path
6
7
  from typing import TYPE_CHECKING, Any
7
8
 
8
- import jinja2
9
9
  import ujson
10
10
  import yaml
11
11
  from infrahub_sdk import InfrahubClient # noqa: TC002
@@ -28,6 +28,8 @@ from infrahub_sdk.schema.repository import (
28
28
  InfrahubPythonTransformConfig,
29
29
  InfrahubRepositoryConfig,
30
30
  )
31
+ from infrahub_sdk.template import Jinja2Template
32
+ from infrahub_sdk.template.exceptions import JinjaTemplateError
31
33
  from infrahub_sdk.utils import compare_lists
32
34
  from infrahub_sdk.yaml import SchemaFile
33
35
  from prefect import flow, task
@@ -1057,14 +1059,14 @@ class InfrahubRepositoryIntegrator(InfrahubRepositoryBase):
1057
1059
 
1058
1060
  self.validate_location(commit=commit, worktree_directory=commit_worktree.directory, file_path=location)
1059
1061
 
1062
+ jinja2_template = Jinja2Template(template=Path(location), template_directory=Path(commit_worktree.directory))
1060
1063
  try:
1061
- templateLoader = jinja2.FileSystemLoader(searchpath=commit_worktree.directory)
1062
- templateEnv = jinja2.Environment(loader=templateLoader, trim_blocks=True, lstrip_blocks=True)
1063
- template = templateEnv.get_template(location)
1064
- return template.render(**data)
1065
- except Exception as exc:
1064
+ return await jinja2_template.render(variables=data)
1065
+ except JinjaTemplateError as exc:
1066
1066
  log.error(str(exc), exc_info=True)
1067
- raise TransformError(repository_name=self.name, commit=commit, location=location, message=str(exc)) from exc
1067
+ raise TransformError(
1068
+ repository_name=self.name, commit=commit, location=location, message=exc.message
1069
+ ) from exc
1068
1070
 
1069
1071
  @task(name="python-check-execute", task_run_name="Execute Python Check", cache_policy=NONE) # type: ignore[arg-type]
1070
1072
  async def execute_python_check(
@@ -363,7 +363,7 @@ class InfrahubMutationMixin:
363
363
  branch: Branch,
364
364
  db: InfrahubDatabase,
365
365
  obj: Node,
366
- run_constraint_checks: bool = True,
366
+ skip_uniqueness_check: bool = False,
367
367
  ) -> tuple[Node, Self]:
368
368
  """
369
369
  Wrapper around mutate_update to potentially activate locking and call it within a database transaction.
@@ -378,11 +378,11 @@ class InfrahubMutationMixin:
378
378
  if lock_names:
379
379
  async with InfrahubMultiLock(lock_registry=lock.registry, locks=lock_names):
380
380
  obj = await cls.mutate_update_object(
381
- db=db, info=info, data=data, branch=branch, obj=obj, run_constraint_checks=run_constraint_checks
381
+ db=db, info=info, data=data, branch=branch, obj=obj, skip_uniqueness_check=skip_uniqueness_check
382
382
  )
383
383
  else:
384
384
  obj = await cls.mutate_update_object(
385
- db=db, info=info, data=data, branch=branch, obj=obj, run_constraint_checks=run_constraint_checks
385
+ db=db, info=info, data=data, branch=branch, obj=obj, skip_uniqueness_check=skip_uniqueness_check
386
386
  )
387
387
  result = await cls.mutate_update_to_graphql(db=db, info=info, obj=obj)
388
388
  return obj, result
@@ -396,11 +396,11 @@ class InfrahubMutationMixin:
396
396
  data=data,
397
397
  branch=branch,
398
398
  obj=obj,
399
- run_constraint_checks=run_constraint_checks,
399
+ skip_uniqueness_check=skip_uniqueness_check,
400
400
  )
401
401
  else:
402
402
  obj = await cls.mutate_update_object(
403
- db=dbt, info=info, data=data, branch=branch, obj=obj, run_constraint_checks=run_constraint_checks
403
+ db=dbt, info=info, data=data, branch=branch, obj=obj, skip_uniqueness_check=skip_uniqueness_check
404
404
  )
405
405
  result = await cls.mutate_update_to_graphql(db=dbt, info=info, obj=obj)
406
406
  return obj, result
@@ -434,7 +434,7 @@ class InfrahubMutationMixin:
434
434
  data: InputObjectType,
435
435
  branch: Branch,
436
436
  obj: Node,
437
- run_constraint_checks: bool = True,
437
+ skip_uniqueness_check: bool = False,
438
438
  ) -> Node:
439
439
  component_registry = get_component_registry()
440
440
  node_constraint_runner = await component_registry.get_component(NodeConstraintRunner, db=db, branch=branch)
@@ -442,8 +442,9 @@ class InfrahubMutationMixin:
442
442
  before_mutate_profile_ids = await cls._get_profile_ids(db=db, obj=obj)
443
443
  await obj.from_graphql(db=db, data=data)
444
444
  fields_to_validate = list(data)
445
- if run_constraint_checks:
446
- await node_constraint_runner.check(node=obj, field_filters=fields_to_validate)
445
+ await node_constraint_runner.check(
446
+ node=obj, field_filters=fields_to_validate, skip_uniqueness_check=skip_uniqueness_check
447
+ )
447
448
 
448
449
  fields = list(data.keys())
449
450
  for field_to_remove in ("id", "hfid"):
@@ -494,7 +495,6 @@ class InfrahubMutationMixin:
494
495
  db = database or graphql_context.db
495
496
  dict_data = dict(data)
496
497
  node = None
497
- run_constraint_checks = True
498
498
 
499
499
  if "id" in dict_data:
500
500
  node = await NodeManager.get_one(
@@ -506,7 +506,6 @@ class InfrahubMutationMixin:
506
506
  db=db,
507
507
  branch=branch,
508
508
  obj=node,
509
- run_constraint_checks=run_constraint_checks,
510
509
  )
511
510
  return updated_obj, mutation, False
512
511
 
@@ -525,7 +524,6 @@ class InfrahubMutationMixin:
525
524
  db=db,
526
525
  branch=branch,
527
526
  obj=node,
528
- run_constraint_checks=run_constraint_checks,
529
527
  )
530
528
  return updated_obj, mutation, False
531
529
 
@@ -545,7 +543,7 @@ class InfrahubMutationMixin:
545
543
  db=db,
546
544
  branch=branch,
547
545
  obj=node,
548
- run_constraint_checks=run_constraint_checks,
546
+ skip_uniqueness_check=True,
549
547
  )
550
548
  return updated_obj, mutation, False
551
549
 
@@ -20,9 +20,9 @@ class MenuRepository:
20
20
  async def add_children(menu_item: MenuItemDict, menu_node: CoreMenuItem) -> MenuItemDict:
21
21
  children = await menu_node.children.get_peers(db=self.db, peer_type=CoreMenuItem)
22
22
  for child_id, child_node in children.items():
23
- child_menu_item = menu_by_ids[child_id]
24
- child = await add_children(child_menu_item, child_node)
25
- menu_item.children[str(child.identifier)] = child
23
+ if child_menu_item := menu_by_ids.get(child_id):
24
+ child = await add_children(child_menu_item, child_node)
25
+ menu_item.children[str(child.identifier)] = child
26
26
  return menu_item
27
27
 
28
28
  for menu_node in nodes.values():
@@ -33,9 +33,9 @@ class MenuRepository:
33
33
 
34
34
  children = await menu_node.children.get_peers(db=self.db, peer_type=CoreMenuItem)
35
35
  for child_id, child_node in children.items():
36
- child_menu_item = menu_by_ids[child_id]
37
- child = await add_children(child_menu_item, child_node)
38
- menu_item.children[str(child.identifier)] = child
36
+ if child_menu_item := menu_by_ids.get(child_id):
37
+ child = await add_children(child_menu_item, child_node)
38
+ menu_item.children[str(child.identifier)] = child
39
39
 
40
40
  menu.data[str(menu_item.identifier)] = menu_item
41
41
 
@@ -2,7 +2,6 @@ from infrahub.message_bus import InfrahubMessage, InfrahubResponse
2
2
 
3
3
  from .check_generator_run import CheckGeneratorRun
4
4
  from .event_branch_merge import EventBranchMerge
5
- from .event_worker_newprimaryapi import EventWorkerNewPrimaryAPI
6
5
  from .finalize_validator_execution import FinalizeValidatorExecution
7
6
  from .git_file_get import GitFileGet, GitFileGetResponse
8
7
  from .git_repository_connectivity import GitRepositoryConnectivity
@@ -17,7 +16,6 @@ from .send_echo_request import SendEchoRequest, SendEchoRequestResponse
17
16
  MESSAGE_MAP: dict[str, type[InfrahubMessage]] = {
18
17
  "check.generator.run": CheckGeneratorRun,
19
18
  "event.branch.merge": EventBranchMerge,
20
- "event.worker.new_primary_api": EventWorkerNewPrimaryAPI,
21
19
  "finalize.validator.execution": FinalizeValidatorExecution,
22
20
  "git.file.get": GitFileGet,
23
21
  "git.repository.connectivity": GitRepositoryConnectivity,
@@ -18,7 +18,6 @@ from infrahub.tasks.check import set_check_status
18
18
  COMMAND_MAP = {
19
19
  "check.generator.run": check.generator.run,
20
20
  "event.branch.merge": event.branch.merge,
21
- "event.worker.new_primary_api": event.worker.new_primary_api,
22
21
  "finalize.validator.execution": finalize.validator.execution,
23
22
  "git.file.get": git.file.get,
24
23
  "git.repository.connectivity": git.repository.connectivity,
@@ -1,3 +1,3 @@
1
- from . import branch, worker
1
+ from . import branch
2
2
 
3
- __all__ = ["branch", "worker"]
3
+ __all__ = ["branch"]
infrahub/server.py CHANGED
@@ -33,10 +33,8 @@ from infrahub.lock import initialize_lock
33
33
  from infrahub.log import clear_log_context, get_logger, set_log_data
34
34
  from infrahub.middleware import InfrahubCORSMiddleware
35
35
  from infrahub.services import InfrahubServices
36
- from infrahub.services.adapters.cache.nats import NATSCache
37
- from infrahub.services.adapters.cache.redis import RedisCache
38
- from infrahub.services.adapters.message_bus.nats import NATSMessageBus
39
- from infrahub.services.adapters.message_bus.rabbitmq import RabbitMQMessageBus
36
+ from infrahub.services.adapters.cache import InfrahubCache
37
+ from infrahub.services.adapters.message_bus import InfrahubMessageBus
40
38
  from infrahub.services.adapters.workflow.local import WorkflowLocalExecution
41
39
  from infrahub.services.adapters.workflow.worker import WorkflowWorkerExecution
42
40
  from infrahub.trace import add_span_exception, configure_trace, get_traceid
@@ -70,14 +68,11 @@ async def app_initialization(application: FastAPI, enable_scheduler: bool = True
70
68
  else WorkflowLocalExecution()
71
69
  )
72
70
  component_type = ComponentType.API_SERVER
73
- message_bus = config.OVERRIDE.message_bus or (
74
- await NATSMessageBus.new(component_type=component_type)
75
- if config.SETTINGS.broker.driver == config.BrokerDriver.NATS
76
- else await RabbitMQMessageBus.new(component_type=component_type)
77
- )
78
- cache = config.OVERRIDE.cache or (
79
- await NATSCache.new() if config.SETTINGS.cache.driver == config.CacheDriver.NATS else RedisCache()
71
+ message_bus = config.OVERRIDE.message_bus or await InfrahubMessageBus.new_from_driver(
72
+ component_type=component_type, driver=config.SETTINGS.broker.driver
80
73
  )
74
+
75
+ cache = config.OVERRIDE.cache or (await InfrahubCache.new_from_driver(driver=config.SETTINGS.cache.driver))
81
76
  service = await InfrahubServices.new(
82
77
  cache=cache,
83
78
  database=database,
@@ -1,9 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import importlib
3
4
  from abc import ABC, abstractmethod
4
5
  from typing import TYPE_CHECKING
5
6
 
6
7
  if TYPE_CHECKING:
8
+ from infrahub.config import CacheDriver
7
9
  from infrahub.message_bus.types import KVTTL
8
10
 
9
11
 
@@ -34,3 +36,18 @@ class InfrahubCache(ABC):
34
36
  async def set(self, key: str, value: str, expires: KVTTL | None = None, not_exists: bool = False) -> bool | None:
35
37
  """Set a value in the cache."""
36
38
  raise NotImplementedError()
39
+
40
+ @classmethod
41
+ async def new_from_driver(cls, driver: CacheDriver) -> InfrahubCache:
42
+ """Imports and initializes the correct class based on the supplied driver.
43
+
44
+ This is to ensure that we only import the Python modules that we actually
45
+ need to operate and not import all possible options.
46
+ """
47
+ module = importlib.import_module(driver.driver_module_path)
48
+ broker_driver: InfrahubCache = getattr(module, driver.driver_class_name)
49
+ return await broker_driver.new()
50
+
51
+ @classmethod
52
+ async def new(cls) -> InfrahubCache:
53
+ raise NotImplementedError()
@@ -1,9 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
1
5
  import redis.asyncio as redis
2
6
 
3
7
  from infrahub import config
4
- from infrahub.message_bus.types import KVTTL
5
8
  from infrahub.services.adapters.cache import InfrahubCache
6
9
 
10
+ if TYPE_CHECKING:
11
+ from infrahub.message_bus.types import KVTTL
12
+
7
13
 
8
14
  class RedisCache(InfrahubCache):
9
15
  def __init__(self) -> None:
@@ -44,3 +50,7 @@ class RedisCache(InfrahubCache):
44
50
 
45
51
  async def set(self, key: str, value: str, expires: KVTTL | None = None, not_exists: bool = False) -> bool | None:
46
52
  return await self.connection.set(name=key, value=value, ex=expires.value if expires else None, nx=not_exists)
53
+
54
+ @classmethod
55
+ async def new(cls) -> RedisCache:
56
+ return cls()
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import importlib
3
4
  from abc import ABC, abstractmethod
4
5
  from typing import TYPE_CHECKING, TypeVar
5
6
 
@@ -8,6 +9,8 @@ from infrahub.message_bus.messages import ROUTING_KEY_MAP
8
9
  ResponseClass = TypeVar("ResponseClass")
9
10
 
10
11
  if TYPE_CHECKING:
12
+ from infrahub.components import ComponentType
13
+ from infrahub.config import BrokerDriver, BrokerSettings
11
14
  from infrahub.message_bus import InfrahubMessage, InfrahubResponse
12
15
  from infrahub.message_bus.types import MessageTTL
13
16
  from infrahub.services import InfrahubServices
@@ -34,6 +37,23 @@ class InfrahubMessageBus(ABC):
34
37
  async def shutdown(self) -> None: # noqa: B027 We want a default empty behavior, so it's ok to have an empty non-abstract method.
35
38
  """Shutdown the Message bus"""
36
39
 
40
+ @classmethod
41
+ async def new(cls, component_type: ComponentType, settings: BrokerSettings | None = None) -> InfrahubMessageBus:
42
+ raise NotImplementedError()
43
+
44
+ @classmethod
45
+ async def new_from_driver(
46
+ cls, component_type: ComponentType, driver: BrokerDriver, settings: BrokerSettings | None = None
47
+ ) -> InfrahubMessageBus:
48
+ """Imports and initializes the correct class based on the supplied driver.
49
+
50
+ This is to ensure that we only import the Python modules that we actually
51
+ need to operate and not import all possible options.
52
+ """
53
+ module = importlib.import_module(driver.driver_module_path)
54
+ broker_driver: InfrahubMessageBus = getattr(module, driver.driver_class_name)
55
+ return await broker_driver.new(component_type=component_type, settings=settings)
56
+
37
57
  @abstractmethod
38
58
  async def publish(
39
59
  self, message: InfrahubMessage, routing_key: str, delay: MessageTTL | None = None, is_retry: bool = False
@@ -10,7 +10,6 @@ from infrahub.core.constants import GLOBAL_BRANCH_NAME
10
10
  from infrahub.core.registry import registry
11
11
  from infrahub.core.timestamp import Timestamp
12
12
  from infrahub.log import get_logger
13
- from infrahub.message_bus import messages
14
13
  from infrahub.message_bus.types import KVTTL
15
14
  from infrahub.worker import WORKER_IDENTITY
16
15
 
@@ -116,7 +115,7 @@ class InfrahubComponent:
116
115
  key=PRIMARY_API_SERVER, value=WORKER_IDENTITY, expires=KVTTL.FIFTEEN, not_exists=True
117
116
  )
118
117
  if result:
119
- await self.message_bus.send(message=messages.EventWorkerNewPrimaryAPI(worker_id=WORKER_IDENTITY))
118
+ log.info("api_worker promoted to primary", worker_id=WORKER_IDENTITY)
120
119
  else:
121
120
  log.debug("Primary node already set")
122
121
  primary_id = await self.cache.get(key=PRIMARY_API_SERVER)
@@ -22,7 +22,6 @@ async def refresh_branches(db: InfrahubDatabase) -> None:
22
22
 
23
23
  async with lock.registry.local_schema_lock():
24
24
  branches = await registry.branch_object.get_list(db=db)
25
- active_branches = [branch.name for branch in branches]
26
25
  for new_branch in branches:
27
26
  if new_branch.name in registry.branch:
28
27
  branch_registry: Branch = registry.branch[new_branch.name]
@@ -61,9 +60,6 @@ async def refresh_branches(db: InfrahubDatabase) -> None:
61
60
  include_types=True,
62
61
  )
63
62
 
64
- for branch_name in list(registry.branch.keys()):
65
- if branch_name not in active_branches:
66
- del registry.branch[branch_name]
67
- log.info(
68
- f"Removed branch {branch_name!r} from the registry", branch=branch_name, worker=WORKER_IDENTITY
69
- )
63
+ purged_branches = await registry.purge_inactive_branches(db=db, active_branches=branches)
64
+ for branch_name in purged_branches:
65
+ log.info(f"Removed branch {branch_name!r} from the registry", branch=branch_name, worker=WORKER_IDENTITY)
@@ -24,11 +24,7 @@ from infrahub.git import initialize_repositories_directory
24
24
  from infrahub.lock import initialize_lock
25
25
  from infrahub.services import InfrahubServices
26
26
  from infrahub.services.adapters.cache import InfrahubCache
27
- from infrahub.services.adapters.cache.nats import NATSCache
28
- from infrahub.services.adapters.cache.redis import RedisCache
29
27
  from infrahub.services.adapters.message_bus import InfrahubMessageBus
30
- from infrahub.services.adapters.message_bus.nats import NATSMessageBus
31
- from infrahub.services.adapters.message_bus.rabbitmq import RabbitMQMessageBus
32
28
  from infrahub.services.adapters.workflow import InfrahubWorkflow
33
29
  from infrahub.services.adapters.workflow.local import WorkflowLocalExecution
34
30
  from infrahub.services.adapters.workflow.worker import WorkflowWorkerExecution
@@ -198,15 +194,13 @@ class InfrahubWorkerAsync(BaseWorker):
198
194
 
199
195
  async def _init_message_bus(self, component_type: ComponentType) -> InfrahubMessageBus:
200
196
  return config.OVERRIDE.message_bus or (
201
- await NATSMessageBus.new(component_type=component_type)
202
- if config.SETTINGS.broker.driver == config.BrokerDriver.NATS
203
- else await RabbitMQMessageBus.new(component_type=component_type)
197
+ await InfrahubMessageBus.new_from_driver(
198
+ component_type=component_type, driver=config.SETTINGS.broker.driver
199
+ )
204
200
  )
205
201
 
206
202
  async def _init_cache(self) -> InfrahubCache:
207
- return config.OVERRIDE.cache or (
208
- await NATSCache.new() if config.SETTINGS.cache.driver == config.CacheDriver.NATS else RedisCache()
209
- )
203
+ return config.OVERRIDE.cache or (await InfrahubCache.new_from_driver(driver=config.SETTINGS.cache.driver))
210
204
 
211
205
  async def _init_services(self, client: InfrahubClient) -> None:
212
206
  component_type = ComponentType.GIT_AGENT
infrahub_sdk/client.py CHANGED
@@ -281,7 +281,7 @@ class InfrahubClient(BaseClient):
281
281
  self.schema = InfrahubSchema(self)
282
282
  self.branch = InfrahubBranchManager(self)
283
283
  self.object_store = ObjectStore(self)
284
- self.store = NodeStore()
284
+ self.store = NodeStore(default_branch=self.default_branch)
285
285
  self.task = InfrahubTaskManager(self)
286
286
  self.concurrent_execution_limit = asyncio.Semaphore(self.max_concurrent_execution)
287
287
  self._request_method: AsyncRequester = self.config.requester or self._default_request_method
@@ -840,11 +840,11 @@ class InfrahubClient(BaseClient):
840
840
  if populate_store:
841
841
  for node in nodes:
842
842
  if node.id:
843
- self.store.set(key=node.id, node=node)
843
+ self.store.set(node=node)
844
844
  related_nodes = list(set(related_nodes))
845
845
  for node in related_nodes:
846
846
  if node.id:
847
- self.store.set(key=node.id, node=node)
847
+ self.store.set(node=node)
848
848
  return nodes
849
849
 
850
850
  def clone(self) -> InfrahubClient:
@@ -1529,7 +1529,7 @@ class InfrahubClientSync(BaseClient):
1529
1529
  self.schema = InfrahubSchemaSync(self)
1530
1530
  self.branch = InfrahubBranchManagerSync(self)
1531
1531
  self.object_store = ObjectStoreSync(self)
1532
- self.store = NodeStoreSync()
1532
+ self.store = NodeStoreSync(default_branch=self.default_branch)
1533
1533
  self.task = InfrahubTaskManagerSync(self)
1534
1534
  self._request_method: SyncRequester = self.config.sync_requester or self._default_request_method
1535
1535
  self.group_context = InfrahubGroupContextSync(self)
@@ -1997,11 +1997,11 @@ class InfrahubClientSync(BaseClient):
1997
1997
  if populate_store:
1998
1998
  for node in nodes:
1999
1999
  if node.id:
2000
- self.store.set(key=node.id, node=node)
2000
+ self.store.set(node=node)
2001
2001
  related_nodes = list(set(related_nodes))
2002
2002
  for node in related_nodes:
2003
2003
  if node.id:
2004
- self.store.set(key=node.id, node=node)
2004
+ self.store.set(node=node)
2005
2005
  return nodes
2006
2006
 
2007
2007
  @overload