This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 923f5a5  Make a separate hook for interacting with the Pagerduty 
Events API (#18784)
923f5a5 is described below

commit 923f5a5912785649be7e61c8ea32a0bd6dc426d8
Author: Guido Tournois <[email protected]>
AuthorDate: Tue Oct 12 20:00:59 2021 +0200

    Make a separate hook for interacting with the Pagerduty Events API (#18784)
---
 airflow/providers/pagerduty/hooks/pagerduty.py     |  89 +++++++++--------
 .../hooks/{pagerduty.py => pagerduty_events.py}    |  80 +++++++--------
 airflow/providers/pagerduty/provider.yaml          |   3 +
 tests/providers/pagerduty/hooks/test_pagerduty.py  | 107 ++++++++++++---------
 .../pagerduty/hooks/test_pagerduty_events.py       |  54 +++++++++++
 5 files changed, 198 insertions(+), 135 deletions(-)

diff --git a/airflow/providers/pagerduty/hooks/pagerduty.py 
b/airflow/providers/pagerduty/hooks/pagerduty.py
index a908ddd..17e1edb 100644
--- a/airflow/providers/pagerduty/hooks/pagerduty.py
+++ b/airflow/providers/pagerduty/hooks/pagerduty.py
@@ -16,19 +16,28 @@
 # specific language governing permissions and limitations
 # under the License.
 """Hook for sending or receiving data from PagerDuty as well as creating 
PagerDuty incidents."""
+import warnings
 from typing import Any, Dict, List, Optional
 
 import pdpyras
 
 from airflow.exceptions import AirflowException
 from airflow.hooks.base import BaseHook
+from airflow.providers.pagerduty.hooks.pagerduty_events import 
PagerdutyEventsHook
 
 
 class PagerdutyHook(BaseHook):
     """
-    Takes both PagerDuty API token directly and connection that has PagerDuty 
API token.
+    The PagerdutyHook can be used to interact with both the PagerDuty API and 
the PagerDuty Events API.
 
+    Takes both PagerDuty API token directly and connection that has PagerDuty 
API token.
     If both supplied, PagerDuty API token will be used.
+    In these cases, the PagerDuty API token refers to an account token:
+    
https://support.pagerduty.com/docs/generating-api-keys#generating-a-general-access-rest-api-key
+    
https://support.pagerduty.com/docs/generating-api-keys#generating-a-personal-rest-api-key
+
+    In order to send events (with the Pagerduty Events API), you will also 
need to specify the
+    routing_key (or Integration key) in the ``extra`` field
 
     :param token: PagerDuty API token
     :param pagerduty_conn_id: connection that has PagerDuty API token in the 
password field
@@ -39,6 +48,16 @@ class PagerdutyHook(BaseHook):
     conn_type = "pagerduty"
     hook_name = "Pagerduty"
 
+    @staticmethod
+    def get_ui_field_behaviour() -> Dict:
+        """Returns custom field behaviour"""
+        return {
+            "hidden_fields": ['port', 'login', 'schema', 'host'],
+            "relabeling": {
+                'password': 'Pagerduty API token',
+            },
+        }
+
     def __init__(self, token: Optional[str] = None, pagerduty_conn_id: 
Optional[str] = None) -> None:
         super().__init__()
         self.routing_key = None
@@ -75,8 +94,8 @@ class PagerdutyHook(BaseHook):
         self,
         summary: str,
         severity: str,
-        source: str = 'airflow',
-        action: str = 'trigger',
+        source: str = "airflow",
+        action: str = "trigger",
         routing_key: Optional[str] = None,
         dedup_key: Optional[str] = None,
         custom_details: Optional[Any] = None,
@@ -129,45 +148,25 @@ class PagerdutyHook(BaseHook):
         :return: PagerDuty Events API v2 response.
         :rtype: dict
         """
-        if routing_key is None:
-            routing_key = self.routing_key
-        if routing_key is None:
-            raise AirflowException('No routing/integration key specified.')
-        payload = {
-            "summary": summary,
-            "severity": severity,
-            "source": source,
-        }
-        if custom_details is not None:
-            payload["custom_details"] = custom_details
-        if component:
-            payload["component"] = component
-        if group:
-            payload["group"] = group
-        if class_type:
-            payload["class"] = class_type
-
-        actions = ('trigger', 'acknowledge', 'resolve')
-        if action not in actions:
-            raise ValueError(f"Event action must be one of: {', 
'.join(actions)}")
-        data = {
-            "event_action": action,
-            "payload": payload,
-        }
-        if dedup_key:
-            data["dedup_key"] = dedup_key
-        elif action != 'trigger':
-            raise ValueError(
-                "The dedup_key property is required for event_action=%s 
events, and it must \
-                be a string."
-                % action
-            )
-        if images is not None:
-            data["images"] = images
-        if links is not None:
-            data["links"] = links
-
-        session = pdpyras.EventsAPISession(routing_key)
-        resp = session.post('/v2/enqueue', json=data)
-        resp.raise_for_status()
-        return resp.json()
+        warnings.warn(
+            "This method will be deprecated. Please use the "
+            "`airflow.providers.pagerduty.hooks.PagerdutyEventsHook` to 
interact with the Events API",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        routing_key = routing_key or self.routing_key
+
+        return PagerdutyEventsHook(integration_key=routing_key).create_event(
+            summary=summary,
+            severity=severity,
+            source=source,
+            action=action,
+            dedup_key=dedup_key,
+            custom_details=custom_details,
+            group=group,
+            component=component,
+            class_type=class_type,
+            images=images,
+            links=links,
+        )
diff --git a/airflow/providers/pagerduty/hooks/pagerduty.py 
b/airflow/providers/pagerduty/hooks/pagerduty_events.py
similarity index 69%
copy from airflow/providers/pagerduty/hooks/pagerduty.py
copy to airflow/providers/pagerduty/hooks/pagerduty_events.py
index a908ddd..f133df2 100644
--- a/airflow/providers/pagerduty/hooks/pagerduty.py
+++ b/airflow/providers/pagerduty/hooks/pagerduty_events.py
@@ -24,52 +24,50 @@ from airflow.exceptions import AirflowException
 from airflow.hooks.base import BaseHook
 
 
-class PagerdutyHook(BaseHook):
+class PagerdutyEventsHook(BaseHook):
     """
-    Takes both PagerDuty API token directly and connection that has PagerDuty 
API token.
+    This class can be used to interact with the Pagerduty Events API.
 
-    If both supplied, PagerDuty API token will be used.
+    It takes both an Events API token and a PagerDuty connection with the 
Events API token
+     (i.e. Integration key) as the password/Pagerduty API token. If both 
supplied, the token will be used.
 
-    :param token: PagerDuty API token
-    :param pagerduty_conn_id: connection that has PagerDuty API token in the 
password field
+    :param integration_key: PagerDuty Events API token
+    :param pagerduty_conn_id: connection that has PagerDuty integration key in 
the Pagerduty API token field
     """
 
-    conn_name_attr = "pagerduty_conn_id"
-    default_conn_name = "pagerduty_default"
-    conn_type = "pagerduty"
-    hook_name = "Pagerduty"
+    conn_name_attr = "pagerduty_events_conn_id"
+    default_conn_name = "pagerduty_events_default"
+    conn_type = "pagerduty_events"
+    hook_name = "Pagerduty Events"
+
+    @staticmethod
+    def get_ui_field_behaviour() -> Dict:
+        """Returns custom field behaviour"""
+        return {
+            "hidden_fields": ['port', 'login', 'schema', 'host', 'extra'],
+            "relabeling": {
+                'password': 'Pagerduty Integration key',
+            },
+        }
 
-    def __init__(self, token: Optional[str] = None, pagerduty_conn_id: 
Optional[str] = None) -> None:
+    def __init__(
+        self, integration_key: Optional[str] = None, pagerduty_events_conn_id: 
Optional[str] = None
+    ) -> None:
         super().__init__()
-        self.routing_key = None
+        self.integration_key = None
         self._session = None
 
-        if pagerduty_conn_id is not None:
-            conn = self.get_connection(pagerduty_conn_id)
-            self.token = conn.get_password()
-
-            routing_key = conn.extra_dejson.get("routing_key")
-            if routing_key:
-                self.routing_key = routing_key
-
-        if token is not None:  # token takes higher priority
-            self.token = token
-
-        if self.token is None:
-            raise AirflowException('Cannot get token: No valid api token nor 
pagerduty_conn_id supplied.')
+        if pagerduty_events_conn_id is not None:
+            conn = self.get_connection(pagerduty_events_conn_id)
+            self.integration_key = conn.get_password()
 
-    def get_session(self) -> pdpyras.APISession:
-        """
-        Returns `pdpyras.APISession` for use with sending or receiving data 
through the PagerDuty REST API.
-
-        The `pdpyras` library supplies a class `pdpyras.APISession` extending 
`requests.Session` from the
-        Requests HTTP library.
+        if integration_key is not None:  # token takes higher priority
+            self.integration_key = integration_key
 
-        Documentation on how to use the `APISession` class can be found at:
-        https://pagerduty.github.io/pdpyras/#data-access-abstraction
-        """
-        self._session = pdpyras.APISession(self.token)
-        return self._session
+        if self.integration_key is None:
+            raise AirflowException(
+                'Cannot get token: No valid integration key nor 
pagerduty_events_conn_id supplied.'
+            )
 
     def create_event(
         self,
@@ -77,7 +75,6 @@ class PagerdutyHook(BaseHook):
         severity: str,
         source: str = 'airflow',
         action: str = 'trigger',
-        routing_key: Optional[str] = None,
         dedup_key: Optional[str] = None,
         custom_details: Optional[Any] = None,
         group: Optional[str] = None,
@@ -99,9 +96,6 @@ class PagerdutyHook(BaseHook):
         :param action: Event action, needs to be one of: trigger, acknowledge,
             resolve. Default to trigger if not specified.
         :type action: str
-        :param routing_key: Integration key. If not specified, will try to read
-            from connection's extra json blob.
-        :type routing_key: str
         :param dedup_key: A string which identifies the alert triggered for 
the given event.
             Required for the actions acknowledge and resolve.
         :type dedup_key: str
@@ -129,10 +123,6 @@ class PagerdutyHook(BaseHook):
         :return: PagerDuty Events API v2 response.
         :rtype: dict
         """
-        if routing_key is None:
-            routing_key = self.routing_key
-        if routing_key is None:
-            raise AirflowException('No routing/integration key specified.')
         payload = {
             "summary": summary,
             "severity": severity,
@@ -167,7 +157,7 @@ class PagerdutyHook(BaseHook):
         if links is not None:
             data["links"] = links
 
-        session = pdpyras.EventsAPISession(routing_key)
-        resp = session.post('/v2/enqueue', json=data)
+        session = pdpyras.EventsAPISession(self.integration_key)
+        resp = session.post("/v2/enqueue", json=data)
         resp.raise_for_status()
         return resp.json()
diff --git a/airflow/providers/pagerduty/provider.yaml 
b/airflow/providers/pagerduty/provider.yaml
index af1aaba..dae2a1c 100644
--- a/airflow/providers/pagerduty/provider.yaml
+++ b/airflow/providers/pagerduty/provider.yaml
@@ -39,8 +39,11 @@ integrations:
 connection-types:
   - hook-class-name: airflow.providers.pagerduty.hooks.pagerduty.PagerdutyHook
     connection-type: pagerduty
+  - hook-class-name: 
airflow.providers.pagerduty.hooks.pagerduty_events.PagerdutyEventsHook
+    connection-type: pagerduty_events
 
 hooks:
   - integration-name: Pagerduty
     python-modules:
       - airflow.providers.pagerduty.hooks.pagerduty
+      - airflow.providers.pagerduty.hooks.pagerduty_events
diff --git a/tests/providers/pagerduty/hooks/test_pagerduty.py 
b/tests/providers/pagerduty/hooks/test_pagerduty.py
index 1c1348d..a2b1b1c 100644
--- a/tests/providers/pagerduty/hooks/test_pagerduty.py
+++ b/tests/providers/pagerduty/hooks/test_pagerduty.py
@@ -15,55 +15,51 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-import unittest
+from unittest import mock
 
-import requests_mock
+import pytest
 
 from airflow.models import Connection
 from airflow.providers.pagerduty.hooks.pagerduty import PagerdutyHook
-from airflow.utils.session import provide_session
+from airflow.providers.pagerduty.hooks.pagerduty_events import 
PagerdutyEventsHook
+from airflow.utils import db
 
 DEFAULT_CONN_ID = "pagerduty_default"
 
 
-class TestPagerdutyHook(unittest.TestCase):
-    @classmethod
-    @provide_session
-    def setUpClass(cls, session=None):
-        session.add(
-            Connection(
-                conn_id=DEFAULT_CONN_ID,
-                conn_type='http',
-                password="pagerduty_token",
-                extra='{"routing_key": "route"}',
-            )
[email protected](scope="class")
+def pagerduty_connections():
+    db.merge_conn(
+        Connection(
+            conn_id=DEFAULT_CONN_ID,
+            conn_type="pagerduty",
+            password="token",
+            extra='{"routing_key": "integration_key"}',
         )
-        session.commit()
+    )
+    db.merge_conn(
+        Connection(
+            conn_id="pagerduty_no_extra", conn_type="pagerduty", 
password="pagerduty_token_without_extra"
+        ),
+    )
 
-    @provide_session
-    def test_without_routing_key_extra(self, session):
-        session.add(
-            Connection(
-                conn_id="pagerduty_no_extra",
-                conn_type='http',
-                password="pagerduty_token_without_extra",
-            )
-        )
-        session.commit()
-        hook = PagerdutyHook(pagerduty_conn_id="pagerduty_no_extra")
-        assert hook.token == 'pagerduty_token_without_extra', 'token 
initialised.'
-        assert hook.routing_key is None, 'default routing key skipped.'
 
-    def test_get_token_from_password(self):
+class TestPagerdutyHook:
+    def test_get_token_from_password(self, pagerduty_connections):
         hook = PagerdutyHook(pagerduty_conn_id=DEFAULT_CONN_ID)
-        assert hook.token == 'pagerduty_token', 'token initialised.'
+        assert hook.token == "token", "token initialised."
+        assert hook.routing_key == "integration_key"
+
+    def test_without_routing_key_extra(self):
+        hook = PagerdutyHook(pagerduty_conn_id="pagerduty_no_extra")
+        assert hook.token == "pagerduty_token_without_extra", "token 
initialised."
+        assert hook.routing_key is None, "default routing key skipped."
 
     def test_token_parameter_override(self):
         hook = PagerdutyHook(token="pagerduty_param_token", 
pagerduty_conn_id=DEFAULT_CONN_ID)
-        assert hook.token == 'pagerduty_param_token', 'token initialised.'
+        assert hook.token == "pagerduty_param_token", "token initialised."
 
-    @requests_mock.mock()
-    def test_get_service(self, m):
+    def test_get_service(self, requests_mock):
         hook = PagerdutyHook(pagerduty_conn_id=DEFAULT_CONN_ID)
         mock_response_body = {
             "id": "PZYX321",
@@ -73,24 +69,45 @@ class TestPagerdutyHook(unittest.TestCase):
             "summary": "Apache Airflow",
             "self": "https://api.pagerduty.com/services/PZYX321";,
         }
-        m.get('https://api.pagerduty.com/services/PZYX321', json={"service": 
mock_response_body})
+        requests_mock.get("https://api.pagerduty.com/services/PZYX321";, 
json={"service": mock_response_body})
         session = hook.get_session()
-        resp = session.rget('/services/PZYX321')
+        resp = session.rget("/services/PZYX321")
         assert resp == mock_response_body
 
-    @requests_mock.mock()
-    def test_create_event(self, m):
+    @mock.patch.object(PagerdutyEventsHook, "__init__")
+    @mock.patch.object(PagerdutyEventsHook, "create_event")
+    def test_create_event(self, events_hook_create_event, events_hook_init):
+        events_hook_init.return_value = None
         hook = PagerdutyHook(pagerduty_conn_id=DEFAULT_CONN_ID)
-        mock_response_body = {
-            "status": "success",
-            "message": "Event processed",
-            "dedup_key": "samplekeyhere",
-        }
-        m.post('https://events.pagerduty.com/v2/enqueue', 
json=mock_response_body)
-        resp = hook.create_event(
+        hook.create_event(
+            summary="test",
+            source="airflow_test",
+            severity="error",
+        )
+        events_hook_init.assert_called_with(integration_key="integration_key")
+        events_hook_create_event.assert_called_with(
+            summary="test",
+            source="airflow_test",
+            severity="error",
+            action="trigger",
+            dedup_key=None,
+            custom_details=None,
+            group=None,
+            component=None,
+            class_type=None,
+            images=None,
+            links=None,
+        )
+
+    @mock.patch.object(PagerdutyEventsHook, "create_event", 
mock.MagicMock(return_value=None))
+    @mock.patch.object(PagerdutyEventsHook, "__init__")
+    def test_create_event_override(self, events_hook_init):
+        events_hook_init.return_value = None
+        hook = PagerdutyHook(pagerduty_conn_id=DEFAULT_CONN_ID)
+        hook.create_event(
             routing_key="different_key",
             summary="test",
             source="airflow_test",
             severity="error",
         )
-        assert resp == mock_response_body
+        events_hook_init.assert_called_with(integration_key="different_key")
diff --git a/tests/providers/pagerduty/hooks/test_pagerduty_events.py 
b/tests/providers/pagerduty/hooks/test_pagerduty_events.py
new file mode 100644
index 0000000..dc1f4f1
--- /dev/null
+++ b/tests/providers/pagerduty/hooks/test_pagerduty_events.py
@@ -0,0 +1,54 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+import pytest
+
+from airflow.models import Connection
+from airflow.providers.pagerduty.hooks.pagerduty import PagerdutyEventsHook
+from airflow.utils import db
+
+DEFAULT_CONN_ID = "pagerduty_events_default"
+
+
[email protected](scope="class")
+def events_connections():
+    db.merge_conn(Connection(conn_id=DEFAULT_CONN_ID, 
conn_type="pagerduty_events", password="events_token"))
+
+
+class TestPagerdutyEventsHook:
+    def test_get_integration_key_from_password(self, events_connections):
+        hook = PagerdutyEventsHook(pagerduty_events_conn_id=DEFAULT_CONN_ID)
+        assert hook.integration_key == "events_token", "token initialised."
+
+    def test_token_parameter_override(self, events_connections):
+        hook = PagerdutyEventsHook(integration_key="override_key", 
pagerduty_events_conn_id=DEFAULT_CONN_ID)
+        assert hook.integration_key == "override_key", "token initialised."
+
+    def test_create_event(self, requests_mock, events_connections):
+        hook = PagerdutyEventsHook(pagerduty_events_conn_id=DEFAULT_CONN_ID)
+        mock_response_body = {
+            "status": "success",
+            "message": "Event processed",
+            "dedup_key": "samplekeyhere",
+        }
+        requests_mock.post("https://events.pagerduty.com/v2/enqueue";, 
json=mock_response_body)
+        resp = hook.create_event(
+            summary="test",
+            source="airflow_test",
+            severity="error",
+        )
+        assert resp == mock_response_body

Reply via email to