Added Send mail functionality & Dockerized application #1
9
config.ini
Normal file
9
config.ini
Normal 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
171
main.py
Normal file
|
@ -0,0 +1,171 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Serve cloud init files
|
||||
PeterSurda marked this conversation as resolved
Outdated
|
||||
"""
|
||||
|
||||
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
PeterSurda
commented
class name class name
|
||||
|
||||
PeterSurda marked this conversation as resolved
Outdated
PeterSurda
commented
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
PeterSurda
commented
docstring docstring
|
||||
|
||||
PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
PeterSurda marked this conversation as resolved
PeterSurda
commented
this should go under this should go under `__main__`
|
||||
CONFIG = configparser.ConfigParser()
|
||||
PeterSurda marked this conversation as resolved
Outdated
PeterSurda
commented
remove 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
PeterSurda
commented
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
PeterSurda
commented
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
PeterSurda
commented
port 587 port 587
|
||||
|
||||
|
||||
class CloudInitRequest:
|
||||
PeterSurda marked this conversation as resolved
Outdated
PeterSurda
commented
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
PeterSurda
commented
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
PeterSurda
commented
- 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
PeterSurda
commented
docstring docstring
|
||||
self.uuid = None
|
||||
|
||||
self._init_ip()
|
||||
|
||||
PeterSurda marked this conversation as resolved
Outdated
PeterSurda
commented
`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
PeterSurda
commented
`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
PeterSurda
commented
`except KeyError`
|
||||
ipobj = IPv6Address(self.remoteip)
|
||||
except AddressValueError:
|
||||
return False
|
||||
return not ipobj.is_global
|
||||
|
||||
PeterSurda marked this conversation as resolved
Outdated
PeterSurda
commented
`except smtplib.SMTPException`
|
||||
def _init_ip(self):
|
||||
"""
|
||||
Get remote IP
|
||||
"""
|
||||
if self._can_ip_be_proxy():
|
||||
try:
|
||||
PeterSurda marked this conversation as resolved
Outdated
PeterSurda
commented
merge with previous merge with previous `try`/`except`
|
||||
self.remoteip = self.request.headers.get(
|
||||
'X-Real-Ip',
|
||||
PeterSurda marked this conversation as resolved
Outdated
PeterSurda
commented
`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
PeterSurda
commented
different name different name
|
||||
"""
|
||||
Serve cloud init files
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _content_type(data):
|
||||
PeterSurda marked this conversation as resolved
Outdated
PeterSurda
commented
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
PeterSurda
commented
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
PeterSurda
commented
remove 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
18
requirements.txt
Normal 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
|
Loading…
Reference in New Issue
Block a user
change docstring