Skip to content

Commit 2e95050

Browse files
authored
Merge pull request #5877 from opsmill/dga-20250226-webhook
Refactoring of Webhook to support `event_type` and `branch_scope`
2 parents ce844fb + c4da514 commit 2e95050

File tree

24 files changed

+905
-275
lines changed

24 files changed

+905
-275
lines changed

backend/infrahub/computed_attribute/triggers.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
trigger=EventTrigger(events={"infrahub.branch.created"}),
1313
actions=[
1414
ExecuteWorkflow(
15-
name=COMPUTED_ATTRIBUTE_SETUP_PYTHON.name,
15+
workflow=COMPUTED_ATTRIBUTE_SETUP_PYTHON,
1616
parameters={
1717
"branch_name": "{{ event.resource['infrahub.branch.name'] }}",
1818
"trigger_updates": False,
@@ -31,7 +31,7 @@
3131
trigger=EventTrigger(events={"infrahub.repository.update_commit"}),
3232
actions=[
3333
ExecuteWorkflow(
34-
name=COMPUTED_ATTRIBUTE_SETUP_PYTHON.name,
34+
workflow=COMPUTED_ATTRIBUTE_SETUP_PYTHON,
3535
parameters={
3636
"branch_name": "{{ event.resource['infrahub.branch.name'] }}",
3737
"commit": "{{ event.payload['commit'] }}",
@@ -50,7 +50,7 @@
5050
trigger=EventTrigger(events={"infrahub.branch.deleted"}),
5151
actions=[
5252
ExecuteWorkflow(
53-
name=COMPUTED_ATTRIBUTE_REMOVE_PYTHON.name,
53+
workflow=COMPUTED_ATTRIBUTE_REMOVE_PYTHON,
5454
parameters={
5555
"branch_name": "{{ event.resource['infrahub.branch.name'] }}",
5656
"context": {
@@ -67,7 +67,7 @@
6767
trigger=EventTrigger(events={"infrahub.schema.update"}),
6868
actions=[
6969
ExecuteWorkflow(
70-
name=COMPUTED_ATTRIBUTE_SETUP.name,
70+
workflow=COMPUTED_ATTRIBUTE_SETUP,
7171
parameters={
7272
"branch_name": "{{ event.resource['infrahub.branch.name'] }}",
7373
"context": {
@@ -77,7 +77,7 @@
7777
},
7878
),
7979
ExecuteWorkflow(
80-
name=COMPUTED_ATTRIBUTE_SETUP_PYTHON.name,
80+
workflow=COMPUTED_ATTRIBUTE_SETUP_PYTHON,
8181
parameters={
8282
"branch_name": "{{ event.resource['infrahub.branch.name'] }}",
8383
"context": {

backend/infrahub/core/constants/__init__.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,29 @@
4343

4444
NULL_VALUE = "NULL"
4545

46+
EVENT_NAMESPACE = "infrahub"
47+
48+
49+
class EventType(InfrahubStringEnum):
50+
BRANCH_CREATED = f"{EVENT_NAMESPACE}.branch.created"
51+
BRANCH_DELETED = f"{EVENT_NAMESPACE}.branch.deleted"
52+
BRANCH_MERGED = f"{EVENT_NAMESPACE}.branch.merged"
53+
BRANCH_REBASED = f"{EVENT_NAMESPACE}.branch.rebased"
54+
55+
SCHEMA_UPDATED = f"{EVENT_NAMESPACE}.schema.update"
56+
57+
NODE_CREATED = f"{EVENT_NAMESPACE}.node.created"
58+
NODE_UPDATED = f"{EVENT_NAMESPACE}.node.updated"
59+
NODE_DELETED = f"{EVENT_NAMESPACE}.node.deleted"
60+
61+
GROUP_MEMBER_ADDED = f"{EVENT_NAMESPACE}.group.member_added"
62+
GROUP_MEMBER_REMOVED = f"{EVENT_NAMESPACE}.group.member_removed"
63+
64+
REPOSITORY_UPDATE_COMMIT = f"{EVENT_NAMESPACE}.repository.update_commit"
65+
66+
ARTIFACT_CREATED = f"{EVENT_NAMESPACE}.artifact.created"
67+
ARTIFACT_UPDATED = f"{EVENT_NAMESPACE}.artifact.updated"
68+
4669

4770
class PermissionLevel(enum.Flag):
4871
READ = 1

backend/infrahub/core/protocols.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ class CoreValidator(CoreNode):
197197

198198
class CoreWebhook(CoreNode):
199199
name: String
200+
event_type: Enum
201+
branch_scope: Dropdown
200202
description: StringOptional
201203
url: URL
202204
validate_certificates: BooleanOptional

backend/infrahub/core/schema/definitions/core/webhook.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
from infrahub.core.constants import (
2-
BranchSupportType,
3-
InfrahubKind,
4-
)
1+
from infrahub.core.constants import AllowOverrideType, BranchSupportType, EventType, InfrahubKind
52

63
core_webhook = {
74
"name": "Webhook",
@@ -12,11 +9,48 @@
129
"order_by": ["name__value"],
1310
"display_labels": ["name__value"],
1411
"include_in_menu": False,
12+
"icon": "mdi:webhook",
1513
"branch": BranchSupportType.AGNOSTIC.value,
1614
"uniqueness_constraints": [["name__value"]],
1715
"attributes": [
1816
{"name": "name", "kind": "Text", "unique": True, "order_weight": 1000},
19-
{"name": "description", "kind": "Text", "optional": True, "order_weight": 2000},
17+
{
18+
"name": "event_type",
19+
"kind": "Text",
20+
"enum": ["all"] + EventType.available_types(),
21+
"default_value": "all",
22+
"order_weight": 1500,
23+
"description": "The event type that triggers the webhook",
24+
},
25+
{
26+
"name": "branch_scope",
27+
"kind": "Dropdown",
28+
"choices": [
29+
{
30+
"name": "all_branches",
31+
"label": "All Branches",
32+
"description": "All branches",
33+
"color": "#fef08a",
34+
},
35+
{
36+
"name": "default_branch",
37+
"label": "Default Branch",
38+
"description": "Only the default branch",
39+
"color": "#86efac",
40+
},
41+
{
42+
"name": "other_branches",
43+
"label": "Other Branches",
44+
"description": "All branches except the default branch",
45+
"color": "#e5e7eb",
46+
},
47+
],
48+
"default_value": "default_branch",
49+
"optional": False,
50+
"order_weight": 2000,
51+
"allow_override": AllowOverrideType.NONE,
52+
},
53+
{"name": "description", "kind": "Text", "optional": True, "order_weight": 2500},
2054
{"name": "url", "kind": "URL", "order_weight": 3000},
2155
{
2256
"name": "validate_certificates",

backend/infrahub/git/integrator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1151,7 +1151,7 @@ async def execute_python_check(
11511151

11521152
@task(name="python-transform-execute", task_run_name="Execute Python Transform", cache_policy=NONE) # type: ignore[arg-type]
11531153
async def execute_python_transform(
1154-
self, branch_name: str, commit: str, location: str, client: InfrahubClient, data: Optional[dict] = None
1154+
self, branch_name: str, commit: str, location: str, client: InfrahubClient, data: dict | None = None
11551155
) -> Any:
11561156
"""Execute A Python Transform stored in the repository."""
11571157
log = get_run_logger()

backend/infrahub/menu/menu.py

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -261,32 +261,11 @@ def _extract_node_icon(model: MainSchemaTypes) -> str:
261261
namespace="Builtin",
262262
name="Webhooks",
263263
label="Webhooks",
264-
icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.CUSTOMWEBHOOK)),
264+
icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.WEBHOOK)),
265+
kind=InfrahubKind.WEBHOOK,
265266
protected=True,
266267
section=MenuSection.INTERNAL,
267268
order_weight=3000,
268-
children=[
269-
MenuItemDefinition(
270-
namespace="Builtin",
271-
name="WebhookStandard",
272-
label="Webhooks",
273-
kind=InfrahubKind.STANDARDWEBHOOK,
274-
icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.STANDARDWEBHOOK)),
275-
protected=True,
276-
section=MenuSection.INTERNAL,
277-
order_weight=1000,
278-
),
279-
MenuItemDefinition(
280-
namespace="Builtin",
281-
name="WebhookCustom",
282-
label="Custom Webhooks",
283-
kind=InfrahubKind.CUSTOMWEBHOOK,
284-
icon=_extract_node_icon(infrahub_schema.get(InfrahubKind.CUSTOMWEBHOOK)),
285-
protected=True,
286-
section=MenuSection.INTERNAL,
287-
order_weight=2000,
288-
),
289-
],
290269
),
291270
MenuItemDefinition(
292271
namespace="Builtin",

backend/infrahub/message_bus/operations/event/worker.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@
22

33
from infrahub.message_bus import messages
44
from infrahub.services import InfrahubServices
5-
from infrahub.workflows.catalogue import WEBHOOK_CONFIGURE
65

76

87
@flow(name="event-worker-newprimary-api")
98
async def new_primary_api(message: messages.EventWorkerNewPrimaryAPI, service: InfrahubServices) -> None:
109
service.log.info("api_worker promoted to primary", worker_id=message.worker_id)
11-
12-
await service.workflow.submit_workflow(workflow=WEBHOOK_CONFIGURE)

backend/infrahub/trigger/catalogue.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
TRIGGER_COMPUTED_ATTRIBUTE_PYTHON_SETUP_BRANCH,
55
TRIGGER_COMPUTED_ATTRIBUTE_PYTHON_SETUP_COMMIT,
66
)
7+
from infrahub.trigger.models import TriggerDefinition
78
from infrahub.webhook.triggers import TRIGGER_WEBHOOK_SETUP_UPDATE
89

9-
triggers = [
10+
builtin_triggers: list[TriggerDefinition] = [
1011
TRIGGER_COMPUTED_ATTRIBUTE_ALL_SCHEMA,
1112
TRIGGER_COMPUTED_ATTRIBUTE_PYTHON_CLEAN_BRANCH,
1213
TRIGGER_COMPUTED_ATTRIBUTE_PYTHON_SETUP_BRANCH,

backend/infrahub/trigger/models.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
1+
from __future__ import annotations
2+
13
from datetime import timedelta
24
from enum import Enum
3-
from typing import Any
4-
from uuid import UUID
5+
from typing import TYPE_CHECKING, Any
56

67
from prefect.events.actions import RunDeployment
78
from prefect.events.schemas.automations import EventTrigger as PrefectEventTrigger
89
from prefect.events.schemas.automations import Posture
910
from prefect.events.schemas.events import ResourceSpecification
1011
from pydantic import BaseModel, Field
1112

13+
from infrahub.workflows.models import WorkflowDefinition # noqa: TC001
14+
1215
from .constants import NAME_SEPARATOR
1316

17+
if TYPE_CHECKING:
18+
from uuid import UUID
19+
1420

1521
class TriggerType(str, Enum):
1622
BUILTIN = "builtin"
23+
WEBHOOK = "webhook"
1724
# OBJECT = "object"
1825
# COMPUTED_ATTR = "computed_attr"
1926

@@ -35,19 +42,44 @@ def get_prefect(self) -> PrefectEventTrigger:
3542

3643

3744
class ExecuteWorkflow(BaseModel):
38-
name: str
45+
workflow: WorkflowDefinition
3946
parameters: dict[str, Any] = Field(default_factory=dict)
4047

48+
@property
49+
def name(self) -> str:
50+
return self.workflow.name
51+
4152
def get_prefect(self, mapping: dict[str, UUID]) -> RunDeployment:
4253
deployment_id = mapping[self.name]
54+
return self.get(deployment_id)
4355

56+
def get(self, id: UUID) -> RunDeployment:
4457
return RunDeployment(
4558
source="selected",
46-
deployment_id=deployment_id,
59+
deployment_id=id,
4760
parameters=self.parameters,
4861
job_variables={},
4962
)
5063

64+
def validate_parameters(self) -> None:
65+
if not self.parameters:
66+
return
67+
68+
workflow_params = self.workflow.get_parameters()
69+
workflow_required_params = [p.name for p in workflow_params.values() if p.required]
70+
trigger_params = list(self.parameters.keys())
71+
72+
missing_required_params = set(workflow_required_params) - set(trigger_params)
73+
wrong_params = set(trigger_params) - set(workflow_params)
74+
75+
if missing_required_params:
76+
raise ValueError(
77+
f"Missing required parameters: {missing_required_params} for workflow {self.workflow.name}"
78+
)
79+
80+
if wrong_params:
81+
raise ValueError(f"Workflow {self.workflow.name} doesn't support parameters: {wrong_params}")
82+
5183

5284
class TriggerDefinition(BaseModel):
5385
name: str
@@ -64,6 +96,10 @@ def get_deployment_names(self) -> list[str]:
6496
def generate_name(self) -> str:
6597
return f"{self.type.value}{NAME_SEPARATOR}{self.name}"
6698

99+
def validate_actions(self) -> None:
100+
for action in self.actions:
101+
action.validate_parameters()
102+
67103

68104
class BuiltinTriggerDefinition(TriggerDefinition):
69105
type: TriggerType = TriggerType.BUILTIN

backend/infrahub/trigger/tasks.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
from typing import TYPE_CHECKING
22

3-
from infrahub_sdk.utils import compare_lists
43
from prefect import get_run_logger, task
54
from prefect.automations import AutomationCore
65
from prefect.cache_policies import NONE
76
from prefect.client.orchestration import PrefectClient
87
from prefect.client.schemas.filters import DeploymentFilter, DeploymentFilterName
98

10-
from .catalogue import triggers
11-
from .constants import DEPRECATED_STATIC_TRIGGER_NAMES
9+
from infrahub.trigger.models import TriggerDefinition
10+
11+
# from .catalogue import triggers
1212
from .models import TriggerType
1313

1414
if TYPE_CHECKING:
1515
from uuid import UUID
1616

1717

18-
@task(name="trigger-setup", task_run_name="Setup triggers in task-manager", cache_policy=NONE) # type: ignore[arg-type]
19-
async def setup_triggers(client: PrefectClient) -> None:
18+
@task(name="trigger-setup", task_run_name="Setup triggers of type {trigger_type.value}", cache_policy=NONE) # type: ignore[arg-type]
19+
async def setup_triggers(
20+
client: PrefectClient,
21+
triggers: list[TriggerDefinition],
22+
trigger_type: TriggerType = TriggerType.BUILTIN,
23+
deprecated_triggers: list[str] | None = None,
24+
) -> None:
2025
log = get_run_logger()
2126

2227
# -------------------------------------------------------------
@@ -32,15 +37,15 @@ async def setup_triggers(client: PrefectClient) -> None:
3237
deployments_mapping: dict[str, UUID] = {name: item.id for name, item in deployments.items()}
3338
existing_automations = {item.name: item for item in await client.read_automations()}
3439

35-
builtin_automations = [
36-
item.name for item in await client.read_automations() if item.name.startswith(TriggerType.BUILTIN.value)
40+
trigger_automations = [
41+
item.name for item in await client.read_automations() if item.name.startswith(trigger_type.value)
3742
]
3843
trigger_names = [trigger.generate_name() for trigger in triggers]
3944

40-
_, to_delete, _ = compare_lists(list1=builtin_automations, list2=trigger_names)
45+
to_delete = set(trigger_automations) - set(trigger_names)
4146

4247
# -------------------------------------------------------------
43-
# Create or Update all builtin triggers
48+
# Create or Update all triggers
4449
# -------------------------------------------------------------
4550
for trigger in triggers:
4651
automation = AutomationCore(
@@ -61,7 +66,7 @@ async def setup_triggers(client: PrefectClient) -> None:
6166
log.info(f"{trigger.name} Created")
6267

6368
# -------------------------------------------------------------
64-
# Delete Builtin Triggers that shouldn't be there
69+
# Delete Triggers that shouldn't be there
6570
# -------------------------------------------------------------
6671
for item_to_delete in to_delete:
6772
existing_automation = existing_automations.get(item_to_delete)
@@ -75,11 +80,12 @@ async def setup_triggers(client: PrefectClient) -> None:
7580
# -------------------------------------------------------------
7681
# Delete Deprecated triggers
7782
# -------------------------------------------------------------
78-
for trigger_name in DEPRECATED_STATIC_TRIGGER_NAMES:
79-
existing_automation = existing_automations.get(trigger_name)
83+
if deprecated_triggers:
84+
for trigger_name in deprecated_triggers:
85+
existing_automation = existing_automations.get(trigger_name)
8086

81-
if not existing_automation:
82-
continue
87+
if not existing_automation:
88+
continue
8389

84-
await client.delete_automation(automation_id=existing_automation.id)
85-
log.info(f"{trigger_name} Deleted")
90+
await client.delete_automation(automation_id=existing_automation.id)
91+
log.info(f"{trigger_name} Deleted")

0 commit comments

Comments
 (0)