infrahub-server 1.3.7__py3-none-any.whl → 1.4.0__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 (174) hide show
  1. infrahub/api/internal.py +5 -0
  2. infrahub/artifacts/tasks.py +17 -22
  3. infrahub/branch/merge_mutation_checker.py +38 -0
  4. infrahub/cli/__init__.py +2 -2
  5. infrahub/cli/context.py +7 -3
  6. infrahub/cli/db.py +5 -16
  7. infrahub/cli/upgrade.py +10 -29
  8. infrahub/computed_attribute/tasks.py +36 -46
  9. infrahub/config.py +57 -6
  10. infrahub/constants/environment.py +1 -0
  11. infrahub/core/attribute.py +15 -7
  12. infrahub/core/branch/tasks.py +43 -41
  13. infrahub/core/constants/__init__.py +21 -6
  14. infrahub/core/constants/infrahubkind.py +2 -0
  15. infrahub/core/diff/coordinator.py +3 -1
  16. infrahub/core/diff/model/path.py +0 -39
  17. infrahub/core/diff/repository/repository.py +0 -8
  18. infrahub/core/diff/tasks.py +11 -8
  19. infrahub/core/graph/__init__.py +1 -1
  20. infrahub/core/graph/index.py +1 -2
  21. infrahub/core/graph/schema.py +50 -29
  22. infrahub/core/initialization.py +81 -47
  23. infrahub/core/ipam/tasks.py +4 -3
  24. infrahub/core/merge.py +8 -10
  25. infrahub/core/migrations/__init__.py +2 -0
  26. infrahub/core/migrations/graph/__init__.py +4 -0
  27. infrahub/core/migrations/graph/m036_drop_attr_value_index.py +45 -0
  28. infrahub/core/migrations/graph/m037_index_attr_vals.py +577 -0
  29. infrahub/core/migrations/query/attribute_add.py +27 -2
  30. infrahub/core/migrations/schema/attribute_kind_update.py +156 -0
  31. infrahub/core/migrations/schema/tasks.py +6 -5
  32. infrahub/core/models.py +5 -1
  33. infrahub/core/node/proposed_change.py +43 -0
  34. infrahub/core/protocols.py +12 -0
  35. infrahub/core/query/attribute.py +32 -14
  36. infrahub/core/query/diff.py +11 -0
  37. infrahub/core/query/ipam.py +13 -7
  38. infrahub/core/query/node.py +51 -10
  39. infrahub/core/query/resource_manager.py +3 -3
  40. infrahub/core/schema/basenode_schema.py +8 -0
  41. infrahub/core/schema/definitions/core/__init__.py +10 -1
  42. infrahub/core/schema/definitions/core/ipam.py +28 -2
  43. infrahub/core/schema/definitions/core/propose_change.py +15 -0
  44. infrahub/core/schema/definitions/core/webhook.py +3 -0
  45. infrahub/core/schema/definitions/internal.py +1 -1
  46. infrahub/core/schema/generated/attribute_schema.py +1 -1
  47. infrahub/core/schema/generic_schema.py +10 -0
  48. infrahub/core/schema/manager.py +10 -1
  49. infrahub/core/schema/node_schema.py +22 -22
  50. infrahub/core/schema/profile_schema.py +8 -0
  51. infrahub/core/schema/schema_branch.py +11 -7
  52. infrahub/core/schema/template_schema.py +8 -0
  53. infrahub/core/validators/attribute/kind.py +5 -1
  54. infrahub/core/validators/checks_runner.py +5 -5
  55. infrahub/core/validators/determiner.py +22 -2
  56. infrahub/core/validators/tasks.py +6 -7
  57. infrahub/core/validators/uniqueness/checker.py +4 -2
  58. infrahub/core/validators/uniqueness/model.py +1 -0
  59. infrahub/core/validators/uniqueness/query.py +57 -7
  60. infrahub/database/__init__.py +2 -1
  61. infrahub/events/__init__.py +20 -0
  62. infrahub/events/constants.py +7 -0
  63. infrahub/events/generator.py +29 -2
  64. infrahub/events/proposed_change_action.py +203 -0
  65. infrahub/generators/tasks.py +24 -20
  66. infrahub/git/base.py +4 -7
  67. infrahub/git/integrator.py +21 -12
  68. infrahub/git/repository.py +15 -30
  69. infrahub/git/tasks.py +121 -106
  70. infrahub/graphql/app.py +2 -1
  71. infrahub/graphql/field_extractor.py +69 -0
  72. infrahub/graphql/manager.py +15 -11
  73. infrahub/graphql/mutations/account.py +2 -2
  74. infrahub/graphql/mutations/action.py +8 -2
  75. infrahub/graphql/mutations/artifact_definition.py +4 -1
  76. infrahub/graphql/mutations/branch.py +10 -5
  77. infrahub/graphql/mutations/graphql_query.py +2 -1
  78. infrahub/graphql/mutations/main.py +14 -8
  79. infrahub/graphql/mutations/menu.py +2 -1
  80. infrahub/graphql/mutations/proposed_change.py +230 -8
  81. infrahub/graphql/mutations/relationship.py +5 -0
  82. infrahub/graphql/mutations/repository.py +2 -1
  83. infrahub/graphql/mutations/tasks.py +7 -9
  84. infrahub/graphql/mutations/webhook.py +4 -1
  85. infrahub/graphql/parser.py +15 -6
  86. infrahub/graphql/queries/__init__.py +10 -1
  87. infrahub/graphql/queries/account.py +3 -3
  88. infrahub/graphql/queries/branch.py +2 -2
  89. infrahub/graphql/queries/diff/tree.py +56 -5
  90. infrahub/graphql/queries/event.py +13 -3
  91. infrahub/graphql/queries/ipam.py +23 -1
  92. infrahub/graphql/queries/proposed_change.py +84 -0
  93. infrahub/graphql/queries/relationship.py +2 -2
  94. infrahub/graphql/queries/resource_manager.py +3 -3
  95. infrahub/graphql/queries/search.py +3 -2
  96. infrahub/graphql/queries/status.py +3 -2
  97. infrahub/graphql/queries/task.py +2 -2
  98. infrahub/graphql/resolvers/ipam.py +440 -0
  99. infrahub/graphql/resolvers/many_relationship.py +4 -3
  100. infrahub/graphql/resolvers/resolver.py +5 -5
  101. infrahub/graphql/resolvers/single_relationship.py +3 -2
  102. infrahub/graphql/schema.py +25 -5
  103. infrahub/graphql/types/__init__.py +2 -2
  104. infrahub/graphql/types/attribute.py +3 -3
  105. infrahub/graphql/types/event.py +68 -0
  106. infrahub/groups/tasks.py +6 -6
  107. infrahub/lock.py +3 -2
  108. infrahub/menu/generator.py +8 -0
  109. infrahub/message_bus/operations/__init__.py +9 -12
  110. infrahub/message_bus/operations/git/file.py +6 -5
  111. infrahub/message_bus/operations/git/repository.py +12 -20
  112. infrahub/message_bus/operations/refresh/registry.py +15 -9
  113. infrahub/message_bus/operations/send/echo.py +7 -4
  114. infrahub/message_bus/types.py +1 -0
  115. infrahub/permissions/__init__.py +2 -1
  116. infrahub/permissions/constants.py +13 -0
  117. infrahub/permissions/globals.py +31 -2
  118. infrahub/permissions/manager.py +8 -5
  119. infrahub/pools/prefix.py +7 -5
  120. infrahub/prefect_server/app.py +31 -0
  121. infrahub/prefect_server/bootstrap.py +18 -0
  122. infrahub/proposed_change/action_checker.py +206 -0
  123. infrahub/proposed_change/approval_revoker.py +40 -0
  124. infrahub/proposed_change/branch_diff.py +3 -1
  125. infrahub/proposed_change/checker.py +45 -0
  126. infrahub/proposed_change/constants.py +32 -2
  127. infrahub/proposed_change/tasks.py +182 -150
  128. infrahub/py.typed +0 -0
  129. infrahub/server.py +29 -17
  130. infrahub/services/__init__.py +13 -28
  131. infrahub/services/adapters/cache/__init__.py +4 -0
  132. infrahub/services/adapters/cache/nats.py +2 -0
  133. infrahub/services/adapters/cache/redis.py +3 -0
  134. infrahub/services/adapters/message_bus/__init__.py +0 -2
  135. infrahub/services/adapters/message_bus/local.py +1 -2
  136. infrahub/services/adapters/message_bus/nats.py +6 -8
  137. infrahub/services/adapters/message_bus/rabbitmq.py +7 -9
  138. infrahub/services/adapters/workflow/__init__.py +1 -0
  139. infrahub/services/adapters/workflow/local.py +1 -8
  140. infrahub/services/component.py +2 -1
  141. infrahub/task_manager/event.py +56 -0
  142. infrahub/task_manager/models.py +9 -0
  143. infrahub/tasks/artifact.py +6 -7
  144. infrahub/tasks/check.py +4 -7
  145. infrahub/telemetry/tasks.py +15 -18
  146. infrahub/transformations/tasks.py +10 -6
  147. infrahub/trigger/tasks.py +4 -3
  148. infrahub/types.py +4 -0
  149. infrahub/validators/events.py +7 -7
  150. infrahub/validators/tasks.py +6 -7
  151. infrahub/webhook/models.py +45 -45
  152. infrahub/webhook/tasks.py +25 -24
  153. infrahub/workers/dependencies.py +143 -0
  154. infrahub/workers/infrahub_async.py +19 -43
  155. infrahub/workflows/catalogue.py +16 -2
  156. infrahub/workflows/initialization.py +5 -4
  157. infrahub/workflows/models.py +2 -0
  158. infrahub_sdk/client.py +2 -2
  159. infrahub_sdk/ctl/repository.py +51 -0
  160. infrahub_sdk/ctl/schema.py +9 -9
  161. infrahub_sdk/node/node.py +2 -2
  162. infrahub_sdk/pytest_plugin/items/graphql_query.py +1 -1
  163. infrahub_sdk/schema/repository.py +1 -1
  164. infrahub_sdk/testing/docker.py +1 -1
  165. infrahub_sdk/utils.py +2 -2
  166. {infrahub_server-1.3.7.dist-info → infrahub_server-1.4.0.dist-info}/METADATA +7 -5
  167. {infrahub_server-1.3.7.dist-info → infrahub_server-1.4.0.dist-info}/RECORD +174 -158
  168. infrahub_testcontainers/container.py +17 -0
  169. infrahub_testcontainers/docker-compose-cluster.test.yml +56 -1
  170. infrahub_testcontainers/docker-compose.test.yml +56 -1
  171. infrahub_testcontainers/helpers.py +4 -1
  172. {infrahub_server-1.3.7.dist-info → infrahub_server-1.4.0.dist-info}/LICENSE.txt +0 -0
  173. {infrahub_server-1.3.7.dist-info → infrahub_server-1.4.0.dist-info}/WHEEL +0 -0
  174. {infrahub_server-1.3.7.dist-info → infrahub_server-1.4.0.dist-info}/entry_points.txt +0 -0
