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

eladkal 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 10df7436f3 Add `ChimeWebhookHook` (#31939)
10df7436f3 is described below

commit 10df7436f3bcdf8465b8cea536511d6126ae377c
Author: Cary <[email protected]>
AuthorDate: Mon Jun 26 22:41:14 2023 -0700

    Add `ChimeWebhookHook` (#31939)
    
    * Create ChimeWebhook Hook to send messages to chime room from Airflow.
    
    ---------
    
    Co-authored-by: Beata Kossakowska 
<[email protected]>
    Co-authored-by: Beata Kossakowska <[email protected]>
    Co-authored-by: eladkal <[email protected]>
    Co-authored-by: Jarek Potiuk <[email protected]>
---
 airflow/providers/amazon/aws/hooks/chime.py        | 116 +++++++++++++++++++++
 airflow/providers/amazon/provider.yaml             |  11 ++
 dev/breeze/tests/test_selective_checks.py          |  14 +--
 .../connections/chime.rst                          |  60 +++++++++++
 docs/apache-airflow-providers-amazon/index.rst     |   1 +
 .../aws/Amazon-Chime-light-bg.png                  | Bin 0 -> 9969 bytes
 generated/provider_dependencies.json               |   2 +
 tests/providers/amazon/aws/hooks/test_chime.py     | 105 +++++++++++++++++++
 8 files changed, 302 insertions(+), 7 deletions(-)

diff --git a/airflow/providers/amazon/aws/hooks/chime.py 
b/airflow/providers/amazon/aws/hooks/chime.py
new file mode 100644
index 0000000000..e2ac1a35a0
--- /dev/null
+++ b/airflow/providers/amazon/aws/hooks/chime.py
@@ -0,0 +1,116 @@
+#
+# 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.
+
+"""This module contains a web hook for Chime."""
+from __future__ import annotations
+
+import json
+import re
+from typing import Any
+
+from airflow.exceptions import AirflowException
+from airflow.providers.http.hooks.http import HttpHook
+
+
+class ChimeWebhookHook(HttpHook):
+    """Interact with Chime Web Hooks to create notifications.
+
+    .. warning:: This hook is only designed to work with web hooks and not 
chat bots.
+
+    :param chime_conn_id: Chime connection ID with Endpoint as 
"https://hooks.chime.aws"; and
+                         the webhook token in the form of 
```{webhook.id}?token{webhook.token}```
+
+    """
+
+    conn_name_attr = "chime_conn_id"
+    default_conn_name = "chime_default"
+    conn_type = "chime"
+    hook_name = "Chime Web Hook"
+
+    def __init__(
+        self,
+        chime_conn_id: str,
+        *args: Any,
+        **kwargs: Any,
+    ) -> None:
+        super().__init__(*args, **kwargs)
+        self.webhook_endpoint = self._get_webhook_endpoint(chime_conn_id)
+
+    def _get_webhook_endpoint(self, conn_id: str) -> str:
+        """
+        Given a Chime conn_id return the default webhook endpoint.
+
+        :param conn_id: The provided connection ID.
+        :return: Endpoint(str) for chime webhook.
+        """
+        conn = self.get_connection(conn_id)
+        token = conn.get_password()
+        if token is None:
+            raise AirflowException("Webhook token field is missing and is 
required.")
+        url = conn.schema + "://" + conn.host
+        endpoint = url + token
+        # Check to make sure the endpoint matches what Chime expects
+        if not re.match(r"^[a-zA-Z0-9_-]+\?token=[a-zA-Z0-9_-]+$", token):
+            raise AirflowException(
+                "Expected Chime webhook token in the form of 
'{webhook.id}?token={webhook.token}'."
+            )
+
+        return endpoint
+
+    def _build_chime_payload(self, message: str) -> str:
+        """
+        Builds payload for Chime and ensures messages do not exceed max length 
allowed.
+
+        :param message: The message you want to send to your Chime room.
+                    (max 4096 characters)
+        """
+        payload: dict[str, Any] = {}
+        # We need to make sure that the message does not exceed the max length 
for Chime
+        if len(message) > 4096:
+            raise AirflowException("Chime message must be 4096 characters or 
less.")
+
+        payload["Content"] = message
+        return json.dumps(payload)
+
+    def send_message(self, message: str) -> None:
+        """Execute calling the Chime webhook endpoint.
+
+        :param message: The message you want to send to your Chime room.
+                    (max 4096 characters)
+
+        """
+        chime_payload = self._build_chime_payload(message)
+        self.run(
+            endpoint=self.webhook_endpoint, data=chime_payload, 
headers={"Content-type": "application/json"}
+        )
+
+    @classmethod
+    def get_ui_field_behaviour(cls) -> dict[str, Any]:
+        """Returns custom field behaviour to only get what is needed for Chime 
webhooks to function."""
+        return {
+            "hidden_fields": ["login", "port", "extra"],
+            "relabeling": {
+                "host": "Chime Webhook Endpoint",
+                "password": "Webhook Token",
+            },
+            "placeholders": {
+                "schema": "https",
+                "host": "hooks.chime.aws/incomingwebhook/",
+                "password": "T00000000?token=XXXXXXXXXXXXXXXXXXXXXXXX",
+            },
+        }
diff --git a/airflow/providers/amazon/provider.yaml 
b/airflow/providers/amazon/provider.yaml
index 223bc553ef..5439f9c8cb 100644
--- a/airflow/providers/amazon/provider.yaml
+++ b/airflow/providers/amazon/provider.yaml
@@ -61,6 +61,7 @@ versions:
 dependencies:
   - apache-airflow>=2.4.0
   - apache-airflow-providers-common-sql>=1.3.1
+  - apache-airflow-providers-http
   - boto3>=1.24.0
   - asgiref
   # watchtower 3 has been released end Jan and introduced breaking change 
across the board that might
@@ -84,6 +85,10 @@ integrations:
     how-to-guide:
       - /docs/apache-airflow-providers-amazon/operators/athena.rst
     tags: [aws]
+  - integration-name: Amazon Chime
+    external-doc-url: https://aws.amazon.com/chime/
+    logo: /integration-logos/aws/Amazon-Chime-light-bg.png
+    tags: [aws]
   - integration-name: Amazon CloudFormation
     external-doc-url: https://aws.amazon.com/cloudformation/
     logo: /integration-logos/aws/[email protected]
@@ -407,6 +412,9 @@ hooks:
   - integration-name: Amazon Athena
     python-modules:
       - airflow.providers.amazon.aws.hooks.athena
+  - integration-name: Amazon Chime
+    python-modules:
+      - airflow.providers.amazon.aws.hooks.chime
   - integration-name: Amazon DynamoDB
     python-modules:
       - airflow.providers.amazon.aws.hooks.dynamodb
@@ -624,11 +632,14 @@ extra-links:
 connection-types:
   - hook-class-name: airflow.providers.amazon.aws.hooks.base_aws.AwsGenericHook
     connection-type: aws
+  - hook-class-name: airflow.providers.amazon.aws.hooks.chime.ChimeWebhookHook
+    connection-type: chime
   - hook-class-name: airflow.providers.amazon.aws.hooks.emr.EmrHook
     connection-type: emr
   - hook-class-name: 
airflow.providers.amazon.aws.hooks.redshift_sql.RedshiftSQLHook
     connection-type: redshift
 
+
 secrets-backends:
   - airflow.providers.amazon.aws.secrets.secrets_manager.SecretsManagerBackend
   - 
airflow.providers.amazon.aws.secrets.systems_manager.SystemsManagerParameterStoreBackend
diff --git a/dev/breeze/tests/test_selective_checks.py 
b/dev/breeze/tests/test_selective_checks.py
index dac17ce815..0081b6a2a7 100644
--- a/dev/breeze/tests/test_selective_checks.py
+++ b/dev/breeze/tests/test_selective_checks.py
@@ -191,7 +191,7 @@ def assert_outputs_are_printed(expected_outputs: dict[str, 
str], stderr: str):
                     "tests/providers/http/file.py",
                 ),
                 {
-                    "affected-providers-list-as-string": "airbyte apache.livy "
+                    "affected-providers-list-as-string": "airbyte amazon 
apache.livy "
                     "dbt.cloud dingding discord http",
                     "all-python-versions": "['3.8']",
                     "all-python-versions-list-as-string": "3.8",
@@ -200,11 +200,11 @@ def assert_outputs_are_printed(expected_outputs: 
dict[str, str], stderr: str):
                     "image-build": "true",
                     "needs-helm-tests": "true",
                     "run-tests": "true",
-                    "run-amazon-tests": "false",
+                    "run-amazon-tests": "true",
                     "docs-build": "true",
                     "run-kubernetes-tests": "true",
                     "upgrade-to-newer-dependencies": "false",
-                    "parallel-test-types-list-as-string": "Always "
+                    "parallel-test-types-list-as-string": "Providers[amazon] 
Always "
                     
"Providers[airbyte,apache.livy,dbt.cloud,dingding,discord,http]",
                 },
                 id="Helm tests, http and all relevant providers, kubernetes 
tests and "
@@ -311,7 +311,7 @@ def assert_outputs_are_printed(expected_outputs: dict[str, 
str], stderr: str):
             ("airflow/providers/amazon/__init__.py",),
             {
                 "affected-providers-list-as-string": "amazon apache.hive 
cncf.kubernetes "
-                "common.sql exasol ftp google imap "
+                "common.sql exasol ftp google http imap "
                 "mongo mysql postgres salesforce ssh",
                 "all-python-versions": "['3.8']",
                 "all-python-versions-list-as-string": "3.8",
@@ -325,7 +325,7 @@ def assert_outputs_are_printed(expected_outputs: dict[str, 
str], stderr: str):
                 "upgrade-to-newer-dependencies": "false",
                 "run-amazon-tests": "true",
                 "parallel-test-types-list-as-string": "Providers[amazon] 
Always "
-                
"Providers[apache.hive,cncf.kubernetes,common.sql,exasol,ftp,imap,"
+                
"Providers[apache.hive,cncf.kubernetes,common.sql,exasol,ftp,http,imap,"
                 "mongo,mysql,postgres,salesforce,ssh] Providers[google]",
             },
             id="Providers tests run including amazon tests if amazon provider 
files changed",
@@ -353,7 +353,7 @@ def assert_outputs_are_printed(expected_outputs: dict[str, 
str], stderr: str):
             ("airflow/providers/amazon/file.py",),
             {
                 "affected-providers-list-as-string": "amazon apache.hive 
cncf.kubernetes "
-                "common.sql exasol ftp google imap "
+                "common.sql exasol ftp google http imap "
                 "mongo mysql postgres salesforce ssh",
                 "all-python-versions": "['3.8']",
                 "all-python-versions-list-as-string": "3.8",
@@ -368,7 +368,7 @@ def assert_outputs_are_printed(expected_outputs: dict[str, 
str], stderr: str):
                 "upgrade-to-newer-dependencies": "false",
                 "parallel-test-types-list-as-string": "Providers[amazon] 
Always "
                 "Providers[apache.hive,cncf.kubernetes,common.sql,exasol,ftp,"
-                "imap,mongo,mysql,postgres,salesforce,ssh] Providers[google]",
+                "http,imap,mongo,mysql,postgres,salesforce,ssh] 
Providers[google]",
             },
             id="Providers tests run including amazon tests if amazon provider 
files changed",
         ),
diff --git a/docs/apache-airflow-providers-amazon/connections/chime.rst 
b/docs/apache-airflow-providers-amazon/connections/chime.rst
new file mode 100644
index 0000000000..94a356f81d
--- /dev/null
+++ b/docs/apache-airflow-providers-amazon/connections/chime.rst
@@ -0,0 +1,60 @@
+.. 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.
+
+.. _howto/connection:chime:
+
+Amazon Chime Connection
+==========================
+
+The Chime connection works with calling Chime webhooks to send messages to a 
chime room.
+
+Authenticating to Amazon Chime
+---------------------------------
+When a webhook is created in a Chime room a token will be included in the url 
for authentication.
+
+
+Default Connection IDs
+----------------------
+
+The default connection ID is ``chime_default``.
+
+Configuring the Connection
+--------------------------
+Chime Webhook Endpoint:
+    Specify the entire url or the base of the url for the service.
+
+
+Chime Webhook token:
+    The token for authentication including the webhook ID.
+
+Schema:
+    Whether or not the endpoint should be http or https
+
+
+Examples
+--------
+
+**Connection**
+
+* **Chime Webhook Endpoint**: hooks.chime.aws
+* **Chime Webhook Token**:
+
+.. code-block:: text
+
+    abceasd-3423-a1237-ffff-000cccccccc?token=somechimetoken
+
+* **Schema**: https
diff --git a/docs/apache-airflow-providers-amazon/index.rst 
b/docs/apache-airflow-providers-amazon/index.rst
index dff699d55a..e0b148425c 100644
--- a/docs/apache-airflow-providers-amazon/index.rst
+++ b/docs/apache-airflow-providers-amazon/index.rst
@@ -134,6 +134,7 @@ Dependent package
 `apache-airflow-providers-exasol 
<https://airflow.apache.org/docs/apache-airflow-providers-exasol>`_             
       ``exasol``
 `apache-airflow-providers-ftp 
<https://airflow.apache.org/docs/apache-airflow-providers-ftp>`_                
          ``ftp``
 `apache-airflow-providers-google 
<https://airflow.apache.org/docs/apache-airflow-providers-google>`_             
       ``google``
+`apache-airflow-providers-http 
<https://airflow.apache.org/docs/apache-airflow-providers-http>`_               
         ``http``
 `apache-airflow-providers-imap 
<https://airflow.apache.org/docs/apache-airflow-providers-imap>`_               
         ``imap``
 `apache-airflow-providers-mongo 
<https://airflow.apache.org/docs/apache-airflow-providers-mongo>`_              
        ``mongo``
 `apache-airflow-providers-salesforce 
<https://airflow.apache.org/docs/apache-airflow-providers-salesforce>`_         
   ``salesforce``
diff --git a/docs/integration-logos/aws/Amazon-Chime-light-bg.png 
b/docs/integration-logos/aws/Amazon-Chime-light-bg.png
new file mode 100644
index 0000000000..bd412e9815
Binary files /dev/null and 
b/docs/integration-logos/aws/Amazon-Chime-light-bg.png differ
diff --git a/generated/provider_dependencies.json 
b/generated/provider_dependencies.json
index 2a1ae40e3f..2755e9f08b 100644
--- a/generated/provider_dependencies.json
+++ b/generated/provider_dependencies.json
@@ -20,6 +20,7 @@
   "amazon": {
     "deps": [
       "apache-airflow-providers-common-sql>=1.3.1",
+      "apache-airflow-providers-http",
       "apache-airflow>=2.4.0",
       "asgiref",
       "asgiref",
@@ -40,6 +41,7 @@
       "exasol",
       "ftp",
       "google",
+      "http",
       "imap",
       "mongo",
       "salesforce",
diff --git a/tests/providers/amazon/aws/hooks/test_chime.py 
b/tests/providers/amazon/aws/hooks/test_chime.py
new file mode 100644
index 0000000000..d5130964f4
--- /dev/null
+++ b/tests/providers/amazon/aws/hooks/test_chime.py
@@ -0,0 +1,105 @@
+#
+# 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.
+from __future__ import annotations
+
+import json
+
+import pytest
+
+from airflow.exceptions import AirflowException
+from airflow.models import Connection
+from airflow.providers.amazon.aws.hooks.chime import ChimeWebhookHook
+from airflow.utils import db
+
+
+class TestChimeWebhookHook:
+
+    _config = {
+        "chime_conn_id": "default-chime-webhook",
+        "webhook_endpoint": 
"incomingwebhooks/abcd-1134-ZeDA?token=somechimetoken-111",
+        "message": "your message here",
+    }
+
+    expected_payload_dict = {
+        "Content": _config["message"],
+    }
+
+    expected_payload = json.dumps(expected_payload_dict)
+
+    def setup_method(self):
+        db.merge_conn(
+            Connection(
+                conn_id="default-chime-webhook",
+                conn_type="chime",
+                host="hooks.chime.aws/incomingwebhooks/",
+                password="abcd-1134-ZeDA?token=somechimetoken111",
+                schema="https",
+            )
+        )
+        db.merge_conn(
+            Connection(
+                conn_id="chime-bad-url",
+                conn_type="chime",
+                host="https://hooks.chime.aws/";,
+                password="somebadurl",
+                schema="https",
+            )
+        )
+
+    def test_get_webhook_endpoint_invalid_url(self):
+        # Given
+
+        # When/Then
+        expected_message = r"Expected Chime webhook token in the form"
+        with pytest.raises(AirflowException, match=expected_message):
+            ChimeWebhookHook(chime_conn_id="chime-bad-url")
+
+    def test_get_webhook_endpoint_conn_id(self):
+        # Given
+        conn_id = "default-chime-webhook"
+        hook = ChimeWebhookHook(chime_conn_id=conn_id)
+        expected_webhook_endpoint = (
+            
"https://hooks.chime.aws/incomingwebhooks/abcd-1134-ZeDA?token=somechimetoken111";
+        )
+
+        # When
+        webhook_endpoint = hook._get_webhook_endpoint(conn_id)
+
+        # Then
+        assert webhook_endpoint == expected_webhook_endpoint
+
+    def test_build_chime_payload(self):
+        # Given
+        hook = ChimeWebhookHook(self._config["chime_conn_id"])
+        message = self._config["message"]
+        # When
+        payload = hook._build_chime_payload(message)
+        # Then
+        assert self.expected_payload == payload
+
+    def test_build_chime_payload_message_length(self):
+        # Given
+        self._config.copy()
+        # create message over the character limit
+        message = "c" * 4097
+        hook = ChimeWebhookHook(self._config["chime_conn_id"])
+
+        # When/Then
+        expected_message = "Chime message must be 4096 characters or less."
+        with pytest.raises(AirflowException, match=expected_message):
+            hook._build_chime_payload(message)

Reply via email to