boto3
– набор библиотек для Python, для работы с AWS.
AWS CLI “под капотом” использует boto3
для работы с ядром Amazon Web Services.
Данный пост – ни разу не HowTo, а скорее просто набор заметок и быстрых примеров + пример скрипта, использующего boto3
для работы с AWS SQS и SES.
Документация: https://boto3.readthedocs.org/en/latest
Документация по модулю Session
: http://boto3.readthedocs.org/en/latest/reference/core/session.html
Документация по модулю sqs
: https://boto3.readthedocs.org/en/latest/guide/sqs.html
Документация по модулю ses
: https://boto3.readthedocs.org/en/latest/reference/services/ses.html
Установка:
$ sudo pip install boto3
Для авторизации boto3
использует ~/.aws/credentials
, ~/.aws/config
либо данные, указанные в самом коде при создании сессии session.Session()
.
Проверяем:
>>> import boto3 >>> ec2 = boto3.resource('ec2') >>> help(ec2)
Результат:
Help on ec2.ServiceResource in module boto3.resources.factory object: class ec2.ServiceResource(boto3.resources.base.ServiceResource) | Method resolution order: | ec2.ServiceResource | boto3.resources.base.ServiceResource | __builtin__.object | | Methods defined here: | | ClassicAddress(self, *args, **kwargs) | Creates a ClassicAddress resource.:: ...
Создать сессию и просмотреть имеющиеся инстансы EC2 можно так:
>>> from boto3.session import Session >>> session = Session(aws_access_key_id='AKI***D2A', aws_secret_access_key='E4Y***uR7', region_name='eu-west-1') >>> ec2 = session.resource('ec2') >>> for instance in ec2.instances.all(): ... print(instance.id) ... i-82a14b08
Использование фильтров:
>>> instances = ec2.instances.filter( ... Filters=[{'Name': 'instance-state-name', 'Values': ['running']}]) >>> for instance in instances: ... print(instance.id, instance.instance_type) ... ('i-82a14b08', 't2.micro')
>>> for status in ec2.meta.client.describe_instance_status()['InstanceStatuses']: ... print(status) ... {u'InstanceId': 'i-82a14b08', u'InstanceState': {u'Code': 16, u'Name': 'running'}, u'AvailabilityZone': 'eu-west-1a', u'SystemStatus': {u'Status': 'ok', u'Details': [{u'Status': 'passed', u'Name': 'reachability'}]}, u'InstanceStatus': {u'Status': 'ok', u'Details': [{u'Status': 'passed', u'Name': 'reachability'}]}}
Содержание
Работа с AWS SQS
Создаем новую очередь:
>>> sqs = session.resource('sqs') >>> queue = sqs.create_queue(QueueName='test', Attributes={'DelaySeconds': '5'})
Вот так она выглядит в консоли AWS:
Или в AWS CLI:
$ aws sqs list-queues { "QueueUrls": [ "https://eu-west-1.queue.amazonaws.com/264418146286/test" ] }
Чтение из очереди:
>>> print(queue.url) https://eu-west-1.queue.amazonaws.com/264418146286/test >>> print(queue.attributes.get('DelaySeconds')) 5
Поиск очереди по имени:
>>> queue = sqs.get_queue_by_name(QueueName='test') >>> print(queue) sqs.Queue(url='https://eu-west-1.queue.amazonaws.com/264418146286/test') >>> queue = sqs.get_queue_by_name(QueueName='NOtest') Traceback (most recent call last): ... botocore.exceptions.ClientError: An error occurred (AWS.SimpleQueueService.NonExistentQueue) when calling the GetQueueUrl operation: The specified queue does not exist for this wsdl version.
Чтение из очереди:
>>> queue = sqs.create_queue(QueueName='test2', Attributes={'DelaySeconds': '5'}) >>> sqs = session.resource('sqs') >>> for queue in sqs.queues.all(): ... print(queue.url) ... https://eu-west-1.queue.amazonaws.com/264418146286/test https://eu-west-1.queue.amazonaws.com/264418146286/test2
Отправка сообщения в очередь:
>>> sqs = session.resource('sqs') >>> queue = sqs.get_queue_by_name(QueueName='test') >>> response = queue.send_message(MessageBody='world') >>> print(response.get('MessageId')) 1d134a72-91a2-4f48-aa1b-38d66b414b9b >>> print(response.get('MD5OfMessageBody')) 7d793037a0760186574b0282f2f435e7
Еще немного документации:
http://docs.aws.amazon.com/cli/latest/reference/sqs/list-queues.html
https://boto3.readthedocs.org/en/latest/guide/sqs.html
Работа с AWS SES
Используем resource
вместо client
.
Для отправки писем можно использовать методы send_email()
и send_raw_email()
.
Пример с send_mail()
:
ses = session.client('ses') print(ses) response = ses.send_email( Source='[email protected]', Destination={ 'ToAddresses': [ '[email protected]', '[email protected]' ], 'CcAddresses': [ '[email protected]', '[email protected]', ] }, Message={ 'Subject': { 'Data': 'Test SES Boto3 - Subj' }, 'Body': { 'Text': { 'Data': 'Test SES Boto3 - Body' } } }, ReplyToAddresses=[ '[email protected]' ], SourceArn='arn:aws:ses:eu-west-1:***:identity/***@***.com' ) print(response)</pre> Результат:
$ ./queues.py <botocore.client.SES object at 0x1037abf10> {'ResponseMetadata': {'HTTPStatusCode': 200, 'RequestId': '3d200e82-e137-11e5-9b70-93e5e7cade1b'}, u'MessageId': '010201533c57e697-87c0b5a9-204c-4a8e-98df-1a5b5e247be4-000000'};
И пример: скрипт пересылки запускается PHP-скриптом приложения, обращается к очереди, пытается читать сообщения. Если сообщения есть – он парсит его, выделяя поля to, subject и content. Из поля content – берется ссылка на PHP-шаблон письма, в котором с помощью Python string.Template()
заменяются переменные $email (логин пользователя) и $password, после чего письмо отправляется адресату с его данными доступа.
#!/usr/bin/env python import urllib2 import json import os import sys import logging import collections from boto3.session import Session from string import Template from urlparse import urljoin # for raw_send from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText class Logger(object): def __init__(self, logpath): """Create Logger""" self.logpath = logpath if not os.path.isdir(os.path.join(self.logpath, 'logs')): os.mkdir(os.path.join(self.logpath, 'logs')) def logger(self, modname): self.log = logging.getLogger(modname) formatter = logging.Formatter('%(asctime)s - %(filename)s[LINE:%(lineno)d] - %(name)s.%(funcName)s() - %(message)s') self.log.setLevel(logging.DEBUG) filehandler = logging.FileHandler(os.path.join(self.logpath, 'logs', 'courier.log')) filehandler.setLevel(logging.DEBUG) filehandler.setFormatter(formatter) consolehandler = logging.StreamHandler() consolehandler.setLevel(logging.INFO) self.log.addHandler(filehandler) self.log.addHandler(consolehandler) logger = Logger('.') logger.logger(__file__) session = Session(aws_access_key_id='***', aws_secret_access_key='***', region_name='eu-west-1') sqs = session.client('sqs') ses = session.client('ses') def convert(data): """ UTF8 => ASCII converter for body['parameters']""" if isinstance(data, basestring): return str(data) elif isinstance(data, collections.Mapping): return dict(map(convert, data.iteritems())) elif isinstance(data, collections.Iterable): return type(data)(map(convert, data)) else: return data def ses_send(m_to, m_subj, m_text): """Obsolete but leave it here""" logger.log.info('Sent email to: {}'.format(m_to)) response = ses.send_email( Source='[email protected]', Destination={ 'ToAddresses': [ m_to ] }, Message={ 'Subject': { 'Data': m_subj }, 'Body': { 'Text': { 'Data': 'Content text' }, 'Html': { 'Data': m_text } } }, ReplyToAddresses=[ '[email protected]' ], SourceArn='arn:aws:ses:eu-west-1:***:identity/[email protected]' ) return response['ResponseMetadata']['HTTPStatusCode'] def ses_raw_send(m_to, m_subj, m_text): to = [] to.append(m_to) logger.log.info('Sent email to: {}'.format(m_to)) msg = MIMEMultipart() msg['Subject'] = m_subj msg['From'] = 'Sender Name <[email protected]>' msg['To'] = m_to msg.attach(MIMEText(m_text,'html')) response = ses.send_raw_email( RawMessage={ 'Data': msg.as_string() }, Destinations=to, SourceArn='arn:aws:ses:eu-west-1:***:identity/[email protected]' ) # 200 if OK return response['ResponseMetadata']['HTTPStatusCode'] if __name__ == '__main__': # call as # ./courier.py fl-dev-emails # ./courier.py fl-prod-emails if len(sys.argv) == 2: q_url = urljoin('https://sqs.eu-west-1.amazonaws.com/***/', sys.argv[1]) else: logger.log.error('ERROR: SQS name must be specified as first argument. Exit.') exit(1) logger.log.info('Started with URL {}'.format(q_url)) while True: message = sqs.receive_message(QueueUrl=q_url) try: body = json.loads(message.get('Messages')[0]["Body"]) m_id = message.get('Messages')[0]["ReceiptHandle"] m_to = body['to'] m_subj = body['title'] m_url = body['content'] data = urllib2.urlopen('{}'.format(m_url)) m_content = """{}""".format(data.read()) # try fetch 'parameters' as some SQS messages may contain only static data try: tmpl = Template(m_content) m_params = convert(body['parameters']) m_text = tmpl.substitute(m_params) except KeyError as error: logger.log.info('No Params: {}'.format(error)) m_text = m_content if ses_raw_send(m_to, m_subj, m_text) == 200: sqs.delete_message(QueueUrl=q_url, ReceiptHandle=m_id) logger.log.info('Message {} deleted from queue'.format(message.get('Messages')[0]["MessageId"])) else: print 'ERR' exit(1) # concerns about timeouts # 'NoneType' object has no attribute '__getitem__' except TypeError as error: logger.log.info('Seems queue empty, exiting') break
Пример скрипта для отправки тестового сообщения в SQS, которое потом обрабатывается скриптом выше:
#!/usr/bin/env python import time from boto3.session import Session session = Session(aws_access_key_id='***', aws_secret_access_key='***', region_name='eu-west-1') sqs = session.resource('sqs') #sqs = session.client('ses') queue = sqs.get_queue_by_name(QueueName='fl-dev-emails') count = 10 a = 1 while a <= count: response = queue.send_messages(Entries=[ { 'Id': '{}'.format(a), 'MessageBody': '{"to": "[email protected]", ' '"title": "Test - Entry welcome", ' '"content": "http:\/\/entry.dev.domain.com\/api\/application\/welcome.php",' '"parameters": {"email": "[email protected]", "password": "p@ssw0rd"}' '}' } ]) a += 1</pre>
UPD Наткнулся на неплохой обзор с примерами по boto3
тут>>>.