Skip to content

Commit

Permalink
Add SMTP server email notification example (#386)
Browse files Browse the repository at this point in the history
* add email notification example

* add workaround for passing possible files

* Update send-email.py
  • Loading branch information
Tomcli authored Jan 11, 2021
1 parent 7563d2f commit d8a9d5b
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 0 deletions.
53 changes: 53 additions & 0 deletions samples/exit-handler-email/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Email notification via SMTP server

This pipeline demonstrates how to use the exit handler in the Kubeflow pipeline to send email notifications via the SMTP server. The exit component is based on the [send-email](https://github.com/tektoncd/catalog/tree/master/task/sendmail/0.1) Tekton catalog task.

## prerequisites
- Install [KFP Tekton prerequisites](/samples/README.md)
- Host or subscribe to a [SMTP server](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol)

## Instructions
1. Modify [secret.yaml](secret.yaml) with the following information. Then create the secret under the Kubeflow namespace (for single user) or User namespace (for multi-user).

* **url**: The IP address of the SMTP server

* **port**: The port number of the SMTP server

* **user**: User name for the SMTP server

* **password**: Password for the SMTP server

* **tls**: The tls enabled or not ("True" or "False")

```shell
NAMESPACE=kubeflow
kubectl apply -f secret.yaml -n ${NAMESPACE}
```

2. Create a shared persistent volume claim for passing optional attachment.

```shell
kubectl apply -f pvc.yaml -n ${NAMESPACE}
```

3. Compile the send-email pipeline using the compiler inside the python code. The kfp-tekton SDK will produce a Tekton pipeline yaml definition in the same directory called `email_pipeline.yaml`.
```shell
# Compile the python code
python send-email.py
```

Then, upload the `email_pipeline.yaml` file to the Kubeflow pipeline dashboard with Tekton Backend to run this pipeline.

### Pipeline parameters

* **server**: The name of the secret that has the SMTP server information

* **subject**: Email subject (plain text)

* **body**: Email body (plain text)

* **sender**: Email sender email address

* **recipients**: Email recipients email addresses (comma space delimited)

* **attachment_path**: Optional attachment path from the previous path
75 changes: 75 additions & 0 deletions samples/exit-handler-email/component.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: sendmail
description: |
This task sends a simple email to receivers via SMTP server
inputs:
- {name: server, description: 'secret name for SMTP server information (url, port, password)'}
- {name: subject, description: 'plain text email subject'}
- {name: body, description: 'plain text email body'}
- {name: sender, description: 'sender email address'}
- {name: recipients, description: 'recipient email addresses (space delimited list)'}
- {name: attachment_path, description: 'email attachment file path'}
implementation:
container:
image: docker.io/library/python:3.8-alpine@sha256:c31682a549a3cc0a02f694a29aed07fd252ad05935a8560237aed99b8e87bf77 #tag: 3.8-alpine
command:
- python3
- -u
- -c
- |
#!/usr/bin/env python3
import argparse
_parser = argparse.ArgumentParser('sendmail inputs')
_parser.add_argument("--server", type=str, required=True)
_parser.add_argument("--subject", type=str, required=True)
_parser.add_argument("--body", type=str, required=True)
_parser.add_argument("--sender", type=str, required=True)
_parser.add_argument("--recipients", type=str, required=True)
_parser.add_argument("--attachment_path", type=str, default='')
_parsed_args = _parser.parse_args()
import smtplib, ssl, os
from pathlib import Path
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
from email import encoders
port = os.getenv('PORT')
smtp_server = os.getenv('SERVER')
sender_email = "$(params.sender)"
receiver_emails = "$(params.recipients)"
user = os.getenv('USER')
password = os.getenv('PASSWORD')
tls = os.getenv('TLS')
path = _parsed_args.attachment_path
msg = MIMEMultipart()
msg['From'] = sender_email
msg['To'] = receiver_emails
msg['Subject'] = "$(params.subject)"
msg.attach(MIMEText("$(params.body)"))
if tls == 'True':
context = ssl.create_default_context()
server = smtplib.SMTP_SSL(smtp_server, port, context=context)
else:
server = smtplib.SMTP(smtp_server, port)
if password != '':
server.login(user, password)
part = MIMEBase('application', "octet-stream")
with open(path, 'rb') as file:
part.set_payload(file.read())
encoders.encode_base64(part)
part.add_header('Content-Disposition',
'attachment; filename="{}"'.format(Path(path).name))
msg.attach(part)
for receiver in receiver_emails.split(' '):
server.sendmail(sender_email, receiver, msg.as_string())
server.quit()
args: [
--server, {inputValue: server},
--subject, {inputValue: subject},
--body, {inputValue: body},
--sender, {inputValue: sender},
--recipients, {inputValue: recipients},
--attachment_path, {inputValue: attachment_path},
]
10 changes: 10 additions & 0 deletions samples/exit-handler-email/pvc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shared-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
10 changes: 10 additions & 0 deletions samples/exit-handler-email/secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
kind: Secret
apiVersion: v1
metadata:
name: server-secret
stringData:
url: "smtp.server.com"
port: "25"
user: "userid"
password: "password"
tls: "False"
77 changes: 77 additions & 0 deletions samples/exit-handler-email/send-email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Copyright 2020 kubeflow.org
#
# Licensed 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 kfp import dsl
import kfp
from kubernetes import client as k8s_client
from kfp.components import func_to_container_op
from kfp import onprem
import os


@func_to_container_op
def write_file(output_text_path: str):
with open(output_text_path, 'w') as writer:
writer.write('hello world')


def env_from_secret(env_name, secret_name, secret_key):
return k8s_client.V1EnvVar(
name=env_name,
value_from=k8s_client.V1EnvVarSource(
secret_key_ref=k8s_client.V1SecretKeySelector(
name=secret_name,
key=secret_key
)
)
)


email_op = kfp.components.load_component_from_file('component.yaml')
# pvc mount point has to be string, not pipeline param.
attachment_path = "/tmp/data"


@dsl.pipeline(
name='email_pipeline',
description='email pipeline'
)
def email_pipeline(
server="server-secret",
subject="Hi, again!",
body="Tekton email",
sender="[email protected]",
recipients="[email protected], [email protected]",
attachment_filepath="/tmp/data/output.txt"
):
email = email_op(server=server,
subject=subject,
body=body,
sender=sender,
recipients=recipients,
attachment_path=attachment_filepath)
email.add_env_variable(env_from_secret('USER', '$(params.server)', 'user'))
email.add_env_variable(env_from_secret('PASSWORD', '$(params.server)', 'password'))
email.add_env_variable(env_from_secret('TLS', '$(params.server)', 'tls'))
email.add_env_variable(env_from_secret('SERVER', '$(params.server)', 'url'))
email.add_env_variable(env_from_secret('PORT', '$(params.server)', 'port'))
email.apply(onprem.mount_pvc('shared-pvc', 'shared-pvc', attachment_path))

with dsl.ExitHandler(email):
write_file_task = write_file(attachment_filepath).apply(onprem.mount_pvc('shared-pvc', 'shared-pvc', attachment_path))


if __name__ == '__main__':
from kfp_tekton.compiler import TektonCompiler
TektonCompiler().compile(email_pipeline, 'email_pipeline.yaml')

0 comments on commit d8a9d5b

Please sign in to comment.