infrahub/config.py CHANGED
@@ -48,6 +48,11 @@ def default_append_git_suffix_domains() -> list[str]:
48
48
  return ["github.com", "gitlab.com"]
49
49
 
50
50
 
51
+ class EnterpriseFeatures(str, Enum):
52
+ PROPOSED_CHANGE_REQUIRE_APPROVAL = "proposed_change_require_approval"
53
+ REVOKE_PROPOSED_CHANGE_APPROVALS = "revoke_proposed_change_approvals"
54
+
55
+
51
56
  class UserInfoMethod(str, Enum):
52
57
  POST = "post"
53
58
  GET = "get"
@@ -316,9 +321,15 @@ class DevelopmentSettings(BaseSettings):
316
321
  default=False,
317
322
  description="Indicates of the frontend should be responsible for the SSO redirection",
318
323
  )
324
+ allow_enterprise_configuration: bool = Field(
325
+ default=False,
326
+ description="Allow enterprise configuration in development mode, this will not enable the features just allow the configuration.",
327
+ )
319
328
 
320
329
 
321
330
  class BrokerSettings(BaseSettings):
331
+ """Configuration settings for the message bus."""
332
+
322
333
  model_config = SettingsConfigDict(env_prefix="INFRAHUB_BROKER_")
323
334
  enable: bool = True
