Added Send mail functionality & Dockerized application #1

Merged
PeterSurda merged 11 commits from cis-kuldeep/influx-smtp-gateway:master into master 2022-02-21 06:41:14 +00:00
3 changed files with 198 additions and 0 deletions
Showing only changes of commit 8cff562e64 - Show all commits

9
config.ini Normal file
View File

@ -0,0 +1,9 @@
[server]
server_host = 0.0.0.0
server_port = 8081
[app]
to_mail = test111@mailinator.com
from_mail = test@gmail.com
from_mail_password = test@123

171
main.py Normal file
View File

@ -0,0 +1,171 @@
#!/usr/bin/env python3
"""
Serve cloud init files
PeterSurda marked this conversation as resolved Outdated

change docstring

change docstring
"""
import configparser
import os
import json
import smtplib
import socket
import sys
from email.header import Header
from email.mime.text import MIMEText
import uuid as uuid_module
from ipaddress import AddressValueError, IPv4Address, IPv6Address
PeterSurda marked this conversation as resolved Outdated

class name

class name
PeterSurda marked this conversation as resolved Outdated

should log to stdout or stderr (research which one is more appropriate)

should log to stdout or stderr (research which one is more appropriate)
import cherrypy
PeterSurda marked this conversation as resolved Outdated

docstring

docstring
PATH = os.path.dirname(os.path.abspath(__file__))
PeterSurda marked this conversation as resolved
Review

this should go under __main__

this should go under `__main__`
CONFIG = configparser.ConfigParser()
PeterSurda marked this conversation as resolved Outdated

remove CONFIG and only use environment variables
if some variable missing, display a helpful error message and quit with non-zero error code

remove `CONFIG` and only use environment variables if some variable missing, display a helpful error message and quit with non-zero error code
CONFIG.read(os.path.join(PATH, "config.ini"))
PeterSurda marked this conversation as resolved Outdated

override from env variables

override from env variables
TO_MAIL = CONFIG["app"].get("to_mail")
FROM_MAIL = CONFIG["app"].get("from_mail")
FROM_MAIL_PASSWORD = CONFIG["app"].get("from_mail_password")
print("TO_MAIL: ", TO_MAIL)
PeterSurda marked this conversation as resolved Outdated

debug we don't need

debug we don't need
print("FROM_MAIL: ", FROM_MAIL)
print("FROM_MAIL_PASSWORD: ", FROM_MAIL_PASSWORD)
PeterSurda marked this conversation as resolved Outdated

port 587

port 587
class CloudInitRequest:
PeterSurda marked this conversation as resolved Outdated

this whole class we don't need

this whole class we don't need
"""
Request data for persistence across methods
"""
def __init__(self, request, uuid=None):
self.remoteip = None
self.hostinfo = ('localhost', )
self.request = request
self.meta_data = None
self.meta_data_loaded = False
PeterSurda marked this conversation as resolved Outdated

ideally an exception per line rather than together, then you can also use more specific exceptions.

ideally an exception per line rather than together, then you can also use more specific exceptions.
self.user_data = None
PeterSurda marked this conversation as resolved Outdated
  • return an error code (in addition to error text) if there's an error
  • add a small delay if there's an error before returning, say 0.2s, and try to avoid sleep, instead look for a way to do this asynchronously
- return an error code (in addition to error text) if there's an error - add a small delay if there's an error before returning, say 0.2s, and try to avoid `sleep`, instead look for a way to do this asynchronously
try:
self.uuid = str(uuid_module.UUID('{' + uuid + '}'))
# ValueError is wrong UUID syntax
# TypeError is None
except (ValueError, TypeError):
PeterSurda marked this conversation as resolved Outdated

docstring

docstring
self.uuid = None
self._init_ip()
PeterSurda marked this conversation as resolved Outdated

except (SMTPConnectionError, TimeoutError) as e:

`except (SMTPConnectionError, TimeoutError) as e:`
def _can_ip_be_proxy(self):
"""
Assuming the connection is through a proxy, is this proxy permitted?
Can't proxy from a publicly reachable IP.
"""
PeterSurda marked this conversation as resolved Outdated

SERVER_PORT should default to 587 and be an integer.

`SERVER_PORT` should default to `587` and be an integer.
self.remoteip = self.request.remote.ip
try:
ipobj = IPv4Address(self.remoteip)
except AddressValueError:
try:
PeterSurda marked this conversation as resolved Outdated

