Auparavant, en utilisant le service de messagerie d'IFTTT [^ iftttemail] comme récepteur de messagerie, accédez à l'API GitHub [^ githubapi] via AWS Lambda et Ajouter un nouveau problème à GitHub par courrier / items / ff516aa90eb87c5140e7) J'ai créé une fonction. Il est en fait très pratique de pouvoir créer un problème GitHub avec un seul e-mail lorsque vous remarquez un bogue ou une amélioration de votre propre service. Je pense que je continuerai à l'utiliser à l'avenir, alors je l'ai refait pour qu'il fonctionne sur AWS, y compris le récepteur de messagerie.
Utilisez Amazon SES (Simple Email Service) [^ ses] comme destinataire du courrier. En dirigeant la destination de distribution du courrier du domaine que vous gérez vers le point de terminaison de réception de SES, vous pouvez envoyer du courrier à Amazon SES → Amazon S3 → AWS Lambda et au relais de compartiment. J'ai implémenté la fonction Lambda qui ajoute un problème au référentiel GitHub en accédant à l'API GitHub [^ githubapi] en fonction du contenu de l'e-mail, en utilisant le framework Python AWS Chalice [^ calice].
La procédure de mise en œuvre est à peu près la suivante.
Pour 1-3, consultez le AWS Developer Guide "[Réception d'e-mails à l'aide d'Amazon SES-Amazon Simple Email Service](https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/receiving] -email.html) »et informations d'assistance« [Recevoir et enregistrer des e-mails sur Amazon S3 à l'aide d'Amazon SES](https://aws.amazon.com/jp/premiumsupport/knowledge-center/ses- receive-inbound-emails /) »est détaillé. Pour 4, la procédure spécifique est expliquée dans "Comment configurer GitHub" Jetons d'accès personnels "--Qiita".
Donc, dans cet article, je vais résumer les implémentations de 5 et 6 ci-dessous.
Ce que vous voulez faire dans 5 et 6 ci-dessus est, après tout, lorsqu'un nouveau courrier entrant est enregistré dans le compartiment S3, lire le courrier reçu à partir du compartiment S3 et ajouter le problème au référentiel GitHub. Ce processus, Chalice [^ calice], un framework Python pour le développement basé sur Lambda, peut être réalisé très facilement en utilisant un décorateur appelé «on_s3_event».
Chalice.on_s3_event() S3 dispose d'un mécanisme pour envoyer une notification à Lambda, etc. en cas de modification dans le compartiment. Pour utiliser ce mécanisme, il est nécessaire de définir un événement pour ignorer la notification dans S3 et créer une fonction pour recevoir la notification dans Lambda, mais si vous utilisez Chalice, ces paramètres seront effectués presque automatiquement.
Le code de base qui implémente la fonction Lambda qui reçoit les événements S3 dans Chalice est [^ on_s3_event].
app.py(sample)
from chalice import Chalice
app = chalice.Chalice(app_name='s3eventdemo')
app.debug = True
@app.on_s3_event(bucket='mybucket-name',
events=['s3:ObjectCreated:*'])
def handle_s3_event(event):
app.log.debug("Received event for bucket: %s, key: %s",
event.bucket, event.key)
Chalice.on_s3_event ()
Si vous définissez une fonction avec un décorateur et écrivez du code, lorsque vous déployez la fonction sur Lambda avec chalice deploy
, tous les rôles et paramètres d'événement pour S3 et Lambda seront effectués automatiquement. Je vais.
Donc, cette fois, dans cette fonction avec le décorateur Chalice.on_s3_event ()
, j'ai décrit le processus de lecture du courrier reçu depuis le compartiment S3 [^ email] et l'ajout du problème au référentiel GitHub. Le code principal de Chalice, ʻapp.py`, est le suivant.
app.py
from chalice import Chalice
import logging, os, json, re
import boto3
from botocore.exceptions import ClientError
import email
from email.header import decode_header
from email.utils import parsedate_to_datetime
import urllib.request
# setup chalice
app = Chalice(app_name='mail2issue')
app.debug = False
# setup logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logformat = (
'[%(levelname)s] %(asctime)s.%(msecs)dZ (%(aws_request_id)s) '
'%(filename)s:%(funcName)s[%(lineno)d] %(message)s'
)
formatter = logging.Formatter(logformat, '%Y-%m-%dT%H:%M:%S')
for handler in logger.handlers:
handler.setFormatter(formatter)
# on_s3_event
@app.on_s3_event(
os.environ.get('BUCKET_NAME'),
events = ['s3:ObjectCreated:*'],
prefix = os.environ.get('BUCKET_KEY_PREFIX')
)
def receive_mail(event):
logger.info('received key: {}'.format(event.key))
# read S3 object (email message)
obj = getS3Object(os.environ.get('BUCKET_NAME'), event.key)
if obj is None:
logger.warning('object not found!')
return
# read S3 object (config)
config = getS3Object(os.environ.get('BUCKET_NAME'), 'mail2issue-config.json')
if config is None:
logger.warning('mail2issue-config.json not found!')
return
settings = json.loads(config)
#Analyser les e-mails
msg = email.message_from_bytes(obj)
msg_from = get_header(msg, 'From')
msg_subject = get_header(msg, 'Subject')
msg_content = get_content(msg)
#Extraire l'adresse e-mail
pattern = "[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+"
adds = re.findall(pattern, msg_from)
#Extraire les paramètres correspondant à l'adresse e-mail
config = None
for add in settings:
if add in adds:
config = settings[add]
break
if config is None:
logger.info('there is no config for {}'.format(', '.join(adds)))
return
#Obtenez un référentiel
repos = getRepositories(config['GITHUB_ACCESS_TOKEN'])
logger.info('repositories: {}'.format(repos))
#Déterminez le référentiel à partir du titre de l'e-mail
repo = config['GITHUB_DEFAULT_REPOSITORY']
title = msg_subject
spaceIdx = msg_subject.find(' ')
if spaceIdx > 0:
repo_tmp = msg_subject[0:spaceIdx]
if repo_tmp in repos:
title = msg_subject[spaceIdx+1:]
repo = repo_tmp
title = title.lstrip().rstrip()
logger.info("repository: '{}'".format(repo))
logger.info("title: '{}'".format(title))
#Problème POST
postIssue(
config['GITHUB_ACCESS_TOKEN'],
config['GITHUB_OWNER'],
repo, title, msg_content
)
#Supprimer le courrier
deleteS3Object(os.environ.get('BUCKET_NAME'), event.key)
#Obtenir un objet de S3
def getS3Object(bucket, key):
ret = None
s3obj = None
try:
s3 = boto3.client('s3')
s3obj = s3.get_object(
Bucket = bucket,
Key = key
)
except ClientError as e:
logger.warning('S3 ClientError: {}'.format(e))
if s3obj is not None:
ret = s3obj['Body'].read()
return ret
#Supprimer l'objet S3
def deleteS3Object(bucket, key):
try:
s3 = boto3.client('s3')
s3.delete_object(
Bucket = bucket,
Key = key
)
except ClientError as e:
logger.warning('S3 ClientError: {}'.format(e))
#Obtenir l'en-tête du courrier
def get_header(msg, name):
header = ''
if msg[name]:
for tup in decode_header(str(msg[name])):
if type(tup[0]) is bytes:
charset = tup[1]
if charset:
header += tup[0].decode(tup[1])
else:
header += tup[0].decode()
elif type(tup[0]) is str:
header += tup[0]
return header
#Obtenir le corps de l'e-mail
def get_content(msg):
charset = msg.get_content_charset()
payload = msg.get_payload(decode=True)
try:
if payload:
if charset:
return payload.decode(charset)
else:
return payload.decode()
else:
return ""
except:
return payload
#Obtenir une liste des dépôts github
def getRepositories(token):
req = urllib.request.Request(
'https://api.github.com/user/repos',
method = 'GET',
headers = {
'Authorization': 'token {}'.format(token)
}
)
repos = []
try:
with urllib.request.urlopen(req) as res:
for repo in json.loads(res.read().decode('utf-8')):
repos.append(repo['name'])
except Exception as e:
logger.exception("urlopen error: %s", e)
return set(repos)
#Ajouter un problème au référentiel github
def postIssue(token, owner, repository, title, content):
req = urllib.request.Request(
'https://api.github.com/repos/{}/{}/issues'.format(owner, repository),
method = 'POST',
headers = {
'Content-Type': 'application/json',
'Authorization': 'token {}'.format(token)
},
data = json.dumps({
'title': title,
'body': content
}).encode('utf-8'),
)
try:
with urllib.request.urlopen(req) as res:
logger.info(res.read().decode("utf-8"))
except Exception as e:
logger.exception("urlopen error: %s", e)
Le fichier de paramètres suivant est lu à partir de S3 afin que le jeton d'accès pour l'utilisation de l'API GitHub puisse être changé en fonction de l'adresse e-mail de l'expéditeur.
mail2issue-config.json
{
"<Adresse e-mail de l'expéditeur>": {
"GITHUB_OWNER": "<Nom d'utilisateur GitHub>",
"GITHUB_ACCESS_TOKEN": "<Jeton d'accès GitHub>",
"GITHUB_DEFAULT_REPOSITORY": "<Nom du référentiel s'il n'est pas spécifié dans le titre de l'e-mail>"
},
...
}
Si je touchais à Amazon SES dans un autre but et que je pouvais recevoir des e-mails sur AWS, j'ai proposé cette refactorisation. Il existe encore de nombreux services déclenchés par e-mail, nous continuerons donc à envisager d'appliquer ce modèle.