Skip to content

Commit 1b4c36a

Browse files
authored
Merge pull request #5944 from opsmill/dga-20250305-webhook--IFC-1355
Various fixes for Webhook
2 parents 42eca37 + 7f88cd7 commit 1b4c36a

File tree

8 files changed

+93
-31
lines changed

8 files changed

+93
-31
lines changed

backend/infrahub/trigger/catalogue.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
TRIGGER_COMPUTED_ATTRIBUTE_PYTHON_SETUP_COMMIT,
66
)
77
from infrahub.trigger.models import TriggerDefinition
8-
from infrahub.webhook.triggers import TRIGGER_WEBHOOK_SETUP_UPDATE
8+
from infrahub.webhook.triggers import TRIGGER_WEBHOOK_DELETE, TRIGGER_WEBHOOK_SETUP_UPDATE
99

1010
builtin_triggers: list[TriggerDefinition] = [
1111
TRIGGER_COMPUTED_ATTRIBUTE_ALL_SCHEMA,
1212
TRIGGER_COMPUTED_ATTRIBUTE_PYTHON_CLEAN_BRANCH,
1313
TRIGGER_COMPUTED_ATTRIBUTE_PYTHON_SETUP_BRANCH,
1414
TRIGGER_COMPUTED_ATTRIBUTE_PYTHON_SETUP_COMMIT,
15+
TRIGGER_WEBHOOK_DELETE,
1516
TRIGGER_WEBHOOK_SETUP_UPDATE,
1617
]

backend/infrahub/trigger/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ def get_deployment_names(self) -> list[str]:
9393
"""Return the name of all deployments used by this trigger"""
9494
return [action.name for action in self.actions]
9595

96+
def get_description(self) -> str:
97+
return f"Automation for Trigger {self.name} of type {self.type.value}"
98+
9699
def generate_name(self) -> str:
97100
return f"{self.type.value}{NAME_SEPARATOR}{self.name}"
98101

backend/infrahub/webhook/models.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from infrahub.core.constants import InfrahubKind
1414
from infrahub.core.timestamp import Timestamp
1515
from infrahub.git.repository import InfrahubReadOnlyRepository, InfrahubRepository
16+
from infrahub.trigger.constants import NAME_SEPARATOR
1617
from infrahub.trigger.models import EventTrigger, ExecuteWorkflow, TriggerDefinition, TriggerType
1718
from infrahub.workflows.catalogue import WEBHOOK_PROCESS
1819

@@ -24,8 +25,16 @@
2425

2526

2627
class WebhookTriggerDefinition(TriggerDefinition):
28+
id: str
2729
type: TriggerType = TriggerType.WEBHOOK
2830

31+
def generate_name(self) -> str:
32+
return f"{self.type.value}{NAME_SEPARATOR}{self.id}"
33+
34+
@classmethod
35+
def generate_name_from_id(cls, id: str) -> str:
36+
return f"{TriggerType.WEBHOOK.value}{NAME_SEPARATOR}{id}"
37+
2938
@classmethod
3039
def from_object(cls, obj: CoreWebhook) -> Self:
3140
event_trigger = EventTrigger()
@@ -46,6 +55,7 @@ def from_object(cls, obj: CoreWebhook) -> Self:
4655
}
4756