except KeyError

`except KeyError`
ipobj = IPv6Address(self.remoteip)
except AddressValueError:
return False
return not ipobj.is_global
PeterSurda marked this conversation as resolved Outdated

except smtplib.SMTPException

`except smtplib.SMTPException`
def _init_ip(self):
"""
Get remote IP
"""
if self._can_ip_be_proxy():
try:
PeterSurda marked this conversation as resolved Outdated

merge with previous try/except

merge with previous `try`/`except`
self.remoteip = self.request.headers.get(
'X-Real-Ip',
PeterSurda marked this conversation as resolved Outdated

quit() should be in finally:

`quit()` should be in `finally:`
self.request.remote.ip
)
except KeyError:
pass
try:
self.hostinfo = socket.gethostbyaddr(self.remoteip)
forward_lookup = socket.gethostbyname(self.hostinfo[0])
if forward_lookup != self.remoteip:
self.hostinfo = ('localhost', )
except socket.herror:
self.hostinfo = ('localhost', )
except socket.gaierror:
self.hostinfo = (self.remoteip, )
class CloudInitApp:
PeterSurda marked this conversation as resolved Outdated

different name

different name
"""
Serve cloud init files
"""
@staticmethod
def _content_type(data):
PeterSurda marked this conversation as resolved Outdated

remove

remove
if not data:
return "text/cloud-config"
if data.startswith("#include"):
return "text/x-include-url"
if data.startswith("## template: jinja"):
return "text/jinja2"
return "text/cloud-config"
def _send_mail(self):
try:
# pylint: disable=deprecated-lambda
PeterSurda marked this conversation as resolved Outdated

live data shouldn't be here, squash to get rid of it

live data shouldn't be here, squash to get rid of it
cl = cherrypy.request.headers['Content-Length']
rawbody = cherrypy.request.body.read(int(cl))
req_body = json.loads(rawbody)
subject = req_body['subject']
body = req_body['body']
client = smtplib.SMTP('smtp.gmail.com')
msg = MIMEText(body, 'plain', 'utf-8')
msg['Subject'] = Header(subject, 'utf-8')
msg['From'] = FROM_MAIL
msg['To'] = TO_MAIL
client.ehlo()
client.starttls()
client.ehlo()
client.login(msg["From"], FROM_MAIL_PASSWORD)
client.sendmail(msg['From'], msg['To'], msg.as_string())
client.quit()
return "mail sent successfully"
except Exception as e:
return "some error: {}".format(e)
@cherrypy.expose
def send_mail(self):
"""
v1 api endpoint user-data
"""
return self._send_mail()
ROOT = CloudInitApp()
if __name__ == "__main__":
cherrypy.server.socket_host = \
CONFIG["server"].get("server_host", "127.0.0.1")
cherrypy.server.socket_port = \
CONFIG["server"].getint("server_port", 8081)
ENGINE = cherrypy.engine
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
CONFIG = {
PeterSurda marked this conversation as resolved Outdated

remove CONFIG

remove `CONFIG`
'/include': {
'tools.staticdir.on': True,
'tools.staticdir.dir': os.path.join(CURRENT_DIR,
'data',
'include'),
'tools.staticdir.content_types': {
'yml': 'text/yaml'
}
}
}
cherrypy.tree.mount(ROOT, config=CONFIG)
if hasattr(ENGINE, "signal_handler"):
ENGINE.signal_handler.subscribe()
if hasattr(ENGINE, "console_control_handler"):
ENGINE.console_control_handler.subscribe()
try:
ENGINE.start()
except Exception: # pylint: disable=broad-except
sys.exit(1)
else:
ENGINE.block()

18
requirements.txt Normal file
View File

@ -0,0 +1,18 @@
cheroot==8.6.0
CherryPy==18.6.1
importlib-resources==5.4.0
jaraco.classes==3.2.1
jaraco.collections==3.5.1
jaraco.context==4.1.1
jaraco.functools==3.5.0
jaraco.text==3.7.0
Jinja2==3.0.3
MarkupSafe==2.0.1
more-itertools==8.12.0
portend==3.1.0
pytz==2021.3
PyYAML==6.0
six==1.16.0
tempora==5.0.1
zc.lockfile==2.0
zipp==3.7.0