Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SMTP server email notification example #386

Merged
merged 5 commits into from
Jan 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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')