infrahub-server 1.3.5__py3-none-any.whl → 1.4.0b0__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 (158) 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 +7 -29
  8. infrahub/computed_attribute/tasks.py +36 -46
  9. infrahub/config.py +53 -2
  10. infrahub/constants/environment.py +1 -0
  11. infrahub/core/attribute.py +9 -7
  12. infrahub/core/branch/tasks.py +43 -41
  13. infrahub/core/constants/__init__.py +20 -6
  14. infrahub/core/constants/infrahubkind.py +2 -0
  15. infrahub/core/diff/coordinator.py +3 -1
  16. infrahub/core/diff/repository/repository.py +0 -8
  17. infrahub/core/diff/tasks.py +11 -8
  18. infrahub/core/graph/__init__.py +1 -1
  19. infrahub/core/graph/index.py +1 -2
  20. infrahub/core/graph/schema.py +50 -29
  21. infrahub/core/initialization.py +62 -33
  22. infrahub/core/ipam/tasks.py +4 -3
  23. infrahub/core/merge.py +8 -10
  24. infrahub/core/migrations/graph/__init__.py +2 -0
  25. infrahub/core/migrations/graph/m035_drop_attr_value_index.py +45 -0
  26. infrahub/core/migrations/query/attribute_add.py +27 -2
  27. infrahub/core/migrations/schema/tasks.py +6 -5
  28. infrahub/core/node/proposed_change.py +43 -0
  29. infrahub/core/protocols.py +12 -0
  30. infrahub/core/query/attribute.py +32 -14
  31. infrahub/core/query/diff.py +11 -0
  32. infrahub/core/query/ipam.py +13 -7
  33. infrahub/core/query/node.py +51 -10
  34. infrahub/core/query/resource_manager.py +3 -3
  35. infrahub/core/schema/basenode_schema.py +8 -0
  36. infrahub/core/schema/definitions/core/__init__.py +10 -1
  37. infrahub/core/schema/definitions/core/ipam.py +28 -2
  38. infrahub/core/schema/definitions/core/propose_change.py +15 -0
  39. infrahub/core/schema/definitions/core/webhook.py +3 -0
  40. infrahub/core/schema/generic_schema.py +10 -0
  41. infrahub/core/schema/manager.py +10 -1
  42. infrahub/core/schema/node_schema.py +22 -17
  43. infrahub/core/schema/profile_schema.py +8 -0
  44. infrahub/core/schema/schema_branch.py +9 -5
  45. infrahub/core/schema/template_schema.py +8 -0
  46. infrahub/core/validators/checks_runner.py +5 -5
  47. infrahub/core/validators/tasks.py +6 -7
  48. infrahub/core/validators/uniqueness/checker.py +4 -2
  49. infrahub/core/validators/uniqueness/model.py +1 -0
  50. infrahub/core/validators/uniqueness/query.py +57 -7
  51. infrahub/database/__init__.py +2 -1
  52. infrahub/events/__init__.py +18 -0
  53. infrahub/events/constants.py +7 -0
  54. infrahub/events/generator.py +29 -2
  55. infrahub/events/proposed_change_action.py +181 -0
  56. infrahub/generators/tasks.py +24 -20
  57. infrahub/git/base.py +4 -7
  58. infrahub/git/integrator.py +21 -12
  59. infrahub/git/repository.py +15 -30
  60. infrahub/git/tasks.py +121 -106
  61. infrahub/graphql/field_extractor.py +69 -0
  62. infrahub/graphql/manager.py +15 -11
  63. infrahub/graphql/mutations/account.py +2 -2
  64. infrahub/graphql/mutations/action.py +8 -2
  65. infrahub/graphql/mutations/artifact_definition.py +4 -1
  66. infrahub/graphql/mutations/branch.py +10 -5
  67. infrahub/graphql/mutations/graphql_query.py +2 -1
  68. infrahub/graphql/mutations/main.py +14 -8
  69. infrahub/graphql/mutations/menu.py +2 -1
  70. infrahub/graphql/mutations/proposed_change.py +225 -8
  71. infrahub/graphql/mutations/relationship.py +5 -0
  72. infrahub/graphql/mutations/repository.py +2 -1
  73. infrahub/graphql/mutations/tasks.py +7 -9
  74. infrahub/graphql/mutations/webhook.py +4 -1
  75. infrahub/graphql/parser.py +15 -6
  76. infrahub/graphql/queries/__init__.py +10 -1
  77. infrahub/graphql/queries/account.py +3 -3
  78. infrahub/graphql/queries/branch.py +2 -2
  79. infrahub/graphql/queries/diff/tree.py +3 -3
  80. infrahub/graphql/queries/event.py +13 -3
  81. infrahub/graphql/queries/ipam.py +23 -1
  82. infrahub/graphql/queries/proposed_change.py +84 -0
  83. infrahub/graphql/queries/relationship.py +2 -2
  84. infrahub/graphql/queries/resource_manager.py +3 -3
  85. infrahub/graphql/queries/search.py +3 -2
  86. infrahub/graphql/queries/status.py +3 -2
  87. infrahub/graphql/queries/task.py +2 -2
  88. infrahub/graphql/resolvers/ipam.py +440 -0
  89. infrahub/graphql/resolvers/many_relationship.py +4 -3
  90. infrahub/graphql/resolvers/resolver.py +5 -5
  91. infrahub/graphql/resolvers/single_relationship.py +3 -2
  92. infrahub/graphql/schema.py +25 -5
  93. infrahub/graphql/types/__init__.py +2 -2
  94. infrahub/graphql/types/attribute.py +3 -3
  95. infrahub/graphql/types/event.py +60 -0
  96. infrahub/groups/tasks.py +6 -6
  97. infrahub/lock.py +3 -2
  98. infrahub/menu/generator.py +8 -0
  99. infrahub/message_bus/operations/__init__.py +9 -12
  100. infrahub/message_bus/operations/git/file.py +6 -5
  101. infrahub/message_bus/operations/git/repository.py +12 -20
  102. infrahub/message_bus/operations/refresh/registry.py +15 -9
  103. infrahub/message_bus/operations/send/echo.py +7 -4
  104. infrahub/message_bus/types.py +1 -0
  105. infrahub/permissions/globals.py +1 -4
  106. infrahub/permissions/manager.py +8 -5
  107. infrahub/pools/prefix.py +7 -5
  108. infrahub/prefect_server/app.py +31 -0
  109. infrahub/prefect_server/bootstrap.py +18 -0
  110. infrahub/proposed_change/action_checker.py +206 -0
  111. infrahub/proposed_change/approval_revoker.py +40 -0
  112. infrahub/proposed_change/branch_diff.py +3 -1
  113. infrahub/proposed_change/checker.py +45 -0
  114. infrahub/proposed_change/constants.py +32 -2
  115. infrahub/proposed_change/tasks.py +182 -150
  116. infrahub/py.typed +0 -0
  117. infrahub/server.py +29 -17
  118. infrahub/services/__init__.py +13 -28
  119. infrahub/services/adapters/cache/__init__.py +4 -0
  120. infrahub/services/adapters/cache/nats.py +2 -0
  121. infrahub/services/adapters/cache/redis.py +3 -0
  122. infrahub/services/adapters/message_bus/__init__.py +0 -2
  123. infrahub/services/adapters/message_bus/local.py +1 -2
  124. infrahub/services/adapters/message_bus/nats.py +6 -8
  125. infrahub/services/adapters/message_bus/rabbitmq.py +7 -9
  126. infrahub/services/adapters/workflow/__init__.py +1 -0
  127. infrahub/services/adapters/workflow/local.py +1 -8
  128. infrahub/services/component.py +2 -1
  129. infrahub/task_manager/event.py +52 -0
  130. infrahub/task_manager/models.py +9 -0
  131. infrahub/tasks/artifact.py +6 -7
  132. infrahub/tasks/check.py +4 -7
  133. infrahub/telemetry/tasks.py +15 -18
  134. infrahub/transformations/tasks.py +10 -6
  135. infrahub/trigger/tasks.py +4 -3
  136. infrahub/types.py +4 -0
  137. infrahub/validators/events.py +7 -7
  138. infrahub/validators/tasks.py +6 -7
  139. infrahub/webhook/models.py +45 -45
  140. infrahub/webhook/tasks.py +25 -24
  141. infrahub/workers/dependencies.py +143 -0
  142. infrahub/workers/infrahub_async.py +19 -43
  143. infrahub/workflows/catalogue.py +16 -2
  144. infrahub/workflows/initialization.py +5 -4
  145. infrahub/workflows/models.py +2 -0
  146. infrahub_sdk/client.py +6 -6
  147. infrahub_sdk/ctl/repository.py +51 -0
  148. infrahub_sdk/ctl/schema.py +9 -9
  149. infrahub_sdk/protocols.py +40 -6
  150. {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/METADATA +5 -4
  151. {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/RECORD +158 -144
  152. infrahub_testcontainers/container.py +17 -0
  153. infrahub_testcontainers/docker-compose-cluster.test.yml +56 -1
  154. infrahub_testcontainers/docker-compose.test.yml +56 -1
  155. infrahub_testcontainers/helpers.py +4 -1
  156. {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/LICENSE.txt +0 -0
  157. {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/WHEEL +0 -0
  158. {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/entry_points.txt +0 -0
@@ -12,16 +12,11 @@ from infrahub import __version__, config
12
12
  from infrahub.core import registry, utils
13
13
  from infrahub.core.branch import Branch
14
14
  from infrahub.core.constants import InfrahubKind
15
- from infrahub.services import InfrahubServices
15
+ from infrahub.workers.dependencies import get_component, get_database, get_http
16
16
 
17
17
  from .constants import TELEMETRY_KIND, TELEMETRY_VERSION
18
18
  from .database import gather_database_information
19
- from .models import (
20
- TelemetryBranchData,
21
- TelemetryData,
22
- TelemetrySchemaData,
23
- TelemetryWorkerData,
24
- )
19
+ from .models import TelemetryBranchData, TelemetryData, TelemetrySchemaData, TelemetryWorkerData
25
20
  from .task_manager import gather_prefect_information
26
21
  from .utils import determine_infrahub_type
27
22
 
@@ -37,8 +32,9 @@ async def gather_schema_information(branch: Branch) -> TelemetrySchemaData:
37
32
 
38
33
 
39
34
  @task(name="telemetry-feature-information", task_run_name="Gather Feature Information", cache_policy=NONE)
40
- async def gather_feature_information(service: InfrahubServices) -> dict[str, int]:
41
- async with service.database.start_session(read_only=True) as db:
35
+ async def gather_feature_information() -> dict[str, int]:
36
+ database = await get_database()
37
+ async with database.start_session(read_only=True) as db:
42
38
  data = {}
43
39
  features_to_count = [
44
40
  InfrahubKind.ARTIFACT,
@@ -58,11 +54,12 @@ async def gather_feature_information(service: InfrahubServices) -> dict[str, int
58
54
 
59
55
 
60
56
  @task(name="telemetry-gather-data", task_run_name="Gather Anonynous Data", cache_policy=NONE)
61
- async def gather_anonymous_telemetry_data(service: InfrahubServices) -> TelemetryData:
57
+ async def gather_anonymous_telemetry_data() -> TelemetryData:
62
58
  start_time = time.time()
63
59
 
64
60
  default_branch = registry.get_branch_from_registry()
65
- workers = await service.component.list_workers(branch=default_branch.name, schema_hash=False)
61
+ component = await get_component()
62
+ workers = await component.list_workers(branch=default_branch.name, schema_hash=False)
66
63
 
67
64
  data = TelemetryData(
68
65
  deployment_id=registry.id,
@@ -78,9 +75,9 @@ async def gather_anonymous_telemetry_data(service: InfrahubServices) -> Telemetr
78
75
  branches=TelemetryBranchData(
79
76
  total=len(registry.branch),
80
77
  ),
81
- features=await gather_feature_information(service=service),
78
+ features=await gather_feature_information(),
82
79
  schema_info=await gather_schema_information(branch=default_branch),
83
- database=await gather_database_information(db=service.database),
80
+ database=await gather_database_information(db=await get_database()),
84
81
  prefect=await gather_prefect_information(),
85
82
  )
86
83
 
@@ -90,14 +87,14 @@ async def gather_anonymous_telemetry_data(service: InfrahubServices) -> Telemetr
90
87
 
91
88
 
92
89
  @task(name="telemetry-post-data", task_run_name="Upload data", retries=5, cache_policy=NONE)
93
- async def post_telemetry_data(service: InfrahubServices, url: str, payload: dict[str, Any]) -> None:
90
+ async def post_telemetry_data(url: str, payload: dict[str, Any]) -> None:
94
91
  """Send the telemetry data to the specified URL, using HTTP POST."""
95
- response = await service.http.post(url=url, json=payload)
92
+ response = await get_http().post(url=url, json=payload)
96
93
  response.raise_for_status()
97
94
 
98
95
 
99
96
  @flow(name="anonymous_telemetry_send", flow_run_name="Send anonymous telemetry")
100
- async def send_telemetry_push(service: InfrahubServices) -> None:
97
+ async def send_telemetry_push() -> None:
101
98
  log = get_run_logger()
102
99
  if config.SETTINGS.main.telemetry_optout:
103
100
  log.info("Skipping, User opted out of this service.")
@@ -105,7 +102,7 @@ async def send_telemetry_push(service: InfrahubServices) -> None:
105
102
 
106
103
  log.info(f"Pushing anonymous telemetry data to {config.SETTINGS.main.telemetry_endpoint}...")
107
104
 
108
- data = await gather_anonymous_telemetry_data(service=service)
105
+ data = await gather_anonymous_telemetry_data()
109
106
  data_dict = data.model_dump(mode="json")
110
107
  log.info(f"Anonymous usage telemetry gathered in {data.execution_time} seconds. | {data_dict}")
111
108
 
@@ -116,4 +113,4 @@ async def send_telemetry_push(service: InfrahubServices) -> None:
116
113
  "checksum": hashlib.sha256(json.dumps(data_dict).encode()).hexdigest(),
117
114
  }
118
115
 
119
- await post_telemetry_data(service=service, url=config.SETTINGS.main.telemetry_endpoint, payload=payload)
116
+ await post_telemetry_data(url=config.SETTINGS.main.telemetry_endpoint, payload=payload)
@@ -4,7 +4,7 @@ from prefect import flow
4
4
 
5
5
  from infrahub.git.repository import get_initialized_repo
6
6
  from infrahub.log import get_logger
7
- from infrahub.services import InfrahubServices
7
+ from infrahub.workers.dependencies import get_client
8
8
  from infrahub.workflows.utils import add_branch_tag
9
9
 
10
10
  from .models import TransformJinjaTemplateData, TransformPythonData
@@ -13,23 +13,25 @@ log = get_logger()
13
13
 
14
14
 
15
15
  @flow(name="transform_render_python", flow_run_name="Render transform python", persist_result=True)
16
- async def transform_python(message: TransformPythonData, service: InfrahubServices) -> Any:
16
+ async def transform_python(message: TransformPythonData) -> Any:
17
17
  await add_branch_tag(branch_name=message.branch)
18
18
 
19
+ client = get_client()
20
+
19
21
  repo = await get_initialized_repo(
22
+ client=client,
20
23
  repository_id=message.repository_id,
21
24
  name=message.repository_name,
22
- service=service,
23
25
  repository_kind=message.repository_kind,
24
26
  commit=message.commit,
25
27
  )
26
28
 
27
29
  transformed_data = await repo.execute_python_transform.with_options(timeout_seconds=message.timeout)(
30
+ client=client,
28
31
  branch_name=message.branch,
29
32
  commit=message.commit,
30
33
  location=message.transform_location,
31
34
  data=message.data,
32
- client=service.client,
33
35
  convert_query_response=message.convert_query_response,
34
36
  ) # type: ignore[misc]
35
37
 
@@ -37,13 +39,15 @@ async def transform_python(message: TransformPythonData, service: InfrahubServic
37
39
 
38
40
 
39
41
  @flow(name="transform_render_jinja2_template", flow_run_name="Render transform Jinja2", persist_result=True)
40
- async def transform_render_jinja2_template(message: TransformJinjaTemplateData, service: InfrahubServices) -> str:
42
+ async def transform_render_jinja2_template(message: TransformJinjaTemplateData) -> str:
41
43
  await add_branch_tag(branch_name=message.branch)
42
44
 
45
+ client = get_client()
46
+
43
47
  repo = await get_initialized_repo(
48
+ client=client,
44
49
  repository_id=message.repository_id,
45
50
  name=message.repository_name,
46
- service=service,
47
51
  repository_kind=message.repository_kind,
48
52
  commit=message.commit,
49
53
  )
infrahub/trigger/tasks.py CHANGED
@@ -6,16 +6,17 @@ from infrahub.computed_attribute.gather import (
6
6
  gather_trigger_computed_attribute_jinja2,
7
7
  gather_trigger_computed_attribute_python,
8
8
  )
9
- from infrahub.services import InfrahubServices
10
9
  from infrahub.trigger.catalogue import builtin_triggers
11
10
  from infrahub.webhook.gather import gather_trigger_webhook
11
+ from infrahub.workers.dependencies import get_database
12
12
 
13
13
  from .setup import setup_triggers
14
14
 
15
15
 
16
16
  @flow(name="trigger-configure-all", flow_run_name="Configure all triggers")
17
- async def trigger_configure_all(service: InfrahubServices) -> None:
18
- async with service.database.start_session() as db:
17
+ async def trigger_configure_all() -> None:
18
+ database = await get_database()
19
+ async with database.start_session() as db:
19
20
  webhook_trigger = await gather_trigger_webhook(db=db)
20
21
  computed_attribute_j2_triggers = await gather_trigger_computed_attribute_jinja2()
21
22
  (
infrahub/types.py CHANGED
@@ -379,3 +379,7 @@ def get_attribute_type(kind: str = "Default") -> type[InfrahubDataType]:
379
379
  """Return an InfrahubDataType object for a given kind
380
380
  If no kind is provided, return the default one."""
381
381
  return ATTRIBUTE_TYPES.get(kind, Default)
382
+
383
+
384
+ def is_large_attribute_type(kind: str) -> bool:
385
+ return ATTRIBUTE_TYPES[kind] in LARGE_ATTRIBUTE_TYPES
@@ -3,11 +3,11 @@ from infrahub_sdk.protocols import CoreValidator
3
3
  from infrahub.context import InfrahubContext
4
4
  from infrahub.events.models import EventMeta
5
5
  from infrahub.events.validator_action import ValidatorFailedEvent, ValidatorPassedEvent, ValidatorStartedEvent
6
- from infrahub.services import InfrahubServices
6
+ from infrahub.services.adapters.event import InfrahubEventService
7
7
 
8
8
 
9
9
  async def send_failed_validator(
10
- service: InfrahubServices, validator: CoreValidator, proposed_change_id: str, context: InfrahubContext
10
+ event_service: InfrahubEventService, validator: CoreValidator, proposed_change_id: str, context: InfrahubContext
11
11
  ) -> None:
12
12
  event = ValidatorFailedEvent(
13
13
  node_id=validator.id,
@@ -15,11 +15,11 @@ async def send_failed_validator(
15
15
  proposed_change_id=proposed_change_id,
16
16
  meta=EventMeta.from_context(context=context),
17
17
  )
18
- await service.event.send(event=event)
18
+ await event_service.send(event=event)
19
19
 
20
20
 
21
21
  async def send_passed_validator(
22
- service: InfrahubServices, validator: CoreValidator, proposed_change_id: str, context: InfrahubContext
22
+ event_service: InfrahubEventService, validator: CoreValidator, proposed_change_id: str, context: InfrahubContext
23
23
  ) -> None:
24
24
  event = ValidatorPassedEvent(
25
25
  node_id=validator.id,
@@ -27,11 +27,11 @@ async def send_passed_validator(
27
27
  proposed_change_id=proposed_change_id,
28
28
  meta=EventMeta.from_context(context=context),
29
29
  )
30
- await service.event.send(event=event)
30
+ await event_service.send(event=event)
31
31
 
32
32
 
33
33
  async def send_start_validator(
34
- service: InfrahubServices, validator: CoreValidator, proposed_change_id: str, context: InfrahubContext
34
+ event_service: InfrahubEventService, validator: CoreValidator, proposed_change_id: str, context: InfrahubContext
35
35
  ) -> None:
36
36
  event = ValidatorStartedEvent(
37
37
  node_id=validator.id,
@@ -39,4 +39,4 @@ async def send_start_validator(
39
39
  proposed_change_id=proposed_change_id,
40
40
  meta=EventMeta.from_context(context=context),
41
41
  )
42
- await service.event.send(event=event)
42
+ await event_service.send(event=event)
@@ -1,10 +1,11 @@
1
1
  from typing import Any, TypeVar, cast
2
2
 
3
+ from infrahub_sdk.client import InfrahubClient
3
4
  from infrahub_sdk.protocols import CoreValidator
4
5
 
5
6
  from infrahub.context import InfrahubContext
6
7
  from infrahub.core.constants import ValidatorConclusion, ValidatorState
7
- from infrahub.services import InfrahubServices
8
+ from infrahub.workers.dependencies import get_event_service
8
9
 
9
10
  from .events import send_start_validator
10
11
 
@@ -12,7 +13,7 @@ ValidatorType = TypeVar("ValidatorType", bound=CoreValidator)
12
13
 
13
14
 
14
15
  async def start_validator(
15
- service: InfrahubServices,
16
+ client: InfrahubClient,
16
17
  validator: CoreValidator | None,
17
18
  validator_type: type[ValidatorType],
18
19
  proposed_change: str,
@@ -28,14 +29,12 @@ async def start_validator(
28
29
  validator = cast(ValidatorType, validator)
29
30
  else:
30
31
  data["proposed_change"] = proposed_change
31
- validator = await service.client.create(
32
- kind=validator_type,
33
- data=data,
34
- )
32
+ validator = await client.create(kind=validator_type, data=data)
35
33
  await validator.save()
36
34
 
35
+ event_service = await get_event_service()
37
36
  await send_start_validator(
38
- service=service, validator=validator, proposed_change_id=proposed_change, context=context
37
+ event_service=event_service, validator=validator, proposed_change_id=proposed_change, context=context
39
38
  )
40
39
 
41
40
  return validator
@@ -11,7 +11,7 @@ from pydantic import BaseModel, ConfigDict, Field, computed_field
11
11
  from typing_extensions import Self
12
12
 
13
13
  from infrahub.core import registry
14
- from infrahub.core.constants import InfrahubKind
14
+ from infrahub.core.constants import GLOBAL_BRANCH_NAME, InfrahubKind
15
15
  from infrahub.core.timestamp import Timestamp
16
16
  from infrahub.events.utils import get_all_infrahub_node_kind_events
17
17
  from infrahub.git.repository import InfrahubReadOnlyRepository, InfrahubRepository
@@ -21,10 +21,11 @@ from infrahub.workflows.catalogue import WEBHOOK_PROCESS
21
21
 
22
22
  if TYPE_CHECKING:
23
23
  from httpx import Response
24
+ from infrahub_sdk.client import InfrahubClient
24
25
  from infrahub_sdk.protocols import CoreCustomWebhook, CoreStandardWebhook, CoreTransformPython, CoreWebhook
25
26
 
26
27
  from infrahub.core.protocols import CoreWebhook as CoreWebhookNode
27
- from infrahub.services import InfrahubServices
28
+ from infrahub.services.adapters.http import InfrahubHTTP
28
29
 
29
30
 
30
31
  class WebhookTriggerDefinition(TriggerDefinition):
@@ -104,9 +105,8 @@ class EventContext(BaseModel):
104
105
 
105
106
  return cls(
106
107
  id=event_id,
107
- branch=branch_info.get("name")
108
- if branch_info and branch_info.get("name") != registry.get_global_branch().name
109
- else None,
108
+ # We use `GLOBAL_BRANCH_NAME` constant instead of `registry.get_global_branch().name` to the flow from depending on the registry
109
+ branch=branch_info.get("name") if branch_info and branch_info.get("name") != GLOBAL_BRANCH_NAME else None,
110
110
  account_id=account_info.get("account_id"),
111
111
  occured_at=event_occured_at,
112
112
  event=event_type,
@@ -121,25 +121,48 @@ class Webhook(BaseModel):
121
121
  validate_certificates: bool = Field(...)
122
122
  _payload: Any = None
123
123
  _headers: dict[str, Any] | None = None
124
+ shared_key: str | None = Field(default=None, description="Shared key for signing the webhook requests")
124
125
 
125
- async def _prepare_payload(self, data: dict[str, Any], context: EventContext, service: InfrahubServices) -> None: # noqa: ARG002
126
+ async def _prepare_payload(self, data: dict[str, Any], context: EventContext, client: InfrahubClient) -> None: # noqa: ARG002
126
127
  self._payload = {"data": data, **context.model_dump()}
127
128
 
128
- def _assign_headers(self) -> None:
129
- self._headers = {}
129
+ def _assign_headers(self, uuid: UUID | None = None, at: Timestamp | None = None) -> None:
130
+ self._headers = {
131
+ "Accept": "application/json",
132
+ "Content-Type": "application/json",
133
+ }
134
+
135
+ if self.shared_key:
136
+ message_id = f"msg_{uuid.hex}" if uuid else f"msg_{uuid4().hex}"
137
+ timestamp = str(at.to_timestamp()) if at else str(Timestamp().to_timestamp())
138
+ payload = json.dumps(self._payload or {})
139
+ unsigned_data = f"{message_id}.{timestamp}.{payload}".encode()
140
+ signature = self._sign(data=unsigned_data)
141
+ self._headers["webhook-id"] = message_id
142
+ self._headers["webhook-timestamp"] = timestamp
143
+ self._headers["webhook-signature"] = f"v1,{base64.b64encode(signature).decode('utf-8')}"
130
144
 
131
145
  @computed_field # type: ignore[prop-decorator]
132
146
  @property
133
147
  def webhook_type(self) -> str:
134
148
  return self.__class__.__name__
135
149
 
136
- async def prepare(self, data: dict[str, Any], context: EventContext, service: InfrahubServices) -> None:
137
- await self._prepare_payload(data=data, context=context, service=service)
150
+ @property
151
+ def signing_key(self) -> str:
152
+ """Return the signing key for the webhook."""
153
+ if self.shared_key:
154
+ return self.shared_key
155
+ raise ValueError("Shared key is not set for the webhook")
156
+
157
+ async def prepare(self, data: dict[str, Any], context: EventContext, client: InfrahubClient) -> None:
158
+ await self._prepare_payload(data=data, context=context, client=client)
138
159
  self._assign_headers()
139
160
 
140
- async def send(self, data: dict[str, Any], context: EventContext, service: InfrahubServices) -> Response:
141
- await self.prepare(data=data, context=context, service=service)
142
- return await service.http.post(url=self.url, json=self.get_payload(), headers=self._headers)
161
+ async def send(
162
+ self, data: dict[str, Any], context: EventContext, http_service: InfrahubHTTP, client: InfrahubClient
163
+ ) -> Response:
164
+ await self.prepare(data=data, context=context, client=client)
165
+ return await http_service.post(url=self.url, json=self.get_payload(), headers=self._headers)
143
166
 
144
167
  def get_payload(self) -> dict[str, Any]:
145
168
  return self._payload
@@ -151,6 +174,9 @@ class Webhook(BaseModel):
151
174
  def from_cache(cls, data: dict[str, Any]) -> Self:
152
175
  return cls(**data)
153
176
 
177
+ def _sign(self, data: bytes) -> bytes:
178
+ return hmac.new(key=self.signing_key.encode(), msg=data, digestmod=hashlib.sha256).digest()
179
+
154
180
 
155
181
  class CustomWebhook(Webhook):
156
182
  """Custom webhook"""
@@ -162,30 +188,11 @@ class CustomWebhook(Webhook):
162
188
  url=obj.url.value,
163
189
  event_type=obj.event_type.value,
164
190
  validate_certificates=obj.validate_certificates.value or False,
191
+ shared_key=obj.shared_key.value,
165
192
  )
166
193
 
167
194
 
168
195
  class StandardWebhook(Webhook):
169
- shared_key: str = Field(...)
170
-
171
- def _assign_headers(self, uuid: UUID | None = None, at: Timestamp | None = None) -> None:
172
- message_id = f"msg_{uuid.hex}" if uuid else f"msg_{uuid4().hex}"
173
- timestamp = str(at.to_timestamp()) if at else str(Timestamp().to_timestamp())
174
- payload = json.dumps(self._payload or {})
175
- unsigned_data = f"{message_id}.{timestamp}.{payload}".encode()
176
- signature = self._sign(data=unsigned_data)
177
-
178
- self._headers = {
179
- "Accept": "application/json",
180
- "Content-Type": "application/json",
181
- "webhook-id": message_id,
182
- "webhook-timestamp": timestamp,
183
- "webhook-signature": f"v1,{base64.b64encode(signature).decode('utf-8')}",
184
- }
185
-
186
- def _sign(self, data: bytes) -> bytes:
187
- return hmac.new(key=self.shared_key.encode(), msg=data, digestmod=hashlib.sha256).digest()
188
-
189
196
  @classmethod
190
197
  def from_object(cls, obj: CoreStandardWebhook) -> Self:
191
198
  return cls(
@@ -207,22 +214,14 @@ class TransformWebhook(Webhook):
207
214
  transform_timeout: int = Field(...)
208
215
  convert_query_response: bool = Field(...)
209
216
 
210
- async def _prepare_payload(self, data: dict[str, Any], context: EventContext, service: InfrahubServices) -> None:
217
+ async def _prepare_payload(self, data: dict[str, Any], context: EventContext, client: InfrahubClient) -> None:
211
218
  repo: InfrahubReadOnlyRepository | InfrahubRepository
212
219
  if self.repository_kind == InfrahubKind.READONLYREPOSITORY:
213
220
  repo = await InfrahubReadOnlyRepository.init(
214
- id=self.repository_id,
215
- name=self.repository_name,
216
- client=service.client,
217
- service=service,
221
+ id=self.repository_id, name=self.repository_name, client=client
218
222
  )
219
223
  else:
220
- repo = await InfrahubRepository.init(
221
- id=self.repository_id,
222
- name=self.repository_name,
223
- client=service.client,
224
- service=service,
225
- )
224
+ repo = await InfrahubRepository.init(id=self.repository_id, name=self.repository_name, client=client)
226
225
 
227
226
  branch = context.branch or repo.default_branch
228
227
  commit = repo.get_commit_value(branch_name=branch)
@@ -233,7 +232,7 @@ class TransformWebhook(Webhook):
233
232
  location=f"{self.transform_file}::{self.transform_class}",
234
233
  convert_query_response=self.convert_query_response,
235
234
  data={"data": data, **context.model_dump()},
236
- client=service.client,
235
+ client=client,
237
236
  ) # type: ignore[misc]
238
237
 
239
238
  @classmethod
@@ -251,4 +250,5 @@ class TransformWebhook(Webhook):
251
250
  transform_file=transform.file_path.value,
252
251
  transform_timeout=transform.timeout.value,
253
252
  convert_query_response=transform.convert_query_response.value or False,
253
+ shared_key=obj.shared_key.value,
254
254
  )
infrahub/webhook/tasks.py CHANGED
@@ -8,13 +8,13 @@ from infrahub_sdk.protocols import CoreTransformPython, CoreWebhook
8
8
  from prefect import flow, task
9
9
  from prefect.automations import AutomationCore
10
10
  from prefect.cache_policies import NONE
11
- from prefect.client.orchestration import get_client
11
+ from prefect.client.orchestration import get_client as get_prefect_client
12
12
  from prefect.logging import get_run_logger
13
13
 
14
14
  from infrahub.message_bus.types import KVTTL
15
- from infrahub.services import InfrahubServices # noqa: TC001 needed for prefect flow
16
15
  from infrahub.trigger.models import TriggerType
17
16
  from infrahub.trigger.setup import setup_triggers_specific
17
+ from infrahub.workers.dependencies import get_cache, get_client, get_database, get_http
18
18
  from infrahub.workflows.utils import add_tags
19
19
 
20
20
  from .gather import gather_trigger_webhook
@@ -23,6 +23,7 @@ from .models import CustomWebhook, EventContext, StandardWebhook, TransformWebho
23
23
  if TYPE_CHECKING:
24
24
  from httpx import Response
25
25
 
26
+
26
27
  WEBHOOK_MAP: dict[str, type[Webhook]] = {
27
28
  "StandardWebhook": StandardWebhook,
28
29
  "CustomWebhook": CustomWebhook,
@@ -31,10 +32,10 @@ WEBHOOK_MAP: dict[str, type[Webhook]] = {
31
32
 
32
33
 
33
34
  @task(name="webhook-send", task_run_name="Send Standard Webhook {webhook.name}", cache_policy=NONE, retries=3)
34
- async def webhook_send(
35
- webhook: Webhook, context: EventContext, event_data: dict, service: InfrahubServices
36
- ) -> Response:
37
- response = await webhook.send(data=event_data, context=context, service=service)
35
+ async def webhook_send(webhook: Webhook, context: EventContext, event_data: dict) -> Response:
36
+ http_service = get_http()
37
+ client = get_client()
38
+ response = await webhook.send(data=event_data, context=context, http_service=http_service, client=client)
38
39
  response.raise_for_status()
39
40
  return response
40
41
 
@@ -71,21 +72,22 @@ async def webhook_process(
71
72
  event_type: str,
72
73
  event_occured_at: str,
73
74
  event_payload: dict,
74
- service: InfrahubServices,
75
75
  branch_name: str | None = None,
76
76
  ) -> None:
77
77
  log = get_run_logger()
78
+ client = get_client()
79
+ cache = await get_cache()
78
80
 
79
81
  if branch_name:
80
82
  await add_tags(branches=[branch_name])
81
83
 
82
- webhook_data_str = await service.cache.get(key=f"webhook:{webhook_id}")
84
+ webhook_data_str = await cache.get(key=f"webhook:{webhook_id}")
83
85
  if not webhook_data_str:
84
86
  log.info(f"Webhook {webhook_id} not found in cache")
85
- webhook_node = await service.client.get(kind=webhook_kind, id=webhook_id)
86
- webhook = await convert_node_to_webhook(webhook_node=webhook_node, client=service.client)
87
+ webhook_node = await client.get(kind=webhook_kind, id=webhook_id)
88
+ webhook = await convert_node_to_webhook(webhook_node=webhook_node, client=client)
87
89
  webhook_data = webhook.to_cache()
88
- await service.cache.set(key=f"webhook:{webhook_id}", value=ujson.dumps(webhook_data), expires=KVTTL.TWO_HOURS)
90
+ await cache.set(key=f"webhook:{webhook_id}", value=ujson.dumps(webhook_data), expires=KVTTL.TWO_HOURS)
89
91
 
90
92
  else:
91
93
  webhook_data = ujson.loads(webhook_data_str)
@@ -103,35 +105,33 @@ async def webhook_process(
103
105
  event_payload=event_payload,
104
106
  )
105
107
  event_data = event_payload.get("data", {})
106
- response = await webhook_send(webhook=webhook, context=webhook_context, event_data=event_data, service=service)
108
+ response = await webhook_send(webhook=webhook, context=webhook_context, event_data=event_data)
107
109
  log.info(f"Successfully sent webhook to {response.url} with status {response.status_code}")
108
110
 
109
111
 
110
112
  @flow(name="webhook-setup-automation-all", flow_run_name="Configure all webhooks")
111
- async def configure_webhook_all(service: InfrahubServices) -> None:
113
+ async def configure_webhook_all() -> None:
112
114
  log = get_run_logger()
113
115
 
114
- async with service.database.start_session(read_only=True) as db:
116
+ database = await get_database()
117
+ async with database.start_session(read_only=True) as db:
115
118
  triggers = await gather_trigger_webhook(db=db)
116
119
 
117
120
  log.info(f"{len(triggers)} Webhooks automation configuration completed")
118
- await setup_triggers_specific(
119
- gatherer=gather_trigger_webhook, db=service.database, trigger_type=TriggerType.WEBHOOK
120
- ) # type: ignore[misc]
121
+ await setup_triggers_specific(gatherer=gather_trigger_webhook, db=database, trigger_type=TriggerType.WEBHOOK) # type: ignore[misc]
121
122
 
122
123
 
123
124
  @flow(name="webhook-setup-automation-one", flow_run_name="Configurate webhook for {webhook_name}")
124
125
  async def configure_webhook_one(
125
126
  webhook_name: str, # noqa: ARG001
126
127
  event_data: dict,
127
- service: InfrahubServices,
128
128
  ) -> None:
129
129
  log = get_run_logger()
130
130
 
131
- webhook = await service.client.get(kind=CoreWebhook, id=event_data["node_id"])
131
+ webhook = await get_client().get(kind=CoreWebhook, id=event_data["node_id"])
132
132
  trigger = WebhookTriggerDefinition.from_object(webhook)
133
133
 
134
- async with get_client(sync_client=False) as prefect_client:
134
+ async with get_prefect_client(sync_client=False) as prefect_client:
135
135
  # Query the deployment associated with the trigger to have its ID
136
136
  deployment_name = trigger.get_deployment_names()[0]
137
137
  deployment = await prefect_client.read_deployment_by_name(name=f"{deployment_name}/{deployment_name}")
@@ -154,18 +154,18 @@ async def configure_webhook_one(
154
154
  await prefect_client.create_automation(automation=automation)
155
155
  log.info(f"Automation {trigger.generate_name()} created")
156
156
 
157
- await service.cache.delete(key=f"webhook:{webhook.id}")
157
+ cache = await get_cache()
158
+ await cache.delete(key=f"webhook:{webhook.id}")
158
159
 
159
160
 
160
161
  @flow(name="webhook-delete-automation", flow_run_name="Delete webhook automation for {webhook_name}")
161
162
  async def delete_webhook_automation(
162
163
  webhook_id: str,
163
164
  webhook_name: str, # noqa: ARG001
164
- service: InfrahubServices,
165
165
  ) -> None:
166
166
  log = get_run_logger()
167
167
 
168
- async with get_client(sync_client=False) as prefect_client:
168
+ async with get_prefect_client(sync_client=False) as prefect_client:
169
169
  automation_name = WebhookTriggerDefinition.generate_name_from_id(id=webhook_id)
170
170
 
171
171
  existing_automations = await prefect_client.read_automations_by_name(automation_name)
@@ -175,4 +175,5 @@ async def delete_webhook_automation(
175
175
  await prefect_client.delete_automation(automation_id=existing_automation.id)
176
176
  log.info(f"Automation {automation_name} deleted")
177
177
 
178
- await service.cache.delete(key=f"webhook:{webhook_id}")
178
+ cache = await get_cache()
179
+ await cache.delete(key=f"webhook:{webhook_id}")