You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by po...@apache.org on 2023/09/01 13:29:22 UTC
[airflow] branch main updated: Add Slack Incoming Webhook Notifier (#33966)
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 e357f7b531 Add Slack Incoming Webhook Notifier (#33966)
e357f7b531 is described below
commit e357f7b531e50981faa1199595c0e92a23a714b5
Author: Andrey Anshin <An...@taragol.is>
AuthorDate: Fri Sep 1 17:29:13 2023 +0400
Add Slack Incoming Webhook Notifier (#33966)
---
.../providers/slack/notifications/slack_webhook.py | 95 ++++++++++++++++++++++
airflow/providers/slack/provider.yaml | 1 +
docs/apache-airflow-providers-slack/index.rst | 2 +-
.../notifications/index.rst | 30 +++++++
.../slackwebhook_notifier_howto_guide.rst | 59 ++++++++++++++
.../slack/notifications/test_slack_webhook.py | 69 ++++++++++++++++
6 files changed, 255 insertions(+), 1 deletion(-)
diff --git a/airflow/providers/slack/notifications/slack_webhook.py b/airflow/providers/slack/notifications/slack_webhook.py
new file mode 100644
index 0000000000..67616e5a51
--- /dev/null
+++ b/airflow/providers/slack/notifications/slack_webhook.py
@@ -0,0 +1,95 @@
+# 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
+
+from functools import cached_property
+
+from airflow.exceptions import AirflowOptionalProviderFeatureException
+from airflow.providers.slack.hooks.slack_webhook import SlackWebhookHook
+
+try:
+ from airflow.notifications.basenotifier import BaseNotifier
+except ImportError:
+ raise AirflowOptionalProviderFeatureException(
+ "Failed to import BaseNotifier. This feature is only available in Airflow versions >= 2.6.0"
+ )
+
+
+class SlackWebhookNotifier(BaseNotifier):
+ """
+ Slack Incoming Webhooks Notifier.
+
+ .. note::
+ ``SlackWebhookNotifier`` provide integration with Slack Incoming Webhooks,
+ and may not function accurately within Legacy Slack Integration Incoming Webhook.
+
+ :param slack_webhook_conn_id: :ref:`Slack Incoming Webhook <howto/connection:slack>`
+ connection id that has Incoming Webhook token in the password field.
+ :param text: The content of the message
+ :param blocks: A list of blocks to send with the message. Optional
+ :param unfurl_links: Option to indicate whether text url should unfurl. Optional
+ :param unfurl_media: Option to indicate whether media url should unfurl. Optional
+ :param timeout: The maximum number of seconds the client will wait to connect. Optional
+ and receive a response from Slack. If not set than default WebhookClient value will use.
+ :param proxy: Proxy to make the Slack Incoming Webhook call. Optional
+ :param attachments: A list of attachments to send with the message. Optional
+ """
+
+ template_fields = ("slack_webhook_conn_id", "text", "attachments", "blocks", "proxy", "timeout")
+
+ def __init__(
+ self,
+ *,
+ slack_webhook_conn_id: str = SlackWebhookHook.default_conn_name,
+ text: str,
+ blocks: list | None = None,
+ unfurl_links: bool | None = None,
+ unfurl_media: bool | None = None,
+ proxy: str | None = None,
+ timeout: int | None = None,
+ attachments: list | None = None,
+ ):
+ super().__init__()
+ self.slack_webhook_conn_id = slack_webhook_conn_id
+ self.text = text
+ self.attachments = attachments
+ self.blocks = blocks
+ self.unfurl_links = unfurl_links
+ self.unfurl_media = unfurl_media
+ self.timeout = timeout
+ self.proxy = proxy
+
+ @cached_property
+ def hook(self) -> SlackWebhookHook:
+ """Slack Incoming Webhook Hook."""
+ return SlackWebhookHook(
+ slack_webhook_conn_id=self.slack_webhook_conn_id, proxy=self.proxy, timeout=self.timeout
+ )
+
+ def notify(self, context):
+ """Send a message to a Slack Incoming Webhook."""
+ self.hook.send(
+ text=self.text,
+ blocks=self.blocks,
+ unfurl_links=self.unfurl_links,
+ unfurl_media=self.unfurl_media,
+ attachments=self.attachments,
+ )
+
+
+send_slack_webhook_notification = SlackWebhookNotifier
diff --git a/airflow/providers/slack/provider.yaml b/airflow/providers/slack/provider.yaml
index e751b2e0f6..bd8e4e06b6 100644
--- a/airflow/providers/slack/provider.yaml
+++ b/airflow/providers/slack/provider.yaml
@@ -84,3 +84,4 @@ connection-types:
notifications:
- airflow.providers.slack.notifications.slack.SlackNotifier
+ - airflow.providers.slack.notifications.slack_webhook.SlackWebhookNotifier
diff --git a/docs/apache-airflow-providers-slack/index.rst b/docs/apache-airflow-providers-slack/index.rst
index dfc4fc3b37..c7cc055a55 100644
--- a/docs/apache-airflow-providers-slack/index.rst
+++ b/docs/apache-airflow-providers-slack/index.rst
@@ -43,7 +43,7 @@
Connection Types <connections/index>
Operators <operators/index>
- Slack Notifications <notifications/slack_notifier_howto_guide>
+ Slack Notifications <notifications/index>
.. toctree::
:hidden:
diff --git a/docs/apache-airflow-providers-slack/notifications/index.rst b/docs/apache-airflow-providers-slack/notifications/index.rst
new file mode 100644
index 0000000000..f1e0dd6a5f
--- /dev/null
+++ b/docs/apache-airflow-providers-slack/notifications/index.rst
@@ -0,0 +1,30 @@
+ .. 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.
+
+
+
+Slack Notifications
+===================
+
+.. important:: This feature is only available in Airflow versions >= 2.6.0
+
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ *
diff --git a/docs/apache-airflow-providers-slack/notifications/slackwebhook_notifier_howto_guide.rst b/docs/apache-airflow-providers-slack/notifications/slackwebhook_notifier_howto_guide.rst
new file mode 100644
index 0000000000..800db06d00
--- /dev/null
+++ b/docs/apache-airflow-providers-slack/notifications/slackwebhook_notifier_howto_guide.rst
@@ -0,0 +1,59 @@
+ .. 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.
+
+How-to Guide for Slack Incoming Webhook notifications
+=====================================================
+
+Introduction
+------------
+Slack Incoming Webhook notifier (:class:`airflow.providers.slack.notifications.slack_webhook.SlackWebhookNotifier`)
+allows users to send messages to a slack channel thought `Incoming Webhook <https://api.slack.com/messaging/webhooks>`__
+using the various ``on_*_callbacks`` at both the DAG level and Task level
+
+You can also use a notifier with ``sla_miss_callback``.
+
+.. note::
+ When notifiers are used with `sla_miss_callback` the context will contain only values passed to the callback, refer :ref:`sla_miss_callback<concepts:sla_miss_callback>`.
+
+Example Code:
+-------------
+
+.. code-block:: python
+
+ from datetime import datetime, timezone
+ from airflow import DAG
+ from airflow.operators.bash import BashOperator
+ from airflow.providers.slack.notifications.slack_webhook import send_slack_webhook_notification
+
+ dag_failure_slack_webhook_notification = send_slack_webhook_notification(
+ slack_webhook_conn_id="slackwebhook", text="The dag {{ dag.dag_id }} failed"
+ )
+ task_failure_slack_webhook_notification = send_slack_webhook_notification(
+ slack_webhook_conn_id="slackwebhook",
+ text="The task {{ ti.task_id }} failed",
+ )
+
+ with DAG(
+ dag_id="mydag",
+ schedule="@once",
+ start_date=datetime(2023, 1, 1, tzinfo=timezone.utc),
+ on_failure_callback=[dag_failure_slack_webhook_notification],
+ catchup=False,
+ ):
+ BashOperator(
+ task_id="mytask", on_failure_callback=[task_failure_slack_webhook_notification], bash_command="fail"
+ )
diff --git a/tests/providers/slack/notifications/test_slack_webhook.py b/tests/providers/slack/notifications/test_slack_webhook.py
new file mode 100644
index 0000000000..e595766bfc
--- /dev/null
+++ b/tests/providers/slack/notifications/test_slack_webhook.py
@@ -0,0 +1,69 @@
+# 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
+
+from unittest import mock
+
+from airflow.operators.empty import EmptyOperator
+from airflow.providers.slack.notifications.slack_webhook import (
+ SlackWebhookNotifier,
+ send_slack_webhook_notification,
+)
+
+
+class TestSlackNotifier:
+ def test_class_and_notifier_are_same(self):
+ assert send_slack_webhook_notification is SlackWebhookNotifier
+
+ @mock.patch("airflow.providers.slack.notifications.slack_webhook.SlackWebhookHook")
+ def test_slack_webhook_notifier(self, mock_slack_hook):
+ notifier = send_slack_webhook_notification(
+ text="foo-bar", blocks="spam-egg", attachments="baz-qux", unfurl_links=True, unfurl_media=False
+ )
+ notifier.notify({})
+ mock_slack_hook.return_value.send.assert_called_once_with(
+ text="foo-bar",
+ blocks="spam-egg",
+ unfurl_links=True,
+ unfurl_media=False,
+ attachments="baz-qux",
+ )
+
+ @mock.patch("airflow.providers.slack.notifications.slack_webhook.SlackWebhookHook")
+ def test_slack_webhook_templated(self, mock_slack_hook, dag_maker):
+ with dag_maker("test_send_slack_webhook_notification_templated") as dag:
+ EmptyOperator(task_id="task1")
+
+ notifier = send_slack_webhook_notification(
+ text="Who am I? {{ username }}",
+ blocks=[{"type": "header", "text": {"type": "plain_text", "text": "{{ dag.dag_id }}"}}],
+ attachments=[{"image_url": "{{ dag.dag_id }}.png"}],
+ )
+ notifier({"dag": dag, "username": "not-a-root"})
+ mock_slack_hook.return_value.send.assert_called_once_with(
+ text="Who am I? not-a-root",
+ blocks=[
+ {
+ "type": "header",
+ "text": {"type": "plain_text", "text": "test_send_slack_webhook_notification_templated"},
+ }
+ ],
+ attachments=[{"image_url": "test_send_slack_webhook_notification_templated.png"}],
+ unfurl_links=None,
+ unfurl_media=None,
+ )