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.
- infrahub/api/internal.py +5 -0
- infrahub/artifacts/tasks.py +17 -22
- infrahub/branch/merge_mutation_checker.py +38 -0
- infrahub/cli/__init__.py +2 -2
- infrahub/cli/context.py +7 -3
- infrahub/cli/db.py +5 -16
- infrahub/cli/upgrade.py +7 -29
- infrahub/computed_attribute/tasks.py +36 -46
- infrahub/config.py +53 -2
- infrahub/constants/environment.py +1 -0
- infrahub/core/attribute.py +9 -7
- infrahub/core/branch/tasks.py +43 -41
- infrahub/core/constants/__init__.py +20 -6
- infrahub/core/constants/infrahubkind.py +2 -0
- infrahub/core/diff/coordinator.py +3 -1
- infrahub/core/diff/repository/repository.py +0 -8
- infrahub/core/diff/tasks.py +11 -8
- infrahub/core/graph/__init__.py +1 -1
- infrahub/core/graph/index.py +1 -2
- infrahub/core/graph/schema.py +50 -29
- infrahub/core/initialization.py +62 -33
- infrahub/core/ipam/tasks.py +4 -3
- infrahub/core/merge.py +8 -10
- infrahub/core/migrations/graph/__init__.py +2 -0
- infrahub/core/migrations/graph/m035_drop_attr_value_index.py +45 -0
- infrahub/core/migrations/query/attribute_add.py +27 -2
- infrahub/core/migrations/schema/tasks.py +6 -5
- infrahub/core/node/proposed_change.py +43 -0
- infrahub/core/protocols.py +12 -0
- infrahub/core/query/attribute.py +32 -14
- infrahub/core/query/diff.py +11 -0
- infrahub/core/query/ipam.py +13 -7
- infrahub/core/query/node.py +51 -10
- infrahub/core/query/resource_manager.py +3 -3
- infrahub/core/schema/basenode_schema.py +8 -0
- infrahub/core/schema/definitions/core/__init__.py +10 -1
- infrahub/core/schema/definitions/core/ipam.py +28 -2
- infrahub/core/schema/definitions/core/propose_change.py +15 -0
- infrahub/core/schema/definitions/core/webhook.py +3 -0
- infrahub/core/schema/generic_schema.py +10 -0
- infrahub/core/schema/manager.py +10 -1
- infrahub/core/schema/node_schema.py +22 -17
- infrahub/core/schema/profile_schema.py +8 -0
- infrahub/core/schema/schema_branch.py +9 -5
- infrahub/core/schema/template_schema.py +8 -0
- infrahub/core/validators/checks_runner.py +5 -5
- infrahub/core/validators/tasks.py +6 -7
- infrahub/core/validators/uniqueness/checker.py +4 -2
- infrahub/core/validators/uniqueness/model.py +1 -0
- infrahub/core/validators/uniqueness/query.py +57 -7
- infrahub/database/__init__.py +2 -1
- infrahub/events/__init__.py +18 -0
- infrahub/events/constants.py +7 -0
- infrahub/events/generator.py +29 -2
- infrahub/events/proposed_change_action.py +181 -0
- infrahub/generators/tasks.py +24 -20
- infrahub/git/base.py +4 -7
- infrahub/git/integrator.py +21 -12
- infrahub/git/repository.py +15 -30
- infrahub/git/tasks.py +121 -106
- infrahub/graphql/field_extractor.py +69 -0
- infrahub/graphql/manager.py +15 -11
- infrahub/graphql/mutations/account.py +2 -2
- infrahub/graphql/mutations/action.py +8 -2
- infrahub/graphql/mutations/artifact_definition.py +4 -1
- infrahub/graphql/mutations/branch.py +10 -5
- infrahub/graphql/mutations/graphql_query.py +2 -1
- infrahub/graphql/mutations/main.py +14 -8
- infrahub/graphql/mutations/menu.py +2 -1
- infrahub/graphql/mutations/proposed_change.py +225 -8
- infrahub/graphql/mutations/relationship.py +5 -0
- infrahub/graphql/mutations/repository.py +2 -1
- infrahub/graphql/mutations/tasks.py +7 -9
- infrahub/graphql/mutations/webhook.py +4 -1
- infrahub/graphql/parser.py +15 -6
- infrahub/graphql/queries/__init__.py +10 -1
- infrahub/graphql/queries/account.py +3 -3
- infrahub/graphql/queries/branch.py +2 -2
- infrahub/graphql/queries/diff/tree.py +3 -3
- infrahub/graphql/queries/event.py +13 -3
- infrahub/graphql/queries/ipam.py +23 -1
- infrahub/graphql/queries/proposed_change.py +84 -0
- infrahub/graphql/queries/relationship.py +2 -2
- infrahub/graphql/queries/resource_manager.py +3 -3
- infrahub/graphql/queries/search.py +3 -2
- infrahub/graphql/queries/status.py +3 -2
- infrahub/graphql/queries/task.py +2 -2
- infrahub/graphql/resolvers/ipam.py +440 -0
- infrahub/graphql/resolvers/many_relationship.py +4 -3
- infrahub/graphql/resolvers/resolver.py +5 -5
- infrahub/graphql/resolvers/single_relationship.py +3 -2
- infrahub/graphql/schema.py +25 -5
- infrahub/graphql/types/__init__.py +2 -2
- infrahub/graphql/types/attribute.py +3 -3
- infrahub/graphql/types/event.py +60 -0
- infrahub/groups/tasks.py +6 -6
- infrahub/lock.py +3 -2
- infrahub/menu/generator.py +8 -0
- infrahub/message_bus/operations/__init__.py +9 -12
- infrahub/message_bus/operations/git/file.py +6 -5
- infrahub/message_bus/operations/git/repository.py +12 -20
- infrahub/message_bus/operations/refresh/registry.py +15 -9
- infrahub/message_bus/operations/send/echo.py +7 -4
- infrahub/message_bus/types.py +1 -0
- infrahub/permissions/globals.py +1 -4
- infrahub/permissions/manager.py +8 -5
- infrahub/pools/prefix.py +7 -5
- infrahub/prefect_server/app.py +31 -0
- infrahub/prefect_server/bootstrap.py +18 -0
- infrahub/proposed_change/action_checker.py +206 -0
- infrahub/proposed_change/approval_revoker.py +40 -0
- infrahub/proposed_change/branch_diff.py +3 -1
- infrahub/proposed_change/checker.py +45 -0
- infrahub/proposed_change/constants.py +32 -2
- infrahub/proposed_change/tasks.py +182 -150
- infrahub/py.typed +0 -0
- infrahub/server.py +29 -17
- infrahub/services/__init__.py +13 -28
- infrahub/services/adapters/cache/__init__.py +4 -0
- infrahub/services/adapters/cache/nats.py +2 -0
- infrahub/services/adapters/cache/redis.py +3 -0
- infrahub/services/adapters/message_bus/__init__.py +0 -2
- infrahub/services/adapters/message_bus/local.py +1 -2
- infrahub/services/adapters/message_bus/nats.py +6 -8
- infrahub/services/adapters/message_bus/rabbitmq.py +7 -9
- infrahub/services/adapters/workflow/__init__.py +1 -0
- infrahub/services/adapters/workflow/local.py +1 -8
- infrahub/services/component.py +2 -1
- infrahub/task_manager/event.py +52 -0
- infrahub/task_manager/models.py +9 -0
- infrahub/tasks/artifact.py +6 -7
- infrahub/tasks/check.py +4 -7
- infrahub/telemetry/tasks.py +15 -18
- infrahub/transformations/tasks.py +10 -6
- infrahub/trigger/tasks.py +4 -3
- infrahub/types.py +4 -0
- infrahub/validators/events.py +7 -7
- infrahub/validators/tasks.py +6 -7
- infrahub/webhook/models.py +45 -45
- infrahub/webhook/tasks.py +25 -24
- infrahub/workers/dependencies.py +143 -0
- infrahub/workers/infrahub_async.py +19 -43
- infrahub/workflows/catalogue.py +16 -2
- infrahub/workflows/initialization.py +5 -4
- infrahub/workflows/models.py +2 -0
- infrahub_sdk/client.py +6 -6
- infrahub_sdk/ctl/repository.py +51 -0
- infrahub_sdk/ctl/schema.py +9 -9
- infrahub_sdk/protocols.py +40 -6
- {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/METADATA +5 -4
- {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/RECORD +158 -144
- infrahub_testcontainers/container.py +17 -0
- infrahub_testcontainers/docker-compose-cluster.test.yml +56 -1
- infrahub_testcontainers/docker-compose.test.yml +56 -1
- infrahub_testcontainers/helpers.py +4 -1
- {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/LICENSE.txt +0 -0
- {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/WHEEL +0 -0
- {infrahub_server-1.3.5.dist-info → infrahub_server-1.4.0b0.dist-info}/entry_points.txt +0 -0
infrahub/telemetry/tasks.py
CHANGED
|
@@ -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.
|
|
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(
|
|
41
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
78
|
+
features=await gather_feature_information(),
|
|
82
79
|
schema_info=await gather_schema_information(branch=default_branch),
|
|
83
|
-
database=await gather_database_information(db=
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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
|
|
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
|
|
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(
|
|
18
|
-
|
|
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
|
infrahub/validators/events.py
CHANGED
|
@@ -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
|
|
6
|
+
from infrahub.services.adapters.event import InfrahubEventService
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
async def send_failed_validator(
|
|
10
|
-
|
|
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
|
|
18
|
+
await event_service.send(event=event)
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
async def send_passed_validator(
|
|
22
|
-
|
|
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
|
|
30
|
+
await event_service.send(event=event)
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
async def send_start_validator(
|
|
34
|
-
|
|
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
|
|
42
|
+
await event_service.send(event=event)
|
infrahub/validators/tasks.py
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
37
|
+
event_service=event_service, validator=validator, proposed_change_id=proposed_change, context=context
|
|
39
38
|
)
|
|
40
39
|
|
|
41
40
|
return validator
|
infrahub/webhook/models.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
108
|
-
if branch_info and branch_info.get("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,
|
|
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
|
-
|
|
137
|
-
|
|
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(
|
|
141
|
-
|
|
142
|
-
|
|
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,
|
|
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=
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
response = await webhook.send(data=event_data, context=context,
|
|
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
|
|
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
|
|
86
|
-
webhook = await convert_node_to_webhook(webhook_node=webhook_node, 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
|
|
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
|
|
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(
|
|
113
|
+
async def configure_webhook_all() -> None:
|
|
112
114
|
log = get_run_logger()
|
|
113
115
|
|
|
114
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
178
|
+
cache = await get_cache()
|
|
179
|
+
await cache.delete(key=f"webhook:{webhook_id}")
|