324
335
  tls_enabled: bool = Field(default=False, description="Indicates if TLS is enabled for the connection")
@@ -432,9 +443,7 @@ class GitSettings(BaseSettings):
432
443
 
433
444
 
434
445
  class HTTPSettings(BaseSettings):
435
- """The HTTP settings control how Infrahub interacts with external HTTP servers
436
-
437
- This can be things like webhooks and OAuth2 providers"""
446
+ """The HTTP settings control how Infrahub interacts with external HTTP servers. This can be things like webhooks and OAuth2 providers."""
438
447
 
439
448
  model_config = SettingsConfigDict(env_prefix="INFRAHUB_HTTP_")
440
449
  timeout: int = Field(default=10, description="Default connection timeout in seconds")
@@ -638,7 +647,10 @@ class AnalyticsSettings(BaseSettings):
638
647
  class ExperimentalFeaturesSettings(BaseSettings):
639
648
  model_config = SettingsConfigDict(env_prefix="INFRAHUB_EXPERIMENTAL_")
640
649
  graphql_enums: bool = False
641
- value_db_index: bool = False
650
+ value_db_index: bool = Field(
651
+ default=False,
652
+ deprecated="This setting has no effect and will be removed in a future version.",
653
+ )
642
654
 
643
655
 
644
656
  class SecuritySettings(BaseSettings):
@@ -655,7 +667,7 @@ class SecuritySettings(BaseSettings):
655
667
  oidc_providers: list[OIDCProvider] = Field(default_factory=list, description="The selected OIDC providers")
656
668
  oidc_provider_settings: SecurityOIDCProviderSettings = Field(default_factory=SecurityOIDCProviderSettings)
657
669
  restrict_untrusted_jinja2_filters: bool = Field(
658
- default=True, description="Indicates if untrusted Jinja2 filters should be disallowd for computed attributes"
670
+ default=True, description="Indicates if untrusted Jinja2 filters should be disallowed for computed attributes"
659
671
  )
660
672
  _oauth2_settings: dict[str, SecurityOAuth2Settings] = PrivateAttr(default_factory=dict)
661
673
  _oidc_settings: dict[str, SecurityOIDCSettings] = PrivateAttr(default_factory=dict)
@@ -766,6 +778,30 @@ class TraceSettings(BaseSettings):
766
778
  exporter_endpoint: str | None = Field(default=None, description="OTLP endpoint for exporting traces")
767
779
 
768
780
 
781
+ class PolicySettings(BaseSettings):
782
+ model_config = SettingsConfigDict(env_prefix="INFRAHUB_POLICY_")
783
+ required_proposed_change_approvals: int = Field(
784
+ default=0,
785
+ ge=0,
786
+ description="Number of approvals required for proposed changes. (Enterprise only: not available in the community version.)",
787
+ )
788
+ revoke_proposed_change_approvals: bool = Field(
789
+ default=False,
790
+ description="Boolean indicating whether performing changes on a proposed change branch should revoke existing approvals."
791
+ " (Enterprise only: not available in the community version.)",
792
+ )
793
+
794
+ @property
795
+ def enterprise_features(self) -> list[EnterpriseFeatures]:
796
+ """Returns a list of enterprise features that are enabled based on the settings."""
797
+ features = []
798
+ if self.required_proposed_change_approvals > 0:
799
+ features.append(EnterpriseFeatures.PROPOSED_CHANGE_REQUIRE_APPROVAL)
800
+ if self.revoke_proposed_change_approvals:
801
+ features.append(EnterpriseFeatures.REVOKE_PROPOSED_CHANGE_APPROVALS)
802
+ return features
803
+
804
+
769
805
  @dataclass
770
806
  class Override:
771
807
  message_bus: InfrahubMessageBus | None = None
@@ -857,6 +893,10 @@ class ConfiguredSettings:
857
893
  def analytics(self) -> AnalyticsSettings:
858
894
  return self.active_settings.analytics
859
895
 
896
+ @property
897
+ def policy(self) -> PolicySettings:
898
+ return self.active_settings.policy
899
+
860
900
  @property
861
901
  def security(self) -> SecuritySettings:
862
902
  return self.active_settings.security
@@ -873,6 +913,11 @@ class ConfiguredSettings:
873
913
  def experimental_features(self) -> ExperimentalFeaturesSettings:
874
914
  return self.active_settings.experimental_features
875
915
 
916
+ @property
917
+ def enterprise_features(self) -> list[EnterpriseFeatures]:
918
+ """Returns a list of enterprise features that are enabled based on the settings."""
919
+ return self.active_settings.enterprise_features
920
+
876
921
 