4857
definition = cls(
58+
id=obj.id,
4959
name=obj.name.value,
5060
trigger=event_trigger,
5161
actions=[

backend/infrahub/webhook/tasks.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -119,38 +119,60 @@ async def configure_webhook_all(service: InfrahubServices) -> None:
119119
log.info(f"{len(triggers)} Webhooks automation configuration completed")
120120

121121

122-
@flow(name="webhook-setup-automation-one", flow_run_name="Configuration webhook automation for one webhook")
123-
async def configure_webhook_one(event_type: str, event_data: dict, service: InfrahubServices) -> None:
122+
@flow(name="webhook-setup-automation-one", flow_run_name="Configuration webhook automation for {webhook_name}")
123+
async def configure_webhook_one(
124+
webhook_name: str, # noqa: ARG001
125+
event_data: dict,
126+
service: InfrahubServices,
127+
) -> None:
124128
log = get_run_logger()
125129

126130
webhook = await service.client.get(kind=CoreWebhook, id=event_data["node_id"])
127131
trigger = WebhookTriggerDefinition.from_object(webhook)
128132

129-
delete_automation: bool = "infrahub.node.deleted" in event_type
130-
131133
async with get_client(sync_client=False) as prefect_client:
132134
# Query the deployment associated with the trigger to have its ID
133135
deployment_name = trigger.get_deployment_names()[0]
134136
deployment = await prefect_client.read_deployment_by_name(name=f"{deployment_name}/{deployment_name}")
135137

136138
automation = AutomationCore(
137139
name=trigger.generate_name(),
138-
description=trigger.description,
140+
description=trigger.get_description(),
139141
enabled=True,
140142
trigger=trigger.trigger.get_prefect(),
141143
actions=[action.get(deployment.id) for action in trigger.actions],
142144
)
143145

144146
existing_automations = await prefect_client.read_automations_by_name(trigger.generate_name())
147+
existing_automation = existing_automations[0] if existing_automations else None
145148

146-
if existing_automations and not delete_automation:
147-
existing_automation = existing_automations[0]
149+
if existing_automation:
148150
await prefect_client.update_automation(automation_id=existing_automation.id, automation=automation)
149151
log.info(f"Automation {trigger.generate_name()} updated")
150-
elif existing_automations and delete_automation:
151-
existing_automation = existing_automations[0]
152-
await prefect_client.delete_automation(automation_id=existing_automation.id)
153-
log.info(f"Automation {trigger.generate_name()} deleted")
154152
else:
155153
await prefect_client.create_automation(automation=automation)
156154
log.info(f"Automation {trigger.generate_name()} created")
155+
156+
await service.cache.delete(key=f"webhook:{webhook.id}")
157+
158+
159+
@flow(name="webhook-delete-automation", flow_run_name="Delete webhook automation for {webhook_name}")
160+
async def delete_webhook_automation(
161+
webhook_id: str,
162+
webhook_name: str, # noqa: ARG001
163+
event_data: dict, # noqa: ARG001
164+
service: InfrahubServices,
165+
) -> None:
166+
log = get_run_logger()
167+
168+
async with get_client(sync_client=False) as prefect_client:
169+
automation_name = WebhookTriggerDefinition.generate_name_from_id(id=webhook_id)
170+
171+
existing_automations = await prefect_client.read_automations_by_name(automation_name)
172+
existing_automation = existing_automations[0] if existing_automations else None
173+
174+
if existing_automation:
175+
await prefect_client.delete_automation(automation_id=existing_automation.id)
176+
log.info(f"Automation {automation_name} deleted")
177+
178+
await service.cache.delete(key=f"webhook:{webhook_id}")

backend/infrahub/webhook/triggers.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
from infrahub.core.constants import InfrahubKind
22
from infrahub.trigger.models import BuiltinTriggerDefinition, EventTrigger, ExecuteWorkflow
3-
from infrahub.workflows.catalogue import (
4-
WEBHOOK_CONFIGURE_ONE,
5-
)
3+
from infrahub.workflows.catalogue import WEBHOOK_CONFIGURE_ONE, WEBHOOK_DELETE_AUTOMATION
64

75
TRIGGER_WEBHOOK_SETUP_UPDATE = BuiltinTriggerDefinition(
86
name="webhook-configure-one",
97
trigger=EventTrigger(
10-
events={"infrahub.node.*"},
8+
events={"infrahub.node.created", "infrahub.node.updated"},
119
match={
1210
"infrahub.node.kind": [InfrahubKind.CUSTOMWEBHOOK, InfrahubKind.STANDARDWEBHOOK],
1311
},
@@ -16,7 +14,30 @@
1614
ExecuteWorkflow(
1715
workflow=WEBHOOK_CONFIGURE_ONE,
1816
parameters={
19-
"event_type": "{{ event.event }}",
17+
"webhook_name": "{{ event.payload['data']['display_label'] }}",
18+
"event_data": {
19+
"__prefect_kind": "json",
20+
"value": {"__prefect_kind": "jinja", "template": "{{ event.payload['data'] | tojson }}"},
21+
},
22+
},
23+
),
24+
],
25+
)
26+
27+
TRIGGER_WEBHOOK_DELETE = BuiltinTriggerDefinition(
28+
name="webhook-delete",
29+
trigger=EventTrigger(
30+
events={"infrahub.node.deleted"},
31+
match={
32+
"infrahub.node.kind": [InfrahubKind.CUSTOMWEBHOOK, InfrahubKind.STANDARDWEBHOOK],
33+
},
34+
),
35+
actions=[
36+
ExecuteWorkflow(
37+
workflow=WEBHOOK_DELETE_AUTOMATION,
38+
parameters={
39+
"webhook_id": "{{ event.payload['data']['node_id'] }}",
40+
"webhook_name": "{{ event.payload['data']['display_label'] }}",
2041
"event_data": {
2142
"__prefect_kind": "json",
2243
"value": {"__prefect_kind": "jinja", "template": "{{ event.payload['data'] | tojson }}"},

backend/infrahub/workflows/catalogue.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,12 +366,19 @@
366366

367367
WEBHOOK_CONFIGURE_ALL = WorkflowDefinition(
368368
name="webhook-setup-automation-all",
369-
type=WorkflowType.CORE,
369+
type=WorkflowType.INTERNAL,
370370
cron=f"{random.randint(0, 59)} 3 * * *",
371371
module="infrahub.webhook.tasks",
372372
function="configure_webhook_all",
373373
)
374374

375+
WEBHOOK_DELETE_AUTOMATION = WorkflowDefinition(
376+
name="webhook-delete-automation",
377+
type=WorkflowType.CORE,
378+
module="infrahub.webhook.tasks",
379+
function="delete_webhook_automation",
380+
)
381+
375382
GIT_REPOSITORIES_CHECK_ARTIFACT_CREATE = WorkflowDefinition(
376383
name="git-repository-check-artifact-create",
377384
type=WorkflowType.USER,
@@ -472,5 +479,6 @@
472479
UPDATE_COMPUTED_ATTRIBUTE_TRANSFORM,
473480
WEBHOOK_CONFIGURE_ALL,
474481
WEBHOOK_CONFIGURE_ONE,
482+
WEBHOOK_DELETE_AUTOMATION,
475483
WEBHOOK_PROCESS,
476484
]

backend/tests/functional/webhook/test_task.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
configure_webhook_all,
1515
configure_webhook_one,
1616
convert_node_to_webhook,
17+
delete_webhook_automation,
1718
webhook_process,
1819
)
1920
from infrahub.workflows.catalogue import WEBHOOK_PROCESS, worker_pools
@@ -155,11 +156,9 @@ async def webhook3(self, db: InfrahubDatabase, initial_dataset: None, client: In
155156
async def test_configure_one(
156157
self, db: InfrahubDatabase, service, prefect_client: PrefectClient, webhook1: Node, webhook_deployment
157158
) -> None:
158-
await configure_webhook_one(
159-
event_type="infrahub.node.created", event_data={"node_id": webhook1.id}, service=service
160-
)
159+
await configure_webhook_one(webhook_name="Webhook1", event_data={"node_id": webhook1.id}, service=service)
161160

162-
name = "webhook::Webhook1"
161+
name = f"webhook::{webhook1.id}"
163162
automations = await prefect_client.read_automations_by_name(name=name)
164163
assert len(automations) == 1
165164
automation = automations[0]
@@ -172,15 +171,13 @@ async def test_configure_one(
172171
assert action.parameters["webhook_kind"] == "CoreStandardWebhook"
173172

174173
# Configure it a second time to ensure the function is idempotent
175-
await configure_webhook_one(
176-
event_type="infrahub.node.created", event_data={"node_id": webhook1.id}, service=service
177-
)
174+
await configure_webhook_one(webhook_name="Webhook1", event_data={"node_id": webhook1.id}, service=service)
178175
automations = await prefect_client.read_automations_by_name(name=name)
179176
assert len(automations) == 1
180177

181178
# Delete the webhook automation
182-
await configure_webhook_one(
183-
event_type="infrahub.node.deleted", event_data={"node_id": webhook1.id}, service=service
179+
await delete_webhook_automation(
180+
webhook_id=webhook1.id, webhook_name="Webhook1", event_data={"node_id": webhook1.id}, service=service
184181
)
185182
automations = await prefect_client.read_automations_by_name(name=name)
186183
assert len(automations) == 0
@@ -199,10 +196,10 @@ async def test_configure_all(
199196
automations = await prefect_client.read_automations()
200197
automations_by_name = {automation.name: automation for automation in automations}
201198

202-
assert "webhook::Webhook1" in automations_by_name.keys()
203-
assert "webhook::Webhook2" in automations_by_name.keys()
199+
assert f"webhook::{webhook1.id}" in automations_by_name.keys()
200+
assert f"webhook::{webhook2.id}" in automations_by_name.keys()
204201

205-
automation = automations_by_name["webhook::Webhook2"]
202+
automation = automations_by_name[f"webhook::{webhook2.id}"]
206203
assert len(automation.actions) == 1
207204
action: RunDeployment = automation.actions[0] # type: ignore[assignment]
208205
assert action.parameters

backend/tests/unit/trigger/test_catalogue.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ def test_builtin_trigger_definition(trigger: TriggerDefinition) -> None:
2020
def test_builtin_triggers_sorted() -> None:
2121
names = sorted(name for name in dir(catalogue) if name.isupper())
2222
ordered_triggers = [getattr(catalogue, name) for name in names]
23-
assert ordered_triggers == builtin_triggers, "The list of workflows isn't sorted alphabetically"
23+
assert ordered_triggers == builtin_triggers, "The list of triggers isn't sorted alphabetically"

0 commit comments

Comments
 (0)