flyteplugins-jira 2.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ """Jira webhooks for Flyte.
2
+
3
+ Hand a `JiraProvider()` to a `WebhookAppEnvironment` and register handlers with the
4
+ typed constants in `events`. Calling the Jira API is not this plugin's job — use
5
+ the `jira` package from your tasks. See `examples/external_saas_integrations`.
6
+
7
+ Note Jira does not sign its webhooks; see `_provider` for what this plugin does
8
+ instead.
9
+ """
10
+
11
+ from . import events
12
+ from ._provider import JiraProvider, parse, verify
13
+
14
+ __all__ = ["SAMPLE_DELIVERY", "JiraProvider", "events", "parse", "verify"]
15
+
16
+
17
+ def _sample_headers(body: bytes, secret: str) -> dict[str, str]:
18
+ # No signature to compute: Jira sends a static shared token.
19
+ return {"X-Webhook-Token": secret}
20
+
21
+
22
+ #: A real `jira:issue_created` delivery, trimmed to the fields the parser reads.
23
+ SAMPLE_DELIVERY = (
24
+ _sample_headers,
25
+ (
26
+ b'{"webhookEvent": "jira:issue_created", "timestamp": 1700000000000,'
27
+ b' "user": {"displayName": "Bob"},'
28
+ b' "issue": {"key": "PROJ-1", "id": "10001",'
29
+ b' "fields": {"summary": "A bug", "project": {"key": "PROJ"}}}}'
30
+ ),
31
+ )
@@ -0,0 +1,83 @@
1
+ """Jira webhook verification and payload normalization.
2
+
3
+ Jira Cloud does **not** sign its webhooks. There is no HMAC to check, so this
4
+ plugin authenticates with a shared token in `X-Webhook-Token` — which something
5
+ in front of the app has to inject, because Jira itself cannot send custom
6
+ headers. `JiraProvider` reports `signed=False` so the dashboard says so plainly rather than
7
+ implying a guarantee that is not there.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import ClassVar, Mapping
13
+
14
+ from flyte.extras.webhooks import (
15
+ Provider,
16
+ WebhookEvent,
17
+ constant_time_equals,
18
+ json_body,
19
+ lower_headers,
20
+ )
21
+
22
+
23
+ def verify(body: bytes, headers: Mapping[str, str], secret: str) -> bool:
24
+ """Compare the `X-Webhook-Token` header against the shared token."""
25
+ token = lower_headers(headers).get("x-webhook-token")
26
+ if not token:
27
+ return False
28
+ return constant_time_equals(token.strip(), secret)
29
+
30
+
31
+ def parse(headers: Mapping[str, str], body: bytes) -> WebhookEvent:
32
+ """Normalize a Jira delivery into a `WebhookEvent`."""
33
+ payload = json_body(body)
34
+ issue = payload.get("issue") or {}
35
+ fields = issue.get("fields") or {}
36
+ user = payload.get("user") or {}
37
+ return WebhookEvent(
38
+ provider="jira",
39
+ event_type=payload.get("webhookEvent", "unknown"),
40
+ delivery_id=str(payload.get("timestamp") or ""),
41
+ resource_id=issue.get("key"),
42
+ occurred_at=str(payload.get("timestamp")) if payload.get("timestamp") is not None else None,
43
+ scope=(fields.get("project") or {}).get("key"),
44
+ title=fields.get("summary"),
45
+ actor=user.get("displayName") or user.get("name"),
46
+ payload=payload,
47
+ )
48
+
49
+
50
+ class JiraProvider(Provider):
51
+ """Jira's webhook provider, with its defaults pre-wired.
52
+
53
+ ```python
54
+ from flyte.extras.webhooks import WebhookAppEnvironment
55
+ from flyteplugins.jira import JiraProvider
56
+
57
+ app_env = WebhookAppEnvironment(name="webhooks", providers=[JiraProvider()])
58
+ ```
59
+
60
+ Jira does not sign its webhooks, so this provider authenticates with a
61
+ shared token instead and reports `signed=False` — which is what makes the
62
+ dashboard say so rather than implying a guarantee that is absent.
63
+
64
+ `WebhookAppEnvironment` mounts `default_secret_env` for you, so it does not
65
+ need naming again in `secrets=`.
66
+
67
+ Args:
68
+ secret_env: Environment variable holding the secret. Pass one only to
69
+ point this provider at a secret stored under a different name;
70
+ otherwise `default_secret_env` applies.
71
+ """
72
+
73
+ default_secret_env: ClassVar[str] = "JIRA_WEBHOOK_TOKEN"
74
+
75
+ def __init__(self, *, secret_env: str | None = None) -> None:
76
+ super().__init__(
77
+ name="jira",
78
+ secret_env=secret_env or self.default_secret_env,
79
+ verify=verify,
80
+ parse=parse,
81
+ signed=False,
82
+ setup_hint="Jira Settings -> System -> Webhooks (needs a proxy to inject X-Webhook-Token)",
83
+ )
@@ -0,0 +1,63 @@
1
+ """Jira webhook events, from the payload's `webhookEvent` field.
2
+
3
+ Some names take a `jira:` prefix and some do not — that inconsistency is Jira's.
4
+ These constants carry the exact wire values so you need not remember which.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from flyte.extras.webhooks import EventType
10
+
11
+ __all__ = ["Comment", "Issue", "Project", "Sprint", "Version", "Worklog"]
12
+
13
+
14
+ class Issue(EventType):
15
+ """Issue events. Note the `jira:` prefix, which comment events lack."""
16
+
17
+ CREATED = "jira:issue_created"
18
+ UPDATED = "jira:issue_updated"
19
+ DELETED = "jira:issue_deleted"
20
+
21
+
22
+ class Comment(EventType):
23
+ """Comment events. These carry no `jira:` prefix."""
24
+
25
+ CREATED = "comment_created"
26
+ UPDATED = "comment_updated"
27
+ DELETED = "comment_deleted"
28
+
29
+
30
+ class Worklog(EventType):
31
+ """Worklog events."""
32
+
33
+ CREATED = "worklog_created"
34
+ UPDATED = "worklog_updated"
35
+ DELETED = "worklog_deleted"
36
+
37
+
38
+ class Project(EventType):
39
+ """Project events."""
40
+
41
+ CREATED = "project_created"
42
+ UPDATED = "project_updated"
43
+ DELETED = "project_deleted"
44
+
45
+
46
+ class Version(EventType):
47
+ """Version (release) events."""
48
+
49
+ CREATED = "jira:version_created"
50
+ UPDATED = "jira:version_updated"
51
+ RELEASED = "jira:version_released"
52
+ UNRELEASED = "jira:version_unreleased"
53
+ DELETED = "jira:version_deleted"
54
+
55
+
56
+ class Sprint(EventType):
57
+ """Sprint events (Jira Software)."""
58
+
59
+ CREATED = "sprint_created"
60
+ UPDATED = "sprint_updated"
61
+ STARTED = "sprint_started"
62
+ CLOSED = "sprint_closed"
63
+ DELETED = "sprint_deleted"
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.4
2
+ Name: flyteplugins-jira
3
+ Version: 2.7.0
4
+ Summary: Receive Jira webhooks in Flyte.
5
+ Author: Flyte Contributors
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: flyte
9
+ Provides-Extra: app
10
+ Requires-Dist: fastapi>=0.115; extra == "app"
11
+ Requires-Dist: uvicorn>=0.30; extra == "app"
12
+
13
+ # flyteplugins-jira
14
+
15
+ Receive Jira webhooks in Flyte.
16
+
17
+ ```bash
18
+ pip install "flyteplugins-jira[app]"
19
+ ```
20
+
21
+ ## Using it
22
+
23
+ Hand a `JiraProvider()` to a `WebhookAppEnvironment` and register handlers with the
24
+ typed constants in `events`:
25
+
26
+ ```python
27
+ import flyte
28
+ from flyte.extras.webhooks import WebhookAppEnvironment, run_once
29
+ from flyteplugins.jira import JiraProvider, events
30
+
31
+ # JiraProvider.default_secret_env is mounted for you.
32
+ app_env = WebhookAppEnvironment(name="jira-webhooks", providers=[JiraProvider()])
33
+
34
+
35
+ @app_env.on_event(events.Issue.CREATED)
36
+ async def handle(event):
37
+ import flyte.remote as remote
38
+
39
+ task = remote.Task.get(name="my-env.my_task", auto_version="latest")
40
+ result = await run_once.aio(task, key=event.dedupe_key(), resource=event.resource_id)
41
+ if not result.created:
42
+ return {"skipped": result.run.name, "url": result.run.url}
43
+ return {"run": result.run.name}
44
+
45
+
46
+ flyte.serve(app_env)
47
+ ```
48
+
49
+ Handlers must `await run_once.aio(...)`. The blocking form stalls the
50
+ app's event loop, and Jira times deliveries out in seconds.
51
+
52
+ One app can serve several products at once — hand it one provider per product.
53
+
54
+ ## Try it
55
+
56
+ `examples/jira_webhooks.py` runs two ways. The first needs no Jira account:
57
+
58
+ ```bash
59
+ python examples/jira_webhooks.py --local # replay a real sample delivery in-process
60
+ python examples/jira_webhooks.py # deploy the receiver to Flyte
61
+ ```
62
+
63
+ `--local` posts this plugin's `SAMPLE_DELIVERY` through the app with FastAPI's
64
+ test client, so you see a delivery verified, normalized, and dispatched — plus
65
+ an unsigned one refused with a 401, and the same delivery replayed to show the
66
+ dedupe key is stable.
67
+
68
+ ## Setup
69
+
70
+ 1. Store the secret and mount it on the app:
71
+ ```bash
72
+ flyte create secret JIRA_WEBHOOK_TOKEN --value <secret>
73
+ ```
74
+ 2. Point Jira at `<app-url>/webhook/jira`, from
75
+ Jira Settings → System → Webhooks.
76
+
77
+ **Verification:** **None.** Jira Cloud does not sign its webhooks.
78
+
79
+ Because there is no signature, this plugin authenticates with a shared token in `X-Webhook-Token` — which something in front of the app has to inject, since Jira cannot send custom headers. `JiraProvider` reports `signed=False`, so the dashboard says the product does not sign rather than implying a guarantee that is absent. A shared token also cannot detect body tampering, only that the sender knew the token.
80
+
81
+ ## Event constants
82
+
83
+ `events` spells every event this plugin can dispatch, as `str` enums grouped by
84
+ event type, so a typo fails at import rather than by silently never matching.
85
+ Raw strings still work, for events the constants do not cover yet.
86
+
87
+ ## What this plugin does not do
88
+
89
+ Call the Jira API. Use the `jira` package directly from your tasks — see
90
+ `examples/external_saas_integrations`. This plugin owns only the part that is
91
+ Flyte's: authenticating an inbound delivery and turning it into a run.
@@ -0,0 +1,7 @@
1
+ flyteplugins/jira/__init__.py,sha256=1BB7HbHLPOFNSOK3GcOe3V2XOQ4GRaYTRj_-lkjMBXo,1077
2
+ flyteplugins/jira/_provider.py,sha256=3sLCzIZDrIP7Xfts1IKp7PwKVN7V3D3x-Ut5B_2TKmk,3002
3
+ flyteplugins/jira/events.py,sha256=t5pJMq_0tVahp71i7qLK-CjHVNAWqM5NGhBJGqSoBzo,1557
4
+ flyteplugins_jira-2.7.0.dist-info/METADATA,sha256=_SuYYTzQ34v2lG8gaUOXlx8cNF7ck1VmeYRhNGM6LwQ,3203
5
+ flyteplugins_jira-2.7.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
6
+ flyteplugins_jira-2.7.0.dist-info/top_level.txt,sha256=cgd779rPu9EsvdtuYgUxNHHgElaQvPn74KhB5XSeMBE,13
7
+ flyteplugins_jira-2.7.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ flyteplugins