877
922
  class Settings(BaseSettings):
878
923
  """Main Settings Class for the project."""
@@ -890,16 +935,22 @@ class Settings(BaseSettings):
890
935
  logging: LoggingSettings = LoggingSettings()
891
936
  analytics: AnalyticsSettings = AnalyticsSettings()
892
937
  initial: InitialSettings = InitialSettings()
938
+ policy: PolicySettings = PolicySettings()
893
939
  security: SecuritySettings = SecuritySettings()
894
940
  storage: StorageSettings = StorageSettings()
895
941
  trace: TraceSettings = TraceSettings()
896
942
  experimental_features: ExperimentalFeaturesSettings = ExperimentalFeaturesSettings()
897
943
 
944
+ @property
945
+ def enterprise_features(self) -> list[EnterpriseFeatures]:
946
+ """Returns a list of enterprise features that are enabled based on the settings."""
947
+ return self.policy.enterprise_features
948
+
898
949
 
899
950
  def load(config_file_name: Path | str = "infrahub.toml", config_data: dict[str, Any] | None = None) -> Settings:
900
951
  """Load configuration.
901
952
 
902
- Configuration is loaded from a config file in toml format that contains the settings,
953
+ Configuration is loaded from a configuration file in toml format that contains the settings,
903
954
  or from a dictionary of those settings passed in as "config_data"
904
955
  """
905
956
  config_file = Path(config_file_name)
@@ -0,0 +1 @@
1
+ INSTALLATION_TYPE = "community"
@@ -29,7 +29,7 @@ from infrahub.core.utils import add_relationship, convert_ip_to_binary_str, upda
29
29
  from infrahub.exceptions import ValidationError
30
30
  from infrahub.helpers import hash_password
31
31
 
32
- from ..types import ATTRIBUTE_TYPES, LARGE_ATTRIBUTE_TYPES
32
+ from ..types import is_large_attribute_type
33
33
  from .constants.relationship_label import RELATIONSHIP_TO_NODE_LABEL, RELATIONSHIP_TO_VALUE_LABEL
34
34
  from .schema.attribute_parameters import NumberAttributeParameters
35
35
 
@@ -72,7 +72,6 @@ class AttributeCreateData(BaseModel):
72
72
  is_visible: bool
73
73
  source_prop: list[NodePropertyData] = Field(default_factory=list)
74
74
  owner_prop: list[NodePropertyData] = Field(default_factory=list)
75
- node_type: AttributeDBNodeType = AttributeDBNodeType.DEFAULT
76
75
 
77
76
 
78
77
  class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
@@ -278,7 +277,7 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
278
277
  data["value"] = NULL_VALUE
279
278
  else:
280
279
  serialized_value = self.serialize_value()
281
- if isinstance(serialized_value, str) and ATTRIBUTE_TYPES[self.schema.kind] not in LARGE_ATTRIBUTE_TYPES:
280
+ if isinstance(serialized_value, str) and not is_large_attribute_type(self.schema.kind):
282
281
  # Perform validation here to avoid an extra serialization during validation step.
283
282
  # Standard non-str attributes (integer, boolean) do not exceed limit size related to neo4j indexing.
284
283
  validate_string_length(serialized_value)
@@ -497,6 +496,8 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
497
496
  if changelog.has_updates:
498
497
  return changelog
499
498
 
499
+ return None
500
+
500
501
  async def to_graphql(
501
502
  self,
502
503
  db: InfrahubDatabase,
@@ -622,7 +623,9 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
622
623
  return changed
623
624
 
624
625
  def get_db_node_type(self) -> AttributeDBNodeType:
625
- return AttributeDBNodeType.DEFAULT
626
+ if is_large_attribute_type(self.get_kind()):
627
+ return AttributeDBNodeType.DEFAULT
628
+ return AttributeDBNodeType.INDEXED
626
629
 
627
630
  def get_create_data(self) -> AttributeCreateData:
628
631
  branch = self.branch
@@ -633,7 +636,6 @@ class BaseAttribute(FlagPropertyMixin, NodePropertyMixin):
633
636
  branch = registry.get_global_branch()
634
637
  hierarchy_level = 0
635
638
  data = AttributeCreateData(
636
- node_type=self.get_db_node_type(),
637
639
  uuid=str(UUIDT()),
638
640
  name=self.name,
639
641
  type=self.get_kind(),
@@ -678,6 +680,12 @@ class String(BaseAttribute):
678
680
  type = str
679
681
  value: str
680
682
 
683
+ @classmethod
684
+ def validate_content(cls, value: Any, name: str, schema: AttributeSchema) -> None:
685
+ if value is not None and not is_large_attribute_type(schema.kind):
686
+ validate_string_length(value=str(value))
687
+ super().validate_content(value=value, name=name, schema=schema)
688
+
681
689
 
682
690
  class StringOptional(String):
683
691
  value: str | None
@@ -944,7 +952,7 @@ class IPNetwork(BaseAttribute):
944
952
  def get_db_node_type(self) -> AttributeDBNodeType:
945
953
  if self.value is not None:
946
954
  return AttributeDBNodeType.IPNETWORK
947
- return AttributeDBNodeType.DEFAULT
955
+ return super().get_db_node_type()
948
956
 
949
957
  def to_db(self) -> dict[str, Any]:
950
958
  data = super().to_db()
@@ -1080,7 +1088,7 @@ class IPHost(BaseAttribute):
1080
1088
  def get_db_node_type(self) -> AttributeDBNodeType:
1081
1089
  if self.value is not None:
1082
1090
  return AttributeDBNodeType.IPHOST
1083
- return AttributeDBNodeType.DEFAULT
1091
+ return super().get_db_node_type()
1084
1092
 
1085
1093
  def to_db(self) -> dict[str, Any]:
1086
1094
  data = super().to_db()
@@ -34,7 +34,7 @@ from infrahub.events.models import EventMeta, InfrahubEvent
34
34
  from infrahub.events.node_action import get_node_event
35
35
  from infrahub.exceptions import BranchNotFoundError, ValidationError
36
36
  from infrahub.graphql.mutations.models import BranchCreateModel # noqa: TC001
37
- from infrahub.services import InfrahubServices # noqa: TC001 needed for prefect flow
37
+ from infrahub.workers.dependencies import get_component, get_database, get_event_service, get_workflow
38
38
  from infrahub.workflows.catalogue import (
39
39
  BRANCH_CANCEL_PROPOSED_CHANGES,
40
40
  BRANCH_MERGE_POST_PROCESS,
@@ -49,8 +49,9 @@ from infrahub.workflows.utils import add_tags
49
49
 
50
50
 
51
51
  @flow(name="branch-rebase", flow_run_name="Rebase branch {branch}")
52
- async def rebase_branch(branch: str, context: InfrahubContext, service: InfrahubServices) -> None: # noqa: PLR0915
53
- async with service.database.start_session() as db:
52
+ async def rebase_branch(branch: str, context: InfrahubContext) -> None: # noqa: PLR0915
53
+ database = await get_database()
54
+ async with database.start_session() as db:
54
55
  log = get_run_logger()
55
56
  await add_tags(branches=[branch])
56
57
  obj = await Branch.get_by_name(db=db, name=branch)
@@ -67,7 +68,7 @@ async def rebase_branch(branch: str, context: InfrahubContext, service: Infrahub
67
68
  diff_repository=diff_repository,
68
69
  source_branch=obj,
69
70
  diff_locker=DiffLocker(),
70
- service=service,
71
+ workflow=get_workflow(),
71
72
  )
72
73
 
73
74
  enriched_diff_metadata = await diff_coordinator.update_branch_diff(base_branch=base_branch, diff_branch=obj)
@@ -94,10 +95,7 @@ async def rebase_branch(branch: str, context: InfrahubContext, service: Infrahub
94
95
  constraints += await merger.calculate_validations(target_schema=candidate_schema)
95
96
  if constraints:
96
97
  responses = await schema_validate_migrations(
97
- message=SchemaValidateMigrationData(
98
- branch=obj, schema_branch=candidate_schema, constraints=constraints
99
- ),
100
- service=service,
98
+ message=SchemaValidateMigrationData(branch=obj, schema_branch=candidate_schema, constraints=constraints)
101
99
  )
102
100
  error_messages = [violation.message for response in responses for violation in response.violations]
103
101
  if error_messages:
@@ -135,8 +133,7 @@ async def rebase_branch(branch: str, context: InfrahubContext, service: Infrahub
135
133
  new_schema=candidate_schema,
136
134
  previous_schema=schema_in_main_before,
137
135
  migrations=migrations,
138
- ),
139
- service=service,
136
+ )
140
137
  )
141
138
  for error in errors:
142
139
  log.error(error)
@@ -158,13 +155,13 @@ async def rebase_branch(branch: str, context: InfrahubContext, service: Infrahub
158
155
  target_branch_name=registry.default_branch,
159
156
  )
160
157
  if ipam_node_details:
161
- await service.workflow.submit_workflow(
158
+ await get_workflow().submit_workflow(
162
159
  workflow=IPAM_RECONCILIATION,
163
160
  context=context,
164
161
  parameters={"branch": obj.name, "ipam_node_details": ipam_node_details},
165
162
  )
166
163
 
167
- await service.workflow.submit_workflow(
164
+ await get_workflow().submit_workflow(
168
165
  workflow=DIFF_REFRESH_ALL, context=context, parameters={"branch_name": obj.name}
169
166
  )
170
167
 
@@ -189,15 +186,15 @@ async def rebase_branch(branch: str, context: InfrahubContext, service: Infrahub
189
186
  )
190
187
  events.append(mutate_event)
191
188
 
189
+ event_service = await get_event_service()
192
190
  for event in events:
193
- await service.event.send(event)
191
+ await event_service.send(event)
194
192
 
195
193
 
196
194
  @flow(name="branch-merge", flow_run_name="Merge branch {branch} into main")
197
- async def merge_branch(
198
- branch: str, context: InfrahubContext, service: InfrahubServices, proposed_change_id: str | None = None
199
- ) -> None:
200
- async with service.database.start_session() as db:
195
+ async def merge_branch(branch: str, context: InfrahubContext, proposed_change_id: str | None = None) -> None:
196
+ database = await get_database()
197
+ async with database.start_session() as db:
201
198
  log = get_run_logger()
202
199
 
203
200
  await add_tags(branches=[branch, registry.default_branch])
@@ -224,7 +221,7 @@ async def merge_branch(
224
221
  diff_repository=diff_repository,
225
222
  source_branch=obj,
226
223
  diff_locker=DiffLocker(),
227
- service=service,
224
+ workflow=get_workflow(),
228
225
  )
229
226
  branch_diff = await merger.merge()
230
227
  await merger.update_schema()
@@ -238,8 +235,7 @@ async def merge_branch(
238
235
  new_schema=merger.destination_schema,
239
236
  previous_schema=merger.initial_source_schema,
240
237
  migrations=merger.migrations,
241
- ),
242
- service=service,
238
+ )
243
239
  )
244
240
  for error in errors:
245
241
  log.error(error)
@@ -253,7 +249,7 @@ async def merge_branch(
253
249
  target_branch_name=registry.default_branch,
254
250
  )
255
251
  if ipam_node_details:
256
- await service.workflow.submit_workflow(
252
+ await get_workflow().submit_workflow(
257
253
  workflow=IPAM_RECONCILIATION,
258
254
  context=context,
259
255
  parameters={"branch": registry.default_branch, "ipam_node_details": ipam_node_details},
@@ -269,7 +265,7 @@ async def merge_branch(
269
265
  # NOTE: we still need to convert this event and potentially pull
270
266
  # some tasks currently executed based on the event into this workflow
271
267
  # -------------------------------------------------------------
272
- await service.workflow.submit_workflow(
268
+ await get_workflow().submit_workflow(
273
269
  workflow=BRANCH_MERGE_POST_PROCESS,
274
270
  context=context,
275
271
  parameters={"source_branch": obj.name, "target_branch": registry.default_branch},
@@ -289,15 +285,17 @@ async def merge_branch(
289
285
  )
290
286
  events.append(mutate_event)
291
287
 
288
+ event_service = await get_event_service()
292
289
  for event in events:
293
- await service.event.send(event=event)
290
+ await event_service.send(event=event)
294
291
 
295
292
 
296
293
  @flow(name="branch-delete", flow_run_name="Delete branch {branch}")
297
- async def delete_branch(branch: str, context: InfrahubContext, service: InfrahubServices) -> None:
294
+ async def delete_branch(branch: str, context: InfrahubContext) -> None:
298
295
  await add_tags(branches=[branch])
299
296
 
300
- async with service.database.start_session() as db:
297
+ database = await get_database()
298
+ async with database.start_session() as db:
301
299
  obj = await Branch.get_by_name(db=db, name=str(branch))
302
300
  await obj.delete(db=db)
303
301
 
@@ -308,11 +306,12 @@ async def delete_branch(branch: str, context: InfrahubContext, service: Infrahub
308
306
  meta=EventMeta.from_context(context=context, branch=registry.get_global_branch()),
309
307
  )
310
308
 
311
- await service.workflow.submit_workflow(
309
+ await get_workflow().submit_workflow(
312
310
  workflow=BRANCH_CANCEL_PROPOSED_CHANGES, context=context, parameters={"branch_name": branch}
313
311
  )
314
312
 
315
- await service.event.send(event=event)
313
+ event_service = await get_event_service()
314
+ await event_service.send(event=event)
316
315
 
317
316
 
318
317
  @flow(
@@ -321,10 +320,11 @@ async def delete_branch(branch: str, context: InfrahubContext, service: Infrahub
321
320
  description="Validate if the branch has some conflicts",
322
321
  persist_result=True,
323
322
  )
324
- async def validate_branch(branch: str, service: InfrahubServices) -> State:
323
+ async def validate_branch(branch: str) -> State:
325
324
  await add_tags(branches=[branch])
326
325
 
327
- async with service.database.start_session() as db:
326
+ database = await get_database()
327
+ async with database.start_session() as db:
328
328
  obj = await Branch.get_by_name(db=db, name=branch)
329
329
 
330
330
  component_registry = get_component_registry()
@@ -338,10 +338,11 @@ async def validate_branch(branch: str, service: InfrahubServices) -> State:
338
338
 
339
339
 
340
340
  @flow(name="create-branch", flow_run_name="Create branch {model.name}")
341
- async def create_branch(model: BranchCreateModel, context: InfrahubContext, service: InfrahubServices) -> None:
341
+ async def create_branch(model: BranchCreateModel, context: InfrahubContext) -> None:
342
342
  await add_tags(branches=[model.name])
343
343
 
344
- async with service.database.start_session() as db:
344
+ database = await get_database()
345
+ async with database.start_session() as db:
345
346
  try:
346
347
  await Branch.get_by_name(db=db, name=model.name)
347
348
  raise ValueError(f"The branch {model.name}, already exist")
@@ -367,7 +368,8 @@ async def create_branch(model: BranchCreateModel, context: InfrahubContext, serv
367
368
 
368
369
  # Add Branch to registry
369
370
  registry.branch[obj.name] = obj
370
- await service.component.refresh_schema_hash(branches=[obj.name])
371
+ component = await get_component()
372
+ await component.refresh_schema_hash(branches=[obj.name])
371
373
 
372
374
  event = BranchCreatedEvent(
373
375
  branch_name=obj.name,
@@ -375,10 +377,11 @@ async def create_branch(model: BranchCreateModel, context: InfrahubContext, serv
375
377
  sync_with_git=obj.sync_with_git,
376
378
  meta=EventMeta.from_context(context=context, branch=registry.get_global_branch()),
377
379
  )
378
- await service.event.send(event=event)
380
+ event_service = await get_event_service()
381
+ await event_service.send(event=event)
379
382
 
380
383
  if obj.sync_with_git:
381
- await service.workflow.submit_workflow(
384
+ await get_workflow().submit_workflow(
382
385
  workflow=GIT_REPOSITORIES_CREATE_BRANCH,
383
386
  context=context,
384
387
  parameters={"branch": obj.name, "branch_id": str(obj.uuid)},
@@ -412,10 +415,9 @@ async def _get_diff_root(
412
415
  name="branch-merge-post-process",
413
416
  flow_run_name="Run additional tasks after merging {source_branch} in {target_branch}",
414
417
  )
415
- async def post_process_branch_merge(
416
- source_branch: str, target_branch: str, context: InfrahubContext, service: InfrahubServices
417
- ) -> None:
418
- async with service.database.start_session() as db:
418
+ async def post_process_branch_merge(source_branch: str, target_branch: str, context: InfrahubContext) -> None:
419
+ database = await get_database()
420
+ async with database.start_session() as db:
419
421
  await add_tags(branches=[source_branch])
420
422
  log = get_run_logger()
421
423
  log.info(f"Running additional tasks after merging {source_branch} within {target_branch}")
@@ -426,13 +428,13 @@ async def post_process_branch_merge(
426
428
  # send diff update requests for every branch-tracking diff
427
429
  branch_diff_roots = await diff_repository.get_roots_metadata(base_branch_names=[target_branch])
428
430
 
429
- await service.workflow.submit_workflow(
431
+ await get_workflow().submit_workflow(
430
432
  workflow=TRIGGER_ARTIFACT_DEFINITION_GENERATE,
431
433
  context=context,
432
434
  parameters={"branch": target_branch},
433
435
  )
434
436
 
435
- await service.workflow.submit_workflow(
437
+ await get_workflow().submit_workflow(
436
438
  workflow=TRIGGER_GENERATOR_DEFINITION_RUN,
437
439
  context=context,
438
440
  parameters={"branch": target_branch},
@@ -445,6 +447,6 @@ async def post_process_branch_merge(
445
447
  and isinstance(diff_root.tracking_id, BranchTrackingId)
446
448
  ):
447
449
  request_diff_update_model = RequestDiffUpdate(branch_name=diff_root.diff_branch_name)
448
- await service.workflow.submit_workflow(
450
+ await get_workflow().submit_workflow(
449
451
  workflow=DIFF_UPDATE, context=context, parameters={"model": request_diff_update_model}
450
452
  )
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- import enum
3
+ from enum import Flag, auto
4
4
 
5
5
  from infrahub.core.constants import infrahubkind as InfrahubKind # noqa: N812
6
6
  from infrahub.exceptions import ValidationError
@@ -61,6 +61,16 @@ class EventType(InfrahubStringEnum):
61
61
  GROUP_MEMBER_ADDED = f"{EVENT_NAMESPACE}.group.member_added"
62
62
  GROUP_MEMBER_REMOVED = f"{EVENT_NAMESPACE}.group.member_removed"
63
63
 
64
+ PROPOSED_CHANGE_MERGED = f"{EVENT_NAMESPACE}.proposed_change.merged"
65
+ PROPOSED_CHANGE_REVIEW_REQUESTED = f"{EVENT_NAMESPACE}.proposed_change.review_requested"
66
+ PROPOSED_CHANGE_APPROVED = f"{EVENT_NAMESPACE}.proposed_change.approved"
67
+ PROPOSED_CHANGE_REJECTED = f"{EVENT_NAMESPACE}.proposed_change.rejected"
68
+ PROPOSED_CHANGE_APPROVAL_REVOKED = f"{EVENT_NAMESPACE}.proposed_change.approval_revoked"
69
+ PROPOSED_CHANGE_APPROVALS_REVOKED = f"{EVENT_NAMESPACE}.proposed_change.approvals_revoked"
70
+ PROPOSED_CHANGE_REJECTION_REVOKED = f"{EVENT_NAMESPACE}.proposed_change.rejection_revoked"
71
+ PROPOSED_CHANGE_THREAD_CREATED = f"{EVENT_NAMESPACE}.proposed_change_thread.created"
72
+ PROPOSED_CHANGE_THREAD_UPDATED = f"{EVENT_NAMESPACE}.proposed_change_thread.updated"
73
+
64
74
  REPOSITORY_UPDATE_COMMIT = f"{EVENT_NAMESPACE}.repository.update_commit"
65
75
 
66
76
  ARTIFACT_CREATED = f"{EVENT_NAMESPACE}.artifact.created"
@@ -71,7 +81,7 @@ class EventType(InfrahubStringEnum):
71
81
  VALIDATOR_FAILED = f"{EVENT_NAMESPACE}.validator.failed"
72
82
 
73
83
 
74
- class PermissionLevel(enum.Flag):
84
+ class PermissionLevel(Flag):
75
85
  READ = 1
76
86
  WRITE = 2
77
87
  ADMIN = 3
@@ -83,6 +93,7 @@ class GlobalPermissions(InfrahubStringEnum):
83
93
  SUPER_ADMIN = "super_admin"
84
94
  MERGE_BRANCH = "merge_branch"
85
95
  MERGE_PROPOSED_CHANGE = "merge_proposed_change"
96
+ REVIEW_PROPOSED_CHANGE = "review_proposed_change"
86
97
  MANAGE_SCHEMA = "manage_schema"
87
98
  MANAGE_ACCOUNTS = "manage_accounts"
88
99
  MANAGE_PERMISSIONS = "manage_permissions"
@@ -335,10 +346,14 @@ class ValidatorState(InfrahubStringEnum):
335
346
  COMPLETED = "completed"
336
347
 
337
348
 
338
- class AttributeDBNodeType(InfrahubStringEnum):
339
- DEFAULT = "default"
340
- IPHOST = "iphost"
341
- IPNETWORK = "ipnetwork"
349
+ class AttributeDBNodeType(Flag):
350
+ DEFAULT = auto()
351
+ INDEX_ONLY = auto()
352
+ IPHOST_ONLY = auto()
353
+ IPNETWORK_ONLY = auto()
354
+ INDEXED = DEFAULT | INDEX_ONLY
355
+ IPHOST = DEFAULT | INDEX_ONLY | IPHOST_ONLY
356
+ IPNETWORK = DEFAULT | INDEX_ONLY | IPNETWORK_ONLY
342
357
 
343
358
 
344
359
  RESTRICTED_NAMESPACES: list[str] = [
@@ -38,7 +38,9 @@ IPNAMESPACE = "BuiltinIPNamespace"
38
38
  IPADDRESS = "BuiltinIPAddress"
39
39
  IPADDRESSPOOL = "CoreIPAddressPool"
40
40
  IPPREFIX = "BuiltinIPPrefix"
41
+ IPPREFIXAVAILABLE = "InternalIPPrefixAvailable"
41
42
  IPPREFIXPOOL = "CoreIPPrefixPool"
43
+ IPRANGEAVAILABLE = "InternalIPRangeAvailable"
42
44
  MENU = "CoreMenu"
43
45
  MENUITEM = "CoreMenuItem"
44
46
  NAMESPACE = "IpamNamespace"
@@ -11,6 +11,7 @@ from infrahub.core.timestamp import Timestamp
11
11
  from infrahub.exceptions import ValidationError
12
12
  from infrahub.log import get_logger
13
13
 
14
+ from ..query.diff import get_num_changes_in_time_range_by_branch
14
15
  from .model.field_specifiers_map import NodeFieldSpecifierMap
15
16
  from .model.path import (
16
17
  BranchTrackingId,
@@ -419,10 +420,11 @@ class DiffCoordinator:
419
420
  log.info(
420
421
  f"Checking number of changes on branches for {diff_request!r}, from_time={current_time}, to_time={end_time}"
421
422
  )
422
- num_changes_by_branch = await self.diff_repo.get_num_changes_in_time_range_by_branch(
423
+ num_changes_by_branch = await get_num_changes_in_time_range_by_branch(
423
424
  branch_names=[diff_request.base_branch.name, diff_request.diff_branch.name],
424
425
  from_time=current_time,
425
426
  to_time=end_time,
427
+ db=self.db,
426
428
  )
427
429
  log.info(f"Number of changes: {num_changes_by_branch}")
428
430
  might_have_changes_in_time_range = any(num_changes_by_branch.values())
@@ -22,8 +22,6 @@ if TYPE_CHECKING:
22
22
  from neo4j.graph import Relationship as Neo4jRelationship
23
23
  from whenever import TimeDelta
24
24
 
25
- from infrahub.graphql.initialization import GraphqlContext
26
-
27
25
 
28
26
  @dataclass
29
27
  class TimeRange:
@@ -314,12 +312,6 @@ class EnrichedDiffRelationship(BaseSummary):
314
312
  )
315
313
 
316
314
 
317
- @dataclass
318
- class ParentNodeInfo:
319
- node: EnrichedDiffNode
320
- relationship_name: str = "undefined"
321
-
322
-
323
315
  @dataclass
324
316
  class EnrichedDiffNode(BaseSummary):
325
317
  identifier: NodeIdentifier
@@ -364,37 +356,6 @@ class EnrichedDiffNode(BaseSummary):
364
356
  rel.clear_conflicts()
365
357
  self.conflict = None
366
358
 
367
- def get_parent_info(self, graphql_context: GraphqlContext | None = None) -> ParentNodeInfo | None:
368
- for r in self.relationships:
369
- for n in r.nodes:
370
- relationship_name: str = "undefined"
371
-
372
- if not graphql_context:
373
- return ParentNodeInfo(node=n, relationship_name=relationship_name)
374
-
375
- node_schema = graphql_context.db.schema.get(name=self.kind)
376
- rel_schema = node_schema.get_relationship(name=r.name)
377
-
378
- parent_schema = graphql_context.db.schema.get(name=n.kind)
379
- rels_parent = parent_schema.get_relationships_by_identifier(id=rel_schema.get_identifier())
380
-
381
- if rels_parent and len(rels_parent) == 1:
382
- relationship_name = rels_parent[0].name
383
- elif rels_parent and len(rels_parent) > 1:
384
- for rel_parent in rels_parent:
385
- if (
386
- rel_schema.direction == RelationshipDirection.INBOUND
387
- and rel_parent.direction == RelationshipDirection.OUTBOUND
388
- ) or (
389
- rel_schema.direction == RelationshipDirection.OUTBOUND
390
- and rel_parent.direction == RelationshipDirection.INBOUND
391
- ):
392
- relationship_name = rel_parent.name
393
- break
394
-
395
- return ParentNodeInfo(node=n, relationship_name=relationship_name)
396
- return None
397
-
398
359
  def get_all_child_nodes(self) -> set[EnrichedDiffNode]:
399
360
  all_children = set()
400
361
  for r in self.relationships:
@@ -10,7 +10,6 @@ from infrahub.core.diff.query.summary_counts_enricher import (
10
10
  DiffFieldsSummaryCountsEnricherQuery,
11
11
  DiffNodesSummaryCountsEnricherQuery,
12
12
  )
13
- from infrahub.core.query.diff import DiffCountChanges
14
13
  from infrahub.core.timestamp import Timestamp
15
14
  from infrahub.database import InfrahubDatabase, retry_db_transaction
16
15
  from infrahub.exceptions import ResourceNotFoundError
@@ -513,13 +512,6 @@ class DiffRepository:
513
512
  query = await EnrichedDiffMergedTrackingIdQuery.init(db=self.db, tracking_ids=tracking_ids)
514
513
  await query.execute(db=self.db)
515
514
 
516
- async def get_num_changes_in_time_range_by_branch(
517
- self, branch_names: list[str], from_time: Timestamp, to_time: Timestamp
518
- ) -> dict[str, int]:
519
- query = await DiffCountChanges.init(db=self.db, branch_names=branch_names, diff_from=from_time, diff_to=to_time)
520
- await query.execute(db=self.db)
521
- return query.get_num_changes_by_branch()
522
-
523
515
  async def get_node_field_specifiers(self, diff_id: str) -> NodeFieldSpecifierMap:
524
516
  limit = config.SETTINGS.database.query_size_limit
525
517
  offset = 0