Removed boto and ec2 related metadata
This commit is contained in:
parent
e4bb04ef38
commit
339d9f5904
|
@ -1 +0,0 @@
|
|||
__author__ = 'gary'
|
|
@ -10,7 +10,7 @@ import socket
|
|||
|
||||
import modules
|
||||
|
||||
from util import get_os, get_uuid, md5, Timer, get_hostname, EC2, GCE
|
||||
from util import get_os, get_uuid, md5, Timer, get_hostname, GCE
|
||||
from config import get_version, get_system_stats
|
||||
|
||||
import checks.system.unix as u
|
||||
|
@ -371,9 +371,6 @@ class Collector(object):
|
|||
if self.agentConfig['tags'] is not None:
|
||||
host_tags.extend([unicode(tag.strip()) for tag in self.agentConfig['tags'].split(",")])
|
||||
|
||||
if self.agentConfig['collect_ec2_tags']:
|
||||
host_tags.extend(EC2.get_tags())
|
||||
|
||||
if host_tags:
|
||||
payload['host-tags']['system'] = host_tags
|
||||
|
||||
|
@ -388,11 +385,7 @@ class Collector(object):
|
|||
return payload
|
||||
|
||||
def _get_metadata(self):
|
||||
metadata = EC2.get_metadata()
|
||||
if metadata.get('hostname'):
|
||||
metadata['ec2-hostname'] = metadata.get('hostname')
|
||||
del metadata['hostname']
|
||||
|
||||
metadata = {}
|
||||
if self.agentConfig.get('hostname'):
|
||||
metadata['agent-hostname'] = self.agentConfig.get('hostname')
|
||||
else:
|
||||
|
|
|
@ -1,838 +0,0 @@
|
|||
# Copyright (c) 2006-2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2010-2011, Eucalyptus Systems, Inc.
|
||||
# Copyright (c) 2011, Nexenta Systems Inc.
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
|
||||
# Copyright (c) 2010, Google, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
from boto.pyami.config import Config, BotoConfigLocations
|
||||
from boto.storage_uri import BucketStorageUri, FileStorageUri
|
||||
import boto.plugin
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import logging
|
||||
import logging.config
|
||||
import urlparse
|
||||
from boto.exception import InvalidUriError
|
||||
|
||||
__version__ = '2.15.0'
|
||||
Version = __version__ # for backware compatibility
|
||||
|
||||
UserAgent = 'Boto/%s Python/%s %s/%s' % (
|
||||
__version__,
|
||||
platform.python_version(),
|
||||
platform.system(),
|
||||
platform.release()
|
||||
)
|
||||
config = Config()
|
||||
|
||||
# Regex to disallow buckets violating charset or not [3..255] chars total.
|
||||
BUCKET_NAME_RE = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9\._-]{1,253}[a-zA-Z0-9]$')
|
||||
# Regex to disallow buckets with individual DNS labels longer than 63.
|
||||
TOO_LONG_DNS_NAME_COMP = re.compile(r'[-_a-z0-9]{64}')
|
||||
GENERATION_RE = re.compile(r'(?P<versionless_uri_str>.+)'
|
||||
r'#(?P<generation>[0-9]+)$')
|
||||
VERSION_RE = re.compile('(?P<versionless_uri_str>.+)#(?P<version_id>.+)$')
|
||||
|
||||
|
||||
def init_logging():
|
||||
for file in BotoConfigLocations:
|
||||
try:
|
||||
logging.config.fileConfig(os.path.expanduser(file))
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
log = logging.getLogger('boto')
|
||||
perflog = logging.getLogger('boto.perf')
|
||||
log.addHandler(NullHandler())
|
||||
perflog.addHandler(NullHandler())
|
||||
init_logging()
|
||||
|
||||
# convenience function to set logging to a particular file
|
||||
|
||||
|
||||
def set_file_logger(name, filepath, level=logging.INFO, format_string=None):
|
||||
global log
|
||||
if not format_string:
|
||||
format_string = "%(asctime)s %(name)s [%(levelname)s]:%(message)s"
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(level)
|
||||
fh = logging.FileHandler(filepath)
|
||||
fh.setLevel(level)
|
||||
formatter = logging.Formatter(format_string)
|
||||
fh.setFormatter(formatter)
|
||||
logger.addHandler(fh)
|
||||
log = logger
|
||||
|
||||
|
||||
def set_stream_logger(name, level=logging.DEBUG, format_string=None):
|
||||
global log
|
||||
if not format_string:
|
||||
format_string = "%(asctime)s %(name)s [%(levelname)s]:%(message)s"
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(level)
|
||||
fh = logging.StreamHandler()
|
||||
fh.setLevel(level)
|
||||
formatter = logging.Formatter(format_string)
|
||||
fh.setFormatter(formatter)
|
||||
logger.addHandler(fh)
|
||||
log = logger
|
||||
|
||||
|
||||
def connect_sqs(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.sqs.connection.SQSConnection`
|
||||
:return: A connection to Amazon's SQS
|
||||
"""
|
||||
from boto.sqs.connection import SQSConnection
|
||||
return SQSConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_s3(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.s3.connection.S3Connection`
|
||||
:return: A connection to Amazon's S3
|
||||
"""
|
||||
from boto.s3.connection import S3Connection
|
||||
return S3Connection(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_gs(gs_access_key_id=None, gs_secret_access_key=None, **kwargs):
|
||||
"""
|
||||
@type gs_access_key_id: string
|
||||
@param gs_access_key_id: Your Google Cloud Storage Access Key ID
|
||||
|
||||
@type gs_secret_access_key: string
|
||||
@param gs_secret_access_key: Your Google Cloud Storage Secret Access Key
|
||||
|
||||
@rtype: L{GSConnection<boto.gs.connection.GSConnection>}
|
||||
@return: A connection to Google's Storage service
|
||||
"""
|
||||
from boto.gs.connection import GSConnection
|
||||
return GSConnection(gs_access_key_id, gs_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_ec2(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.ec2.connection.EC2Connection`
|
||||
:return: A connection to Amazon's EC2
|
||||
"""
|
||||
from boto.ec2.connection import EC2Connection
|
||||
return EC2Connection(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_elb(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.ec2.elb.ELBConnection`
|
||||
:return: A connection to Amazon's Load Balancing Service
|
||||
"""
|
||||
from boto.ec2.elb import ELBConnection
|
||||
return ELBConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_autoscale(aws_access_key_id=None, aws_secret_access_key=None,
|
||||
**kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.ec2.autoscale.AutoScaleConnection`
|
||||
:return: A connection to Amazon's Auto Scaling Service
|
||||
"""
|
||||
from boto.ec2.autoscale import AutoScaleConnection
|
||||
return AutoScaleConnection(aws_access_key_id, aws_secret_access_key,
|
||||
**kwargs)
|
||||
|
||||
|
||||
def connect_cloudwatch(aws_access_key_id=None, aws_secret_access_key=None,
|
||||
**kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.ec2.cloudwatch.CloudWatchConnection`
|
||||
:return: A connection to Amazon's EC2 Monitoring service
|
||||
"""
|
||||
from boto.ec2.cloudwatch import CloudWatchConnection
|
||||
return CloudWatchConnection(aws_access_key_id, aws_secret_access_key,
|
||||
**kwargs)
|
||||
|
||||
|
||||
def connect_sdb(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.sdb.connection.SDBConnection`
|
||||
:return: A connection to Amazon's SDB
|
||||
"""
|
||||
from boto.sdb.connection import SDBConnection
|
||||
return SDBConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_fps(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.fps.connection.FPSConnection`
|
||||
:return: A connection to FPS
|
||||
"""
|
||||
from boto.fps.connection import FPSConnection
|
||||
return FPSConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_mturk(aws_access_key_id=None, aws_secret_access_key=None,
|
||||
**kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.mturk.connection.MTurkConnection`
|
||||
:return: A connection to MTurk
|
||||
"""
|
||||
from boto.mturk.connection import MTurkConnection
|
||||
return MTurkConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_cloudfront(aws_access_key_id=None, aws_secret_access_key=None,
|
||||
**kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.fps.connection.FPSConnection`
|
||||
:return: A connection to FPS
|
||||
"""
|
||||
from boto.cloudfront import CloudFrontConnection
|
||||
return CloudFrontConnection(aws_access_key_id, aws_secret_access_key,
|
||||
**kwargs)
|
||||
|
||||
|
||||
def connect_vpc(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.vpc.VPCConnection`
|
||||
:return: A connection to VPC
|
||||
"""
|
||||
from boto.vpc import VPCConnection
|
||||
return VPCConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_rds(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.rds.RDSConnection`
|
||||
:return: A connection to RDS
|
||||
"""
|
||||
from boto.rds import RDSConnection
|
||||
return RDSConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_emr(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.emr.EmrConnection`
|
||||
:return: A connection to Elastic mapreduce
|
||||
"""
|
||||
from boto.emr import EmrConnection
|
||||
return EmrConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_sns(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.sns.SNSConnection`
|
||||
:return: A connection to Amazon's SNS
|
||||
"""
|
||||
from boto.sns import SNSConnection
|
||||
return SNSConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_iam(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.iam.IAMConnection`
|
||||
:return: A connection to Amazon's IAM
|
||||
"""
|
||||
from boto.iam import IAMConnection
|
||||
return IAMConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_route53(aws_access_key_id=None, aws_secret_access_key=None,
|
||||
**kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.dns.Route53Connection`
|
||||
:return: A connection to Amazon's Route53 DNS Service
|
||||
"""
|
||||
from boto.route53 import Route53Connection
|
||||
return Route53Connection(aws_access_key_id, aws_secret_access_key,
|
||||
**kwargs)
|
||||
|
||||
|
||||
def connect_cloudformation(aws_access_key_id=None, aws_secret_access_key=None,
|
||||
**kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.cloudformation.CloudFormationConnection`
|
||||
:return: A connection to Amazon's CloudFormation Service
|
||||
"""
|
||||
from boto.cloudformation import CloudFormationConnection
|
||||
return CloudFormationConnection(aws_access_key_id, aws_secret_access_key,
|
||||
**kwargs)
|
||||
|
||||
|
||||
def connect_euca(host=None, aws_access_key_id=None, aws_secret_access_key=None,
|
||||
port=8773, path='/services/Eucalyptus', is_secure=False,
|
||||
**kwargs):
|
||||
"""
|
||||
Connect to a Eucalyptus service.
|
||||
|
||||
:type host: string
|
||||
:param host: the host name or ip address of the Eucalyptus server
|
||||
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.ec2.connection.EC2Connection`
|
||||
:return: A connection to Eucalyptus server
|
||||
"""
|
||||
from boto.ec2 import EC2Connection
|
||||
from boto.ec2.regioninfo import RegionInfo
|
||||
|
||||
# Check for values in boto config, if not supplied as args
|
||||
if not aws_access_key_id:
|
||||
aws_access_key_id = config.get('Credentials',
|
||||
'euca_access_key_id',
|
||||
None)
|
||||
if not aws_secret_access_key:
|
||||
aws_secret_access_key = config.get('Credentials',
|
||||
'euca_secret_access_key',
|
||||
None)
|
||||
if not host:
|
||||
host = config.get('Boto', 'eucalyptus_host', None)
|
||||
|
||||
reg = RegionInfo(name='eucalyptus', endpoint=host)
|
||||
return EC2Connection(aws_access_key_id, aws_secret_access_key,
|
||||
region=reg, port=port, path=path,
|
||||
is_secure=is_secure, **kwargs)
|
||||
|
||||
|
||||
def connect_glacier(aws_access_key_id=None, aws_secret_access_key=None,
|
||||
**kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.glacier.layer2.Layer2`
|
||||
:return: A connection to Amazon's Glacier Service
|
||||
"""
|
||||
from boto.glacier.layer2 import Layer2
|
||||
return Layer2(aws_access_key_id, aws_secret_access_key,
|
||||
**kwargs)
|
||||
|
||||
|
||||
def connect_ec2_endpoint(url, aws_access_key_id=None,
|
||||
aws_secret_access_key=None,
|
||||
**kwargs):
|
||||
"""
|
||||
Connect to an EC2 Api endpoint. Additional arguments are passed
|
||||
through to connect_ec2.
|
||||
|
||||
:type url: string
|
||||
:param url: A url for the ec2 api endpoint to connect to
|
||||
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.ec2.connection.EC2Connection`
|
||||
:return: A connection to Eucalyptus server
|
||||
"""
|
||||
from boto.ec2.regioninfo import RegionInfo
|
||||
|
||||
purl = urlparse.urlparse(url)
|
||||
kwargs['port'] = purl.port
|
||||
kwargs['host'] = purl.hostname
|
||||
kwargs['path'] = purl.path
|
||||
if not 'is_secure' in kwargs:
|
||||
kwargs['is_secure'] = (purl.scheme == "https")
|
||||
|
||||
kwargs['region'] = RegionInfo(name=purl.hostname,
|
||||
endpoint=purl.hostname)
|
||||
kwargs['aws_access_key_id'] = aws_access_key_id
|
||||
kwargs['aws_secret_access_key'] = aws_secret_access_key
|
||||
|
||||
return(connect_ec2(**kwargs))
|
||||
|
||||
|
||||
def connect_walrus(host=None, aws_access_key_id=None,
|
||||
aws_secret_access_key=None,
|
||||
port=8773, path='/services/Walrus', is_secure=False,
|
||||
**kwargs):
|
||||
"""
|
||||
Connect to a Walrus service.
|
||||
|
||||
:type host: string
|
||||
:param host: the host name or ip address of the Walrus server
|
||||
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.s3.connection.S3Connection`
|
||||
:return: A connection to Walrus
|
||||
"""
|
||||
from boto.s3.connection import S3Connection
|
||||
from boto.s3.connection import OrdinaryCallingFormat
|
||||
|
||||
# Check for values in boto config, if not supplied as args
|
||||
if not aws_access_key_id:
|
||||
aws_access_key_id = config.get('Credentials',
|
||||
'euca_access_key_id',
|
||||
None)
|
||||
if not aws_secret_access_key:
|
||||
aws_secret_access_key = config.get('Credentials',
|
||||
'euca_secret_access_key',
|
||||
None)
|
||||
if not host:
|
||||
host = config.get('Boto', 'walrus_host', None)
|
||||
|
||||
return S3Connection(aws_access_key_id, aws_secret_access_key,
|
||||
host=host, port=port, path=path,
|
||||
calling_format=OrdinaryCallingFormat(),
|
||||
is_secure=is_secure, **kwargs)
|
||||
|
||||
|
||||
def connect_ses(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.ses.SESConnection`
|
||||
:return: A connection to Amazon's SES
|
||||
"""
|
||||
from boto.ses import SESConnection
|
||||
return SESConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_sts(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.sts.STSConnection`
|
||||
:return: A connection to Amazon's STS
|
||||
"""
|
||||
from boto.sts import STSConnection
|
||||
return STSConnection(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_ia(ia_access_key_id=None, ia_secret_access_key=None,
|
||||
is_secure=False, **kwargs):
|
||||
"""
|
||||
Connect to the Internet Archive via their S3-like API.
|
||||
|
||||
:type ia_access_key_id: string
|
||||
:param ia_access_key_id: Your IA Access Key ID. This will also look
|
||||
in your boto config file for an entry in the Credentials
|
||||
section called "ia_access_key_id"
|
||||
|
||||
:type ia_secret_access_key: string
|
||||
:param ia_secret_access_key: Your IA Secret Access Key. This will also
|
||||
look in your boto config file for an entry in the Credentials
|
||||
section called "ia_secret_access_key"
|
||||
|
||||
:rtype: :class:`boto.s3.connection.S3Connection`
|
||||
:return: A connection to the Internet Archive
|
||||
"""
|
||||
from boto.s3.connection import S3Connection
|
||||
from boto.s3.connection import OrdinaryCallingFormat
|
||||
|
||||
access_key = config.get('Credentials', 'ia_access_key_id',
|
||||
ia_access_key_id)
|
||||
secret_key = config.get('Credentials', 'ia_secret_access_key',
|
||||
ia_secret_access_key)
|
||||
|
||||
return S3Connection(access_key, secret_key,
|
||||
host='s3.us.archive.org',
|
||||
calling_format=OrdinaryCallingFormat(),
|
||||
is_secure=is_secure, **kwargs)
|
||||
|
||||
|
||||
def connect_dynamodb(aws_access_key_id=None,
|
||||
aws_secret_access_key=None,
|
||||
**kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.dynamodb.layer2.Layer2`
|
||||
:return: A connection to the Layer2 interface for DynamoDB.
|
||||
"""
|
||||
from boto.dynamodb.layer2 import Layer2
|
||||
return Layer2(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_swf(aws_access_key_id=None,
|
||||
aws_secret_access_key=None,
|
||||
**kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.swf.layer1.Layer1`
|
||||
:return: A connection to the Layer1 interface for SWF.
|
||||
"""
|
||||
from boto.swf.layer1 import Layer1
|
||||
return Layer1(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_cloudsearch(aws_access_key_id=None,
|
||||
aws_secret_access_key=None,
|
||||
**kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.ec2.autoscale.CloudSearchConnection`
|
||||
:return: A connection to Amazon's CloudSearch service
|
||||
"""
|
||||
from boto.cloudsearch.layer2 import Layer2
|
||||
return Layer2(aws_access_key_id, aws_secret_access_key,
|
||||
**kwargs)
|
||||
|
||||
|
||||
def connect_beanstalk(aws_access_key_id=None,
|
||||
aws_secret_access_key=None,
|
||||
**kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.beanstalk.layer1.Layer1`
|
||||
:return: A connection to Amazon's Elastic Beanstalk service
|
||||
"""
|
||||
from boto.beanstalk.layer1 import Layer1
|
||||
return Layer1(aws_access_key_id, aws_secret_access_key, **kwargs)
|
||||
|
||||
|
||||
def connect_elastictranscoder(aws_access_key_id=None,
|
||||
aws_secret_access_key=None,
|
||||
**kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.ets.layer1.ElasticTranscoderConnection`
|
||||
:return: A connection to Amazon's Elastic Transcoder service
|
||||
"""
|
||||
from boto.elastictranscoder.layer1 import ElasticTranscoderConnection
|
||||
return ElasticTranscoderConnection(
|
||||
aws_access_key_id=aws_access_key_id,
|
||||
aws_secret_access_key=aws_secret_access_key,
|
||||
**kwargs)
|
||||
|
||||
|
||||
def connect_opsworks(aws_access_key_id=None,
|
||||
aws_secret_access_key=None,
|
||||
**kwargs):
|
||||
from boto.opsworks.layer1 import OpsWorksConnection
|
||||
return OpsWorksConnection(
|
||||
aws_access_key_id=aws_access_key_id,
|
||||
aws_secret_access_key=aws_secret_access_key,
|
||||
**kwargs)
|
||||
|
||||
|
||||
def connect_redshift(aws_access_key_id=None,
|
||||
aws_secret_access_key=None,
|
||||
**kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.redshift.layer1.RedshiftConnection`
|
||||
:return: A connection to Amazon's Redshift service
|
||||
"""
|
||||
from boto.redshift.layer1 import RedshiftConnection
|
||||
return RedshiftConnection(
|
||||
aws_access_key_id=aws_access_key_id,
|
||||
aws_secret_access_key=aws_secret_access_key,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
def connect_support(aws_access_key_id=None,
|
||||
aws_secret_access_key=None,
|
||||
**kwargs):
|
||||
"""
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: Your AWS Access Key ID
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: Your AWS Secret Access Key
|
||||
|
||||
:rtype: :class:`boto.support.layer1.SupportConnection`
|
||||
:return: A connection to Amazon's Support service
|
||||
"""
|
||||
from boto.support.layer1 import SupportConnection
|
||||
return SupportConnection(
|
||||
aws_access_key_id=aws_access_key_id,
|
||||
aws_secret_access_key=aws_secret_access_key,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
def storage_uri(uri_str, default_scheme='file', debug=0, validate=True,
|
||||
bucket_storage_uri_class=BucketStorageUri,
|
||||
suppress_consec_slashes=True, is_latest=False):
|
||||
"""
|
||||
Instantiate a StorageUri from a URI string.
|
||||
|
||||
:type uri_str: string
|
||||
:param uri_str: URI naming bucket + optional object.
|
||||
:type default_scheme: string
|
||||
:param default_scheme: default scheme for scheme-less URIs.
|
||||
:type debug: int
|
||||
:param debug: debug level to pass in to boto connection (range 0..2).
|
||||
:type validate: bool
|
||||
:param validate: whether to check for bucket name validity.
|
||||
:type bucket_storage_uri_class: BucketStorageUri interface.
|
||||
:param bucket_storage_uri_class: Allows mocking for unit tests.
|
||||
:param suppress_consec_slashes: If provided, controls whether
|
||||
consecutive slashes will be suppressed in key paths.
|
||||
:type is_latest: bool
|
||||
:param is_latest: whether this versioned object represents the
|
||||
current version.
|
||||
|
||||
We allow validate to be disabled to allow caller
|
||||
to implement bucket-level wildcarding (outside the boto library;
|
||||
see gsutil).
|
||||
|
||||
:rtype: :class:`boto.StorageUri` subclass
|
||||
:return: StorageUri subclass for given URI.
|
||||
|
||||
``uri_str`` must be one of the following formats:
|
||||
|
||||
* gs://bucket/name
|
||||
* gs://bucket/name#ver
|
||||
* s3://bucket/name
|
||||
* gs://bucket
|
||||
* s3://bucket
|
||||
* filename (which could be a Unix path like /a/b/c or a Windows path like
|
||||
C:\a\b\c)
|
||||
|
||||
The last example uses the default scheme ('file', unless overridden).
|
||||
"""
|
||||
version_id = None
|
||||
generation = None
|
||||
|
||||
# Manually parse URI components instead of using urlparse.urlparse because
|
||||
# what we're calling URIs don't really fit the standard syntax for URIs
|
||||
# (the latter includes an optional host/net location part).
|
||||
end_scheme_idx = uri_str.find('://')
|
||||
if end_scheme_idx == -1:
|
||||
scheme = default_scheme.lower()
|
||||
path = uri_str
|
||||
else:
|
||||
scheme = uri_str[0:end_scheme_idx].lower()
|
||||
path = uri_str[end_scheme_idx + 3:]
|
||||
|
||||
if scheme not in ['file', 's3', 'gs']:
|
||||
raise InvalidUriError('Unrecognized scheme "%s"' % scheme)
|
||||
if scheme == 'file':
|
||||
# For file URIs we have no bucket name, and use the complete path
|
||||
# (minus 'file://') as the object name.
|
||||
is_stream = False
|
||||
if path == '-':
|
||||
is_stream = True
|
||||
return FileStorageUri(path, debug, is_stream)
|
||||
else:
|
||||
path_parts = path.split('/', 1)
|
||||
bucket_name = path_parts[0]
|
||||
object_name = ''
|
||||
# If validate enabled, ensure the bucket name is valid, to avoid
|
||||
# possibly confusing other parts of the code. (For example if we didn't
|
||||
# catch bucket names containing ':', when a user tried to connect to
|
||||
# the server with that name they might get a confusing error about
|
||||
# non-integer port numbers.)
|
||||
if (validate and bucket_name and
|
||||
(not BUCKET_NAME_RE.match(bucket_name)
|
||||
or TOO_LONG_DNS_NAME_COMP.search(bucket_name))):
|
||||
raise InvalidUriError('Invalid bucket name in URI "%s"' % uri_str)
|
||||
if scheme == 'gs':
|
||||
match = GENERATION_RE.search(path)
|
||||
if match:
|
||||
md = match.groupdict()
|
||||
versionless_uri_str = md['versionless_uri_str']
|
||||
path_parts = versionless_uri_str.split('/', 1)
|
||||
generation = int(md['generation'])
|
||||
elif scheme == 's3':
|
||||
match = VERSION_RE.search(path)
|
||||
if match:
|
||||
md = match.groupdict()
|
||||
versionless_uri_str = md['versionless_uri_str']
|
||||
path_parts = versionless_uri_str.split('/', 1)
|
||||
version_id = md['version_id']
|
||||
else:
|
||||
raise InvalidUriError('Unrecognized scheme "%s"' % scheme)
|
||||
if len(path_parts) > 1:
|
||||
object_name = path_parts[1]
|
||||
return bucket_storage_uri_class(
|
||||
scheme, bucket_name, object_name, debug,
|
||||
suppress_consec_slashes=suppress_consec_slashes,
|
||||
version_id=version_id, generation=generation, is_latest=is_latest)
|
||||
|
||||
|
||||
def storage_uri_for_key(key):
|
||||
"""Returns a StorageUri for the given key.
|
||||
|
||||
:type key: :class:`boto.s3.key.Key` or subclass
|
||||
:param key: URI naming bucket + optional object.
|
||||
"""
|
||||
if not isinstance(key, boto.s3.key.Key):
|
||||
raise InvalidUriError('Requested key (%s) is not a subclass of '
|
||||
'boto.s3.key.Key' % str(type(key)))
|
||||
prov_name = key.bucket.connection.provider.get_provider_name()
|
||||
uri_str = '%s://%s/%s' % (prov_name, key.bucket.name, key.name)
|
||||
return storage_uri(uri_str)
|
||||
|
||||
boto.plugin.load_plugins(config)
|
|
@ -1,744 +0,0 @@
|
|||
# Copyright 2010 Google Inc.
|
||||
# Copyright (c) 2011 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2011, Eucalyptus Systems, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
"""
|
||||
Handles authentication required to AWS and GS
|
||||
"""
|
||||
|
||||
import base64
|
||||
import boto
|
||||
import boto.auth_handler
|
||||
import boto.exception
|
||||
import boto.plugin
|
||||
import boto.utils
|
||||
import copy
|
||||
import datetime
|
||||
from email.utils import formatdate
|
||||
import hmac
|
||||
import sys
|
||||
import time
|
||||
import urllib
|
||||
import posixpath
|
||||
|
||||
from boto.auth_handler import AuthHandler
|
||||
from boto.exception import BotoClientError
|
||||
#
|
||||
# the following is necessary because of the incompatibilities
|
||||
# between Python 2.4, 2.5, and 2.6 as well as the fact that some
|
||||
# people running 2.4 have installed hashlib as a separate module
|
||||
# this fix was provided by boto user mccormix.
|
||||
# see: http://code.google.com/p/boto/issues/detail?id=172
|
||||
# for more details.
|
||||
#
|
||||
try:
|
||||
from hashlib import sha1 as sha
|
||||
from hashlib import sha256 as sha256
|
||||
|
||||
if sys.version[:3] == "2.4":
|
||||
# we are using an hmac that expects a .new() method.
|
||||
class Faker:
|
||||
def __init__(self, which):
|
||||
self.which = which
|
||||
self.digest_size = self.which().digest_size
|
||||
|
||||
def new(self, *args, **kwargs):
|
||||
return self.which(*args, **kwargs)
|
||||
|
||||
sha = Faker(sha)
|
||||
sha256 = Faker(sha256)
|
||||
|
||||
except ImportError:
|
||||
import sha
|
||||
sha256 = None
|
||||
|
||||
|
||||
class HmacKeys(object):
|
||||
"""Key based Auth handler helper."""
|
||||
|
||||
def __init__(self, host, config, provider):
|
||||
if provider.access_key is None or provider.secret_key is None:
|
||||
raise boto.auth_handler.NotReadyToAuthenticate()
|
||||
self.host = host
|
||||
self.update_provider(provider)
|
||||
|
||||
def update_provider(self, provider):
|
||||
self._provider = provider
|
||||
self._hmac = hmac.new(self._provider.secret_key, digestmod=sha)
|
||||
if sha256:
|
||||
self._hmac_256 = hmac.new(self._provider.secret_key,
|
||||
digestmod=sha256)
|
||||
else:
|
||||
self._hmac_256 = None
|
||||
|
||||
def algorithm(self):
|
||||
if self._hmac_256:
|
||||
return 'HmacSHA256'
|
||||
else:
|
||||
return 'HmacSHA1'
|
||||
|
||||
def _get_hmac(self):
|
||||
if self._hmac_256:
|
||||
digestmod = sha256
|
||||
else:
|
||||
digestmod = sha
|
||||
return hmac.new(self._provider.secret_key,
|
||||
digestmod=digestmod)
|
||||
|
||||
def sign_string(self, string_to_sign):
|
||||
new_hmac = self._get_hmac()
|
||||
new_hmac.update(string_to_sign)
|
||||
return base64.encodestring(new_hmac.digest()).strip()
|
||||
|
||||
def __getstate__(self):
|
||||
pickled_dict = copy.copy(self.__dict__)
|
||||
del pickled_dict['_hmac']
|
||||
del pickled_dict['_hmac_256']
|
||||
return pickled_dict
|
||||
|
||||
def __setstate__(self, dct):
|
||||
self.__dict__ = dct
|
||||
self.update_provider(self._provider)
|
||||
|
||||
|
||||
class AnonAuthHandler(AuthHandler, HmacKeys):
|
||||
"""
|
||||
Implements Anonymous requests.
|
||||
"""
|
||||
|
||||
capability = ['anon']
|
||||
|
||||
def __init__(self, host, config, provider):
|
||||
AuthHandler.__init__(self, host, config, provider)
|
||||
|
||||
def add_auth(self, http_request, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class HmacAuthV1Handler(AuthHandler, HmacKeys):
|
||||
""" Implements the HMAC request signing used by S3 and GS."""
|
||||
|
||||
capability = ['hmac-v1', 's3']
|
||||
|
||||
def __init__(self, host, config, provider):
|
||||
AuthHandler.__init__(self, host, config, provider)
|
||||
HmacKeys.__init__(self, host, config, provider)
|
||||
self._hmac_256 = None
|
||||
|
||||
def update_provider(self, provider):
|
||||
super(HmacAuthV1Handler, self).update_provider(provider)
|
||||
self._hmac_256 = None
|
||||
|
||||
def add_auth(self, http_request, **kwargs):
|
||||
headers = http_request.headers
|
||||
method = http_request.method
|
||||
auth_path = http_request.auth_path
|
||||
if 'Date' not in headers:
|
||||
headers['Date'] = formatdate(usegmt=True)
|
||||
|
||||
if self._provider.security_token:
|
||||
key = self._provider.security_token_header
|
||||
headers[key] = self._provider.security_token
|
||||
string_to_sign = boto.utils.canonical_string(method, auth_path,
|
||||
headers, None,
|
||||
self._provider)
|
||||
boto.log.debug('StringToSign:\n%s' % string_to_sign)
|
||||
b64_hmac = self.sign_string(string_to_sign)
|
||||
auth_hdr = self._provider.auth_header
|
||||
auth = ("%s %s:%s" % (auth_hdr, self._provider.access_key, b64_hmac))
|
||||
boto.log.debug('Signature:\n%s' % auth)
|
||||
headers['Authorization'] = auth
|
||||
|
||||
|
||||
class HmacAuthV2Handler(AuthHandler, HmacKeys):
|
||||
"""
|
||||
Implements the simplified HMAC authorization used by CloudFront.
|
||||
"""
|
||||
capability = ['hmac-v2', 'cloudfront']
|
||||
|
||||
def __init__(self, host, config, provider):
|
||||
AuthHandler.__init__(self, host, config, provider)
|
||||
HmacKeys.__init__(self, host, config, provider)
|
||||
self._hmac_256 = None
|
||||
|
||||
def update_provider(self, provider):
|
||||
super(HmacAuthV2Handler, self).update_provider(provider)
|
||||
self._hmac_256 = None
|
||||
|
||||
def add_auth(self, http_request, **kwargs):
|
||||
headers = http_request.headers
|
||||
if 'Date' not in headers:
|
||||
headers['Date'] = formatdate(usegmt=True)
|
||||
if self._provider.security_token:
|
||||
key = self._provider.security_token_header
|
||||
headers[key] = self._provider.security_token
|
||||
|
||||
b64_hmac = self.sign_string(headers['Date'])
|
||||
auth_hdr = self._provider.auth_header
|
||||
headers['Authorization'] = ("%s %s:%s" %
|
||||
(auth_hdr,
|
||||
self._provider.access_key, b64_hmac))
|
||||
|
||||
|
||||
class HmacAuthV3Handler(AuthHandler, HmacKeys):
|
||||
"""Implements the new Version 3 HMAC authorization used by Route53."""
|
||||
|
||||
capability = ['hmac-v3', 'route53', 'ses']
|
||||
|
||||
def __init__(self, host, config, provider):
|
||||
AuthHandler.__init__(self, host, config, provider)
|
||||
HmacKeys.__init__(self, host, config, provider)
|
||||
|
||||
def add_auth(self, http_request, **kwargs):
|
||||
headers = http_request.headers
|
||||
if 'Date' not in headers:
|
||||
headers['Date'] = formatdate(usegmt=True)
|
||||
|
||||
if self._provider.security_token:
|
||||
key = self._provider.security_token_header
|
||||
headers[key] = self._provider.security_token
|
||||
|
||||
b64_hmac = self.sign_string(headers['Date'])
|
||||
s = "AWS3-HTTPS AWSAccessKeyId=%s," % self._provider.access_key
|
||||
s += "Algorithm=%s,Signature=%s" % (self.algorithm(), b64_hmac)
|
||||
headers['X-Amzn-Authorization'] = s
|
||||
|
||||
|
||||
class HmacAuthV3HTTPHandler(AuthHandler, HmacKeys):
|
||||
"""
|
||||
Implements the new Version 3 HMAC authorization used by DynamoDB.
|
||||
"""
|
||||
|
||||
capability = ['hmac-v3-http']
|
||||
|
||||
def __init__(self, host, config, provider):
|
||||
AuthHandler.__init__(self, host, config, provider)
|
||||
HmacKeys.__init__(self, host, config, provider)
|
||||
|
||||
def headers_to_sign(self, http_request):
|
||||
"""
|
||||
Select the headers from the request that need to be included
|
||||
in the StringToSign.
|
||||
"""
|
||||
headers_to_sign = {}
|
||||
headers_to_sign = {'Host': self.host}
|
||||
for name, value in http_request.headers.items():
|
||||
lname = name.lower()
|
||||
if lname.startswith('x-amz'):
|
||||
headers_to_sign[name] = value
|
||||
return headers_to_sign
|
||||
|
||||
def canonical_headers(self, headers_to_sign):
|
||||
"""
|
||||
Return the headers that need to be included in the StringToSign
|
||||
in their canonical form by converting all header keys to lower
|
||||
case, sorting them in alphabetical order and then joining
|
||||
them into a string, separated by newlines.
|
||||
"""
|
||||
l = sorted(['%s:%s' % (n.lower().strip(),
|
||||
headers_to_sign[n].strip()) for n in headers_to_sign])
|
||||
return '\n'.join(l)
|
||||
|
||||
def string_to_sign(self, http_request):
|
||||
"""
|
||||
Return the canonical StringToSign as well as a dict
|
||||
containing the original version of all headers that
|
||||
were included in the StringToSign.
|
||||
"""
|
||||
headers_to_sign = self.headers_to_sign(http_request)
|
||||
canonical_headers = self.canonical_headers(headers_to_sign)
|
||||
string_to_sign = '\n'.join([http_request.method,
|
||||
http_request.auth_path,
|
||||
'',
|
||||
canonical_headers,
|
||||
'',
|
||||
http_request.body])
|
||||
return string_to_sign, headers_to_sign
|
||||
|
||||
def add_auth(self, req, **kwargs):
|
||||
"""
|
||||
Add AWS3 authentication to a request.
|
||||
|
||||
:type req: :class`boto.connection.HTTPRequest`
|
||||
:param req: The HTTPRequest object.
|
||||
"""
|
||||
# This could be a retry. Make sure the previous
|
||||
# authorization header is removed first.
|
||||
if 'X-Amzn-Authorization' in req.headers:
|
||||
del req.headers['X-Amzn-Authorization']
|
||||
req.headers['X-Amz-Date'] = formatdate(usegmt=True)
|
||||
if self._provider.security_token:
|
||||
req.headers['X-Amz-Security-Token'] = self._provider.security_token
|
||||
string_to_sign, headers_to_sign = self.string_to_sign(req)
|
||||
boto.log.debug('StringToSign:\n%s' % string_to_sign)
|
||||
hash_value = sha256(string_to_sign).digest()
|
||||
b64_hmac = self.sign_string(hash_value)
|
||||
s = "AWS3 AWSAccessKeyId=%s," % self._provider.access_key
|
||||
s += "Algorithm=%s," % self.algorithm()
|
||||
s += "SignedHeaders=%s," % ';'.join(headers_to_sign)
|
||||
s += "Signature=%s" % b64_hmac
|
||||
req.headers['X-Amzn-Authorization'] = s
|
||||
|
||||
|
||||
class HmacAuthV4Handler(AuthHandler, HmacKeys):
|
||||
"""
|
||||
Implements the new Version 4 HMAC authorization.
|
||||
"""
|
||||
|
||||
capability = ['hmac-v4']
|
||||
|
||||
def __init__(self, host, config, provider,
|
||||
service_name=None, region_name=None):
|
||||
AuthHandler.__init__(self, host, config, provider)
|
||||
HmacKeys.__init__(self, host, config, provider)
|
||||
# You can set the service_name and region_name to override the
|
||||
# values which would otherwise come from the endpoint, e.g.
|
||||
# <service>.<region>.amazonaws.com.
|
||||
self.service_name = service_name
|
||||
self.region_name = region_name
|
||||
|
||||
def _sign(self, key, msg, hex=False):
|
||||
if hex:
|
||||
sig = hmac.new(key, msg.encode('utf-8'), sha256).hexdigest()
|
||||
else:
|
||||
sig = hmac.new(key, msg.encode('utf-8'), sha256).digest()
|
||||
return sig
|
||||
|
||||
def headers_to_sign(self, http_request):
|
||||
"""
|
||||
Select the headers from the request that need to be included
|
||||
in the StringToSign.
|
||||
"""
|
||||
host_header_value = self.host_header(self.host, http_request)
|
||||
headers_to_sign = {}
|
||||
headers_to_sign = {'Host': host_header_value}
|
||||
for name, value in http_request.headers.items():
|
||||
lname = name.lower()
|
||||
if lname.startswith('x-amz'):
|
||||
headers_to_sign[name] = value
|
||||
return headers_to_sign
|
||||
|
||||
def host_header(self, host, http_request):
|
||||
port = http_request.port
|
||||
secure = http_request.protocol == 'https'
|
||||
if ((port == 80 and not secure) or (port == 443 and secure)):
|
||||
return host
|
||||
return '%s:%s' % (host, port)
|
||||
|
||||
def query_string(self, http_request):
|
||||
parameter_names = sorted(http_request.params.keys())
|
||||
pairs = []
|
||||
for pname in parameter_names:
|
||||
pval = str(http_request.params[pname]).encode('utf-8')
|
||||
pairs.append(urllib.quote(pname, safe='') + '=' +
|
||||
urllib.quote(pval, safe='-_~'))
|
||||
return '&'.join(pairs)
|
||||
|
||||
def canonical_query_string(self, http_request):
|
||||
# POST requests pass parameters in through the
|
||||
# http_request.body field.
|
||||
if http_request.method == 'POST':
|
||||
return ""
|
||||
l = []
|
||||
for param in sorted(http_request.params):
|
||||
value = str(http_request.params[param])
|
||||
l.append('%s=%s' % (urllib.quote(param, safe='-_.~'),
|
||||
urllib.quote(value, safe='-_.~')))
|
||||
return '&'.join(l)
|
||||
|
||||
def canonical_headers(self, headers_to_sign):
|
||||
"""
|
||||
Return the headers that need to be included in the StringToSign
|
||||
in their canonical form by converting all header keys to lower
|
||||
case, sorting them in alphabetical order and then joining
|
||||
them into a string, separated by newlines.
|
||||
"""
|
||||
l = sorted(['%s:%s' % (n.lower().strip(),
|
||||
' '.join(headers_to_sign[n].strip().split()))
|
||||
for n in headers_to_sign])
|
||||
return '\n'.join(l)
|
||||
|
||||
def signed_headers(self, headers_to_sign):
|
||||
l = ['%s' % n.lower().strip() for n in headers_to_sign]
|
||||
l = sorted(l)
|
||||
return ';'.join(l)
|
||||
|
||||
def canonical_uri(self, http_request):
|
||||
path = http_request.auth_path
|
||||
# Normalize the path
|
||||
# in windows normpath('/') will be '\\' so we chane it back to '/'
|
||||
normalized = posixpath.normpath(path).replace('\\','/')
|
||||
# Then urlencode whatever's left.
|
||||
encoded = urllib.quote(normalized)
|
||||
if len(path) > 1 and path.endswith('/'):
|
||||
encoded += '/'
|
||||
return encoded
|
||||
|
||||
def payload(self, http_request):
|
||||
body = http_request.body
|
||||
# If the body is a file like object, we can use
|
||||
# boto.utils.compute_hash, which will avoid reading
|
||||
# the entire body into memory.
|
||||
if hasattr(body, 'seek') and hasattr(body, 'read'):
|
||||
return boto.utils.compute_hash(body, hash_algorithm=sha256)[0]
|
||||
return sha256(http_request.body).hexdigest()
|
||||
|
||||
def canonical_request(self, http_request):
|
||||
cr = [http_request.method.upper()]
|
||||
cr.append(self.canonical_uri(http_request))
|
||||
cr.append(self.canonical_query_string(http_request))
|
||||
headers_to_sign = self.headers_to_sign(http_request)
|
||||
cr.append(self.canonical_headers(headers_to_sign) + '\n')
|
||||
cr.append(self.signed_headers(headers_to_sign))
|
||||
cr.append(self.payload(http_request))
|
||||
return '\n'.join(cr)
|
||||
|
||||
def scope(self, http_request):
|
||||
scope = [self._provider.access_key]
|
||||
scope.append(http_request.timestamp)
|
||||
scope.append(http_request.region_name)
|
||||
scope.append(http_request.service_name)
|
||||
scope.append('aws4_request')
|
||||
return '/'.join(scope)
|
||||
|
||||
def credential_scope(self, http_request):
|
||||
scope = []
|
||||
http_request.timestamp = http_request.headers['X-Amz-Date'][0:8]
|
||||
scope.append(http_request.timestamp)
|
||||
# The service_name and region_name either come from:
|
||||
# * The service_name/region_name attrs or (if these values are None)
|
||||
# * parsed from the endpoint <service>.<region>.amazonaws.com.
|
||||
parts = http_request.host.split('.')
|
||||
if self.region_name is not None:
|
||||
region_name = self.region_name
|
||||
elif len(parts) > 1:
|
||||
if parts[1] == 'us-gov':
|
||||
region_name = 'us-gov-west-1'
|
||||
else:
|
||||
if len(parts) == 3:
|
||||
region_name = 'us-east-1'
|
||||
else:
|
||||
region_name = parts[1]
|
||||
else:
|
||||
region_name = parts[0]
|
||||
|
||||
if self.service_name is not None:
|
||||
service_name = self.service_name
|
||||
else:
|
||||
service_name = parts[0]
|
||||
|
||||
http_request.service_name = service_name
|
||||
http_request.region_name = region_name
|
||||
|
||||
scope.append(http_request.region_name)
|
||||
scope.append(http_request.service_name)
|
||||
scope.append('aws4_request')
|
||||
return '/'.join(scope)
|
||||
|
||||
def string_to_sign(self, http_request, canonical_request):
|
||||
"""
|
||||
Return the canonical StringToSign as well as a dict
|
||||
containing the original version of all headers that
|
||||
were included in the StringToSign.
|
||||
"""
|
||||
sts = ['AWS4-HMAC-SHA256']
|
||||
sts.append(http_request.headers['X-Amz-Date'])
|
||||
sts.append(self.credential_scope(http_request))
|
||||
sts.append(sha256(canonical_request).hexdigest())
|
||||
return '\n'.join(sts)
|
||||
|
||||
def signature(self, http_request, string_to_sign):
|
||||
key = self._provider.secret_key
|
||||
k_date = self._sign(('AWS4' + key).encode('utf-8'),
|
||||
http_request.timestamp)
|
||||
k_region = self._sign(k_date, http_request.region_name)
|
||||
k_service = self._sign(k_region, http_request.service_name)
|
||||
k_signing = self._sign(k_service, 'aws4_request')
|
||||
return self._sign(k_signing, string_to_sign, hex=True)
|
||||
|
||||
def add_auth(self, req, **kwargs):
|
||||
"""
|
||||
Add AWS4 authentication to a request.
|
||||
|
||||
:type req: :class`boto.connection.HTTPRequest`
|
||||
:param req: The HTTPRequest object.
|
||||
"""
|
||||
# This could be a retry. Make sure the previous
|
||||
# authorization header is removed first.
|
||||
if 'X-Amzn-Authorization' in req.headers:
|
||||
del req.headers['X-Amzn-Authorization']
|
||||
now = datetime.datetime.utcnow()
|
||||
req.headers['X-Amz-Date'] = now.strftime('%Y%m%dT%H%M%SZ')
|
||||
if self._provider.security_token:
|
||||
req.headers['X-Amz-Security-Token'] = self._provider.security_token
|
||||
qs = self.query_string(req)
|
||||
if qs and req.method == 'POST':
|
||||
# Stash request parameters into post body
|
||||
# before we generate the signature.
|
||||
req.body = qs
|
||||
req.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
req.headers['Content-Length'] = str(len(req.body))
|
||||
else:
|
||||
# Safe to modify req.path here since
|
||||
# the signature will use req.auth_path.
|
||||
req.path = req.path.split('?')[0]
|
||||
req.path = req.path + '?' + qs
|
||||
canonical_request = self.canonical_request(req)
|
||||
boto.log.debug('CanonicalRequest:\n%s' % canonical_request)
|
||||
string_to_sign = self.string_to_sign(req, canonical_request)
|
||||
boto.log.debug('StringToSign:\n%s' % string_to_sign)
|
||||
signature = self.signature(req, string_to_sign)
|
||||
boto.log.debug('Signature:\n%s' % signature)
|
||||
headers_to_sign = self.headers_to_sign(req)
|
||||
l = ['AWS4-HMAC-SHA256 Credential=%s' % self.scope(req)]
|
||||
l.append('SignedHeaders=%s' % self.signed_headers(headers_to_sign))
|
||||
l.append('Signature=%s' % signature)
|
||||
req.headers['Authorization'] = ','.join(l)
|
||||
|
||||
|
||||
class QueryAuthHandler(AuthHandler):
|
||||
"""
|
||||
Provides pure query construction (no actual signing).
|
||||
|
||||
Mostly useful for STS' ``assume_role_with_web_identity``.
|
||||
|
||||
Does **NOT** escape query string values!
|
||||
"""
|
||||
|
||||
capability = ['pure-query']
|
||||
|
||||
def _escape_value(self, value):
|
||||
# Would normally be ``return urllib.quote(value)``.
|
||||
return value
|
||||
|
||||
def _build_query_string(self, params):
|
||||
keys = params.keys()
|
||||
keys.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
|
||||
pairs = []
|
||||
for key in keys:
|
||||
val = boto.utils.get_utf8_value(params[key])
|
||||
pairs.append(key + '=' + self._escape_value(val))
|
||||
return '&'.join(pairs)
|
||||
|
||||
def add_auth(self, http_request, **kwargs):
|
||||
headers = http_request.headers
|
||||
params = http_request.params
|
||||
qs = self._build_query_string(
|
||||
http_request.params
|
||||
)
|
||||
boto.log.debug('query_string: %s' % qs)
|
||||
headers['Content-Type'] = 'application/json; charset=UTF-8'
|
||||
http_request.body = ''
|
||||
# if this is a retried request, the qs from the previous try will
|
||||
# already be there, we need to get rid of that and rebuild it
|
||||
http_request.path = http_request.path.split('?')[0]
|
||||
http_request.path = http_request.path + '?' + qs
|
||||
|
||||
|
||||
class QuerySignatureHelper(HmacKeys):
|
||||
"""
|
||||
Helper for Query signature based Auth handler.
|
||||
|
||||
Concrete sub class need to implement _calc_sigature method.
|
||||
"""
|
||||
|
||||
def add_auth(self, http_request, **kwargs):
|
||||
headers = http_request.headers
|
||||
params = http_request.params
|
||||
params['AWSAccessKeyId'] = self._provider.access_key
|
||||
params['SignatureVersion'] = self.SignatureVersion
|
||||
params['Timestamp'] = boto.utils.get_ts()
|
||||
qs, signature = self._calc_signature(
|
||||
http_request.params, http_request.method,
|
||||
http_request.auth_path, http_request.host)
|
||||
boto.log.debug('query_string: %s Signature: %s' % (qs, signature))
|
||||
if http_request.method == 'POST':
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
http_request.body = qs + '&Signature=' + urllib.quote_plus(signature)
|
||||
http_request.headers['Content-Length'] = str(len(http_request.body))
|
||||
else:
|
||||
http_request.body = ''
|
||||
# if this is a retried request, the qs from the previous try will
|
||||
# already be there, we need to get rid of that and rebuild it
|
||||
http_request.path = http_request.path.split('?')[0]
|
||||
http_request.path = (http_request.path + '?' + qs +
|
||||
'&Signature=' + urllib.quote_plus(signature))
|
||||
|
||||
|
||||
class QuerySignatureV0AuthHandler(QuerySignatureHelper, AuthHandler):
|
||||
"""Provides Signature V0 Signing"""
|
||||
|
||||
SignatureVersion = 0
|
||||
capability = ['sign-v0']
|
||||
|
||||
def _calc_signature(self, params, *args):
|
||||
boto.log.debug('using _calc_signature_0')
|
||||
hmac = self._get_hmac()
|
||||
s = params['Action'] + params['Timestamp']
|
||||
hmac.update(s)
|
||||
keys = params.keys()
|
||||
keys.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
|
||||
pairs = []
|
||||
for key in keys:
|
||||
val = boto.utils.get_utf8_value(params[key])
|
||||
pairs.append(key + '=' + urllib.quote(val))
|
||||
qs = '&'.join(pairs)
|
||||
return (qs, base64.b64encode(hmac.digest()))
|
||||
|
||||
|
||||
class QuerySignatureV1AuthHandler(QuerySignatureHelper, AuthHandler):
|
||||
"""
|
||||
Provides Query Signature V1 Authentication.
|
||||
"""
|
||||
|
||||
SignatureVersion = 1
|
||||
capability = ['sign-v1', 'mturk']
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
QuerySignatureHelper.__init__(self, *args, **kw)
|
||||
AuthHandler.__init__(self, *args, **kw)
|
||||
self._hmac_256 = None
|
||||
|
||||
def _calc_signature(self, params, *args):
|
||||
boto.log.debug('using _calc_signature_1')
|
||||
hmac = self._get_hmac()
|
||||
keys = params.keys()
|
||||
keys.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
|
||||
pairs = []
|
||||
for key in keys:
|
||||
hmac.update(key)
|
||||
val = boto.utils.get_utf8_value(params[key])
|
||||
hmac.update(val)
|
||||
pairs.append(key + '=' + urllib.quote(val))
|
||||
qs = '&'.join(pairs)
|
||||
return (qs, base64.b64encode(hmac.digest()))
|
||||
|
||||
|
||||
class QuerySignatureV2AuthHandler(QuerySignatureHelper, AuthHandler):
|
||||
"""Provides Query Signature V2 Authentication."""
|
||||
|
||||
SignatureVersion = 2
|
||||
capability = ['sign-v2', 'ec2', 'ec2', 'emr', 'fps', 'ecs',
|
||||
'sdb', 'iam', 'rds', 'sns', 'sqs', 'cloudformation']
|
||||
|
||||
def _calc_signature(self, params, verb, path, server_name):
|
||||
boto.log.debug('using _calc_signature_2')
|
||||
string_to_sign = '%s\n%s\n%s\n' % (verb, server_name.lower(), path)
|
||||
hmac = self._get_hmac()
|
||||
params['SignatureMethod'] = self.algorithm()
|
||||
if self._provider.security_token:
|
||||
params['SecurityToken'] = self._provider.security_token
|
||||
keys = sorted(params.keys())
|
||||
pairs = []
|
||||
for key in keys:
|
||||
val = boto.utils.get_utf8_value(params[key])
|
||||
pairs.append(urllib.quote(key, safe='') + '=' +
|
||||
urllib.quote(val, safe='-_~'))
|
||||
qs = '&'.join(pairs)
|
||||
boto.log.debug('query string: %s' % qs)
|
||||
string_to_sign += qs
|
||||
boto.log.debug('string_to_sign: %s' % string_to_sign)
|
||||
hmac.update(string_to_sign)
|
||||
b64 = base64.b64encode(hmac.digest())
|
||||
boto.log.debug('len(b64)=%d' % len(b64))
|
||||
boto.log.debug('base64 encoded digest: %s' % b64)
|
||||
return (qs, b64)
|
||||
|
||||
|
||||
class POSTPathQSV2AuthHandler(QuerySignatureV2AuthHandler, AuthHandler):
|
||||
"""
|
||||
Query Signature V2 Authentication relocating signed query
|
||||
into the path and allowing POST requests with Content-Types.
|
||||
"""
|
||||
|
||||
capability = ['mws']
|
||||
|
||||
def add_auth(self, req, **kwargs):
|
||||
req.params['AWSAccessKeyId'] = self._provider.access_key
|
||||
req.params['SignatureVersion'] = self.SignatureVersion
|
||||
req.params['Timestamp'] = boto.utils.get_ts()
|
||||
qs, signature = self._calc_signature(req.params, req.method,
|
||||
req.auth_path, req.host)
|
||||
boto.log.debug('query_string: %s Signature: %s' % (qs, signature))
|
||||
if req.method == 'POST':
|
||||
req.headers['Content-Length'] = str(len(req.body))
|
||||
req.headers['Content-Type'] = req.headers.get('Content-Type',
|
||||
'text/plain')
|
||||
else:
|
||||
req.body = ''
|
||||
# if this is a retried req, the qs from the previous try will
|
||||
# already be there, we need to get rid of that and rebuild it
|
||||
req.path = req.path.split('?')[0]
|
||||
req.path = (req.path + '?' + qs +
|
||||
'&Signature=' + urllib.quote_plus(signature))
|
||||
|
||||
|
||||
def get_auth_handler(host, config, provider, requested_capability=None):
|
||||
"""Finds an AuthHandler that is ready to authenticate.
|
||||
|
||||
Lists through all the registered AuthHandlers to find one that is willing
|
||||
to handle for the requested capabilities, config and provider.
|
||||
|
||||
:type host: string
|
||||
:param host: The name of the host
|
||||
|
||||
:type config:
|
||||
:param config:
|
||||
|
||||
:type provider:
|
||||
:param provider:
|
||||
|
||||
Returns:
|
||||
An implementation of AuthHandler.
|
||||
|
||||
Raises:
|
||||
boto.exception.NoAuthHandlerFound
|
||||
"""
|
||||
ready_handlers = []
|
||||
auth_handlers = boto.plugin.get_plugin(AuthHandler, requested_capability)
|
||||
total_handlers = len(auth_handlers)
|
||||
for handler in auth_handlers:
|
||||
try:
|
||||
ready_handlers.append(handler(host, config, provider))
|
||||
except boto.auth_handler.NotReadyToAuthenticate:
|
||||
pass
|
||||
|
||||
if not ready_handlers:
|
||||
checked_handlers = auth_handlers
|
||||
names = [handler.__name__ for handler in checked_handlers]
|
||||
raise boto.exception.NoAuthHandlerFound(
|
||||
'No handler was ready to authenticate. %d handlers were checked.'
|
||||
' %s '
|
||||
'Check your credentials' % (len(names), str(names)))
|
||||
|
||||
# We select the last ready auth handler that was loaded, to allow users to
|
||||
# customize how auth works in environments where there are shared boto
|
||||
# config files (e.g., /etc/boto.cfg and ~/.boto): The more general,
|
||||
# system-wide shared configs should be loaded first, and the user's
|
||||
# customizations loaded last. That way, for example, the system-wide
|
||||
# config might include a plugin_directory that includes a service account
|
||||
# auth plugin shared by all users of a Google Compute Engine instance
|
||||
# (allowing sharing of non-user data between various services), and the
|
||||
# user could override this with a .boto config that includes user-specific
|
||||
# credentials (for access to user data).
|
||||
return ready_handlers[-1]
|
|
@ -1,58 +0,0 @@
|
|||
# Copyright 2010 Google Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
"""
|
||||
Defines an interface which all Auth handlers need to implement.
|
||||
"""
|
||||
|
||||
from plugin import Plugin
|
||||
|
||||
class NotReadyToAuthenticate(Exception):
|
||||
pass
|
||||
|
||||
class AuthHandler(Plugin):
|
||||
|
||||
capability = []
|
||||
|
||||
def __init__(self, host, config, provider):
|
||||
"""Constructs the handlers.
|
||||
:type host: string
|
||||
:param host: The host to which the request is being sent.
|
||||
|
||||
:type config: boto.pyami.Config
|
||||
:param config: Boto configuration.
|
||||
|
||||
:type provider: boto.provider.Provider
|
||||
:param provider: Provider details.
|
||||
|
||||
Raises:
|
||||
NotReadyToAuthenticate: if this handler is not willing to
|
||||
authenticate for the given provider and config.
|
||||
"""
|
||||
pass
|
||||
|
||||
def add_auth(self, http_request):
|
||||
"""Invoked to add authentication details to request.
|
||||
|
||||
:type http_request: boto.connection.HTTPRequest
|
||||
:param http_request: HTTP request that needs to be authenticated.
|
||||
"""
|
||||
pass
|
|
@ -1,65 +0,0 @@
|
|||
# Copyright (c) 2013 Amazon.com, Inc. or its affiliates.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
from boto.regioninfo import RegionInfo
|
||||
|
||||
|
||||
def regions():
|
||||
"""
|
||||
Get all available regions for the AWS Elastic Beanstalk service.
|
||||
|
||||
:rtype: list
|
||||
:return: A list of :class:`boto.regioninfo.RegionInfo`
|
||||
"""
|
||||
import boto.beanstalk.layer1
|
||||
return [RegionInfo(name='us-east-1',
|
||||
endpoint='elasticbeanstalk.us-east-1.amazonaws.com',
|
||||
connection_cls=boto.beanstalk.layer1.Layer1),
|
||||
RegionInfo(name='us-west-1',
|
||||
endpoint='elasticbeanstalk.us-west-1.amazonaws.com',
|
||||
connection_cls=boto.beanstalk.layer1.Layer1),
|
||||
RegionInfo(name='us-west-2',
|
||||
endpoint='elasticbeanstalk.us-west-2.amazonaws.com',
|
||||
connection_cls=boto.beanstalk.layer1.Layer1),
|
||||
RegionInfo(name='ap-northeast-1',
|
||||
endpoint='elasticbeanstalk.ap-northeast-1.amazonaws.com',
|
||||
connection_cls=boto.beanstalk.layer1.Layer1),
|
||||
RegionInfo(name='ap-southeast-1',
|
||||
endpoint='elasticbeanstalk.ap-southeast-1.amazonaws.com',
|
||||
connection_cls=boto.beanstalk.layer1.Layer1),
|
||||
RegionInfo(name='ap-southeast-2',
|
||||
endpoint='elasticbeanstalk.ap-southeast-2.amazonaws.com',
|
||||
connection_cls=boto.beanstalk.layer1.Layer1),
|
||||
RegionInfo(name='eu-west-1',
|
||||
endpoint='elasticbeanstalk.eu-west-1.amazonaws.com',
|
||||
connection_cls=boto.beanstalk.layer1.Layer1),
|
||||
RegionInfo(name='sa-east-1',
|
||||
endpoint='elasticbeanstalk.sa-east-1.amazonaws.com',
|
||||
connection_cls=boto.beanstalk.layer1.Layer1),
|
||||
]
|
||||
|
||||
|
||||
def connect_to_region(region_name, **kw_params):
|
||||
for region in regions():
|
||||
if region.name == region_name:
|
||||
return region.connect(**kw_params)
|
||||
return None
|
|
@ -1,64 +0,0 @@
|
|||
import sys
|
||||
from boto.compat import json
|
||||
from boto.exception import BotoServerError
|
||||
|
||||
|
||||
def simple(e):
|
||||
err = json.loads(e.error_message)
|
||||
code = err['Error']['Code']
|
||||
|
||||
try:
|
||||
# Dynamically get the error class.
|
||||
simple_e = getattr(sys.modules[__name__], code)(e, err)
|
||||
except AttributeError:
|
||||
# Return original exception on failure.
|
||||
return e
|
||||
|
||||
return simple_e
|
||||
|
||||
|
||||
class SimpleException(BotoServerError):
|
||||
def __init__(self, e, err):
|
||||
super(SimpleException, self).__init__(e.status, e.reason, e.body)
|
||||
self.body = e.error_message
|
||||
self.request_id = err['RequestId']
|
||||
self.error_code = err['Error']['Code']
|
||||
self.error_message = err['Error']['Message']
|
||||
|
||||
def __repr__(self):
|
||||
return self.__class__.__name__ + ': ' + self.error_message
|
||||
def __str__(self):
|
||||
return self.__class__.__name__ + ': ' + self.error_message
|
||||
|
||||
|
||||
class ValidationError(SimpleException): pass
|
||||
|
||||
# Common beanstalk exceptions.
|
||||
class IncompleteSignature(SimpleException): pass
|
||||
class InternalFailure(SimpleException): pass
|
||||
class InvalidAction(SimpleException): pass
|
||||
class InvalidClientTokenId(SimpleException): pass
|
||||
class InvalidParameterCombination(SimpleException): pass
|
||||
class InvalidParameterValue(SimpleException): pass
|
||||
class InvalidQueryParameter(SimpleException): pass
|
||||
class MalformedQueryString(SimpleException): pass
|
||||
class MissingAction(SimpleException): pass
|
||||
class MissingAuthenticationToken(SimpleException): pass
|
||||
class MissingParameter(SimpleException): pass
|
||||
class OptInRequired(SimpleException): pass
|
||||
class RequestExpired(SimpleException): pass
|
||||
class ServiceUnavailable(SimpleException): pass
|
||||
class Throttling(SimpleException): pass
|
||||
|
||||
|
||||
# Action specific exceptions.
|
||||
class TooManyApplications(SimpleException): pass
|
||||
class InsufficientPrivileges(SimpleException): pass
|
||||
class S3LocationNotInServiceRegion(SimpleException): pass
|
||||
class TooManyApplicationVersions(SimpleException): pass
|
||||
class TooManyConfigurationTemplates(SimpleException): pass
|
||||
class TooManyEnvironments(SimpleException): pass
|
||||
class S3SubscriptionRequired(SimpleException): pass
|
||||
class TooManyBuckets(SimpleException): pass
|
||||
class OperationInProgress(SimpleException): pass
|
||||
class SourceBundleDeletion(SimpleException): pass
|
File diff suppressed because it is too large
Load Diff
|
@ -1,703 +0,0 @@
|
|||
"""Classify responses from layer1 and strict type values."""
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class BaseObject(object):
|
||||
|
||||
def __repr__(self):
|
||||
result = self.__class__.__name__ + '{ '
|
||||
counter = 0
|
||||
for key, value in self.__dict__.iteritems():
|
||||
# first iteration no comma
|
||||
counter += 1
|
||||
if counter > 1:
|
||||
result += ', '
|
||||
result += key + ': '
|
||||
result += self._repr_by_type(value)
|
||||
result += ' }'
|
||||
return result
|
||||
|
||||
def _repr_by_type(self, value):
|
||||
# Everything is either a 'Response', 'list', or 'None/str/int/bool'.
|
||||
result = ''
|
||||
if isinstance(value, Response):
|
||||
result += value.__repr__()
|
||||
elif isinstance(value, list):
|
||||
result += self._repr_list(value)
|
||||
else:
|
||||
result += str(value)
|
||||
return result
|
||||
|
||||
def _repr_list(self, array):
|
||||
result = '['
|
||||
for value in array:
|
||||
result += ' ' + self._repr_by_type(value) + ','
|
||||
# Check for trailing comma with a space.
|
||||
if len(result) > 1:
|
||||
result = result[:-1] + ' '
|
||||
result += ']'
|
||||
return result
|
||||
|
||||
|
||||
class Response(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(Response, self).__init__()
|
||||
|
||||
if response['ResponseMetadata']:
|
||||
self.response_metadata = ResponseMetadata(response['ResponseMetadata'])
|
||||
else:
|
||||
self.response_metadata = None
|
||||
|
||||
|
||||
class ResponseMetadata(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(ResponseMetadata, self).__init__()
|
||||
|
||||
self.request_id = str(response['RequestId'])
|
||||
|
||||
|
||||
class ApplicationDescription(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(ApplicationDescription, self).__init__()
|
||||
|
||||
self.application_name = str(response['ApplicationName'])
|
||||
self.configuration_templates = []
|
||||
if response['ConfigurationTemplates']:
|
||||
for member in response['ConfigurationTemplates']:
|
||||
configuration_template = str(member)
|
||||
self.configuration_templates.append(configuration_template)
|
||||
self.date_created = datetime.fromtimestamp(response['DateCreated'])
|
||||
self.date_updated = datetime.fromtimestamp(response['DateUpdated'])
|
||||
self.description = str(response['Description'])
|
||||
self.versions = []
|
||||
if response['Versions']:
|
||||
for member in response['Versions']:
|
||||
version = str(member)
|
||||
self.versions.append(version)
|
||||
|
||||
|
||||
class ApplicationVersionDescription(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(ApplicationVersionDescription, self).__init__()
|
||||
|
||||
self.application_name = str(response['ApplicationName'])
|
||||
self.date_created = datetime.fromtimestamp(response['DateCreated'])
|
||||
self.date_updated = datetime.fromtimestamp(response['DateUpdated'])
|
||||
self.description = str(response['Description'])
|
||||
if response['SourceBundle']:
|
||||
self.source_bundle = S3Location(response['SourceBundle'])
|
||||
else:
|
||||
self.source_bundle = None
|
||||
self.version_label = str(response['VersionLabel'])
|
||||
|
||||
|
||||
class AutoScalingGroup(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(AutoScalingGroup, self).__init__()
|
||||
|
||||
self.name = str(response['Name'])
|
||||
|
||||
|
||||
class ConfigurationOptionDescription(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(ConfigurationOptionDescription, self).__init__()
|
||||
|
||||
self.change_severity = str(response['ChangeSeverity'])
|
||||
self.default_value = str(response['DefaultValue'])
|
||||
self.max_length = int(response['MaxLength']) if response['MaxLength'] else None
|
||||
self.max_value = int(response['MaxValue']) if response['MaxValue'] else None
|
||||
self.min_value = int(response['MinValue']) if response['MinValue'] else None
|
||||
self.name = str(response['Name'])
|
||||
self.namespace = str(response['Namespace'])
|
||||
if response['Regex']:
|
||||
self.regex = OptionRestrictionRegex(response['Regex'])
|
||||
else:
|
||||
self.regex = None
|
||||
self.user_defined = str(response['UserDefined'])
|
||||
self.value_options = []
|
||||
if response['ValueOptions']:
|
||||
for member in response['ValueOptions']:
|
||||
value_option = str(member)
|
||||
self.value_options.append(value_option)
|
||||
self.value_type = str(response['ValueType'])
|
||||
|
||||
|
||||
class ConfigurationOptionSetting(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(ConfigurationOptionSetting, self).__init__()
|
||||
|
||||
self.namespace = str(response['Namespace'])
|
||||
self.option_name = str(response['OptionName'])
|
||||
self.value = str(response['Value'])
|
||||
|
||||
|
||||
class ConfigurationSettingsDescription(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(ConfigurationSettingsDescription, self).__init__()
|
||||
|
||||
self.application_name = str(response['ApplicationName'])
|
||||
self.date_created = datetime.fromtimestamp(response['DateCreated'])
|
||||
self.date_updated = datetime.fromtimestamp(response['DateUpdated'])
|
||||
self.deployment_status = str(response['DeploymentStatus'])
|
||||
self.description = str(response['Description'])
|
||||
self.environment_name = str(response['EnvironmentName'])
|
||||
self.option_settings = []
|
||||
if response['OptionSettings']:
|
||||
for member in response['OptionSettings']:
|
||||
option_setting = ConfigurationOptionSetting(member)
|
||||
self.option_settings.append(option_setting)
|
||||
self.solution_stack_name = str(response['SolutionStackName'])
|
||||
self.template_name = str(response['TemplateName'])
|
||||
|
||||
|
||||
class EnvironmentDescription(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(EnvironmentDescription, self).__init__()
|
||||
|
||||
self.application_name = str(response['ApplicationName'])
|
||||
self.cname = str(response['CNAME'])
|
||||
self.date_created = datetime.fromtimestamp(response['DateCreated'])
|
||||
self.date_updated = datetime.fromtimestamp(response['DateUpdated'])
|
||||
self.description = str(response['Description'])
|
||||
self.endpoint_url = str(response['EndpointURL'])
|
||||
self.environment_id = str(response['EnvironmentId'])
|
||||
self.environment_name = str(response['EnvironmentName'])
|
||||
self.health = str(response['Health'])
|
||||
if response['Resources']:
|
||||
self.resources = EnvironmentResourcesDescription(response['Resources'])
|
||||
else:
|
||||
self.resources = None
|
||||
self.solution_stack_name = str(response['SolutionStackName'])
|
||||
self.status = str(response['Status'])
|
||||
self.template_name = str(response['TemplateName'])
|
||||
self.version_label = str(response['VersionLabel'])
|
||||
|
||||
|
||||
class EnvironmentInfoDescription(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(EnvironmentInfoDescription, self).__init__()
|
||||
|
||||
self.ec2_instance_id = str(response['Ec2InstanceId'])
|
||||
self.info_type = str(response['InfoType'])
|
||||
self.message = str(response['Message'])
|
||||
self.sample_timestamp = datetime.fromtimestamp(response['SampleTimestamp'])
|
||||
|
||||
|
||||
class EnvironmentResourceDescription(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(EnvironmentResourceDescription, self).__init__()
|
||||
|
||||
self.auto_scaling_groups = []
|
||||
if response['AutoScalingGroups']:
|
||||
for member in response['AutoScalingGroups']:
|
||||
auto_scaling_group = AutoScalingGroup(member)
|
||||
self.auto_scaling_groups.append(auto_scaling_group)
|
||||
self.environment_name = str(response['EnvironmentName'])
|
||||
self.instances = []
|
||||
if response['Instances']:
|
||||
for member in response['Instances']:
|
||||
instance = Instance(member)
|
||||
self.instances.append(instance)
|
||||
self.launch_configurations = []
|
||||
if response['LaunchConfigurations']:
|
||||
for member in response['LaunchConfigurations']:
|
||||
launch_configuration = LaunchConfiguration(member)
|
||||
self.launch_configurations.append(launch_configuration)
|
||||
self.load_balancers = []
|
||||
if response['LoadBalancers']:
|
||||
for member in response['LoadBalancers']:
|
||||
load_balancer = LoadBalancer(member)
|
||||
self.load_balancers.append(load_balancer)
|
||||
self.triggers = []
|
||||
if response['Triggers']:
|
||||
for member in response['Triggers']:
|
||||
trigger = Trigger(member)
|
||||
self.triggers.append(trigger)
|
||||
|
||||
|
||||
class EnvironmentResourcesDescription(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(EnvironmentResourcesDescription, self).__init__()
|
||||
|
||||
if response['LoadBalancer']:
|
||||
self.load_balancer = LoadBalancerDescription(response['LoadBalancer'])
|
||||
else:
|
||||
self.load_balancer = None
|
||||
|
||||
|
||||
class EventDescription(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(EventDescription, self).__init__()
|
||||
|
||||
self.application_name = str(response['ApplicationName'])
|
||||
self.environment_name = str(response['EnvironmentName'])
|
||||
self.event_date = datetime.fromtimestamp(response['EventDate'])
|
||||
self.message = str(response['Message'])
|
||||
self.request_id = str(response['RequestId'])
|
||||
self.severity = str(response['Severity'])
|
||||
self.template_name = str(response['TemplateName'])
|
||||
self.version_label = str(response['VersionLabel'])
|
||||
|
||||
|
||||
class Instance(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(Instance, self).__init__()
|
||||
|
||||
self.id = str(response['Id'])
|
||||
|
||||
|
||||
class LaunchConfiguration(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(LaunchConfiguration, self).__init__()
|
||||
|
||||
self.name = str(response['Name'])
|
||||
|
||||
|
||||
class Listener(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(Listener, self).__init__()
|
||||
|
||||
self.port = int(response['Port']) if response['Port'] else None
|
||||
self.protocol = str(response['Protocol'])
|
||||
|
||||
|
||||
class LoadBalancer(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(LoadBalancer, self).__init__()
|
||||
|
||||
self.name = str(response['Name'])
|
||||
|
||||
|
||||
class LoadBalancerDescription(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(LoadBalancerDescription, self).__init__()
|
||||
|
||||
self.domain = str(response['Domain'])
|
||||
self.listeners = []
|
||||
if response['Listeners']:
|
||||
for member in response['Listeners']:
|
||||
listener = Listener(member)
|
||||
self.listeners.append(listener)
|
||||
self.load_balancer_name = str(response['LoadBalancerName'])
|
||||
|
||||
|
||||
class OptionRestrictionRegex(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(OptionRestrictionRegex, self).__init__()
|
||||
|
||||
self.label = response['Label']
|
||||
self.pattern = response['Pattern']
|
||||
|
||||
|
||||
class SolutionStackDescription(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(SolutionStackDescription, self).__init__()
|
||||
|
||||
self.permitted_file_types = []
|
||||
if response['PermittedFileTypes']:
|
||||
for member in response['PermittedFileTypes']:
|
||||
permitted_file_type = str(member)
|
||||
self.permitted_file_types.append(permitted_file_type)
|
||||
self.solution_stack_name = str(response['SolutionStackName'])
|
||||
|
||||
|
||||
class S3Location(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(S3Location, self).__init__()
|
||||
|
||||
self.s3_bucket = str(response['S3Bucket'])
|
||||
self.s3_key = str(response['S3Key'])
|
||||
|
||||
|
||||
class Trigger(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(Trigger, self).__init__()
|
||||
|
||||
self.name = str(response['Name'])
|
||||
|
||||
|
||||
class ValidationMessage(BaseObject):
|
||||
def __init__(self, response):
|
||||
super(ValidationMessage, self).__init__()
|
||||
|
||||
self.message = str(response['Message'])
|
||||
self.namespace = str(response['Namespace'])
|
||||
self.option_name = str(response['OptionName'])
|
||||
self.severity = str(response['Severity'])
|
||||
|
||||
|
||||
# These are the response objects layer2 uses, one for each layer1 api call.
|
||||
class CheckDNSAvailabilityResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['CheckDNSAvailabilityResponse']
|
||||
super(CheckDNSAvailabilityResponse, self).__init__(response)
|
||||
|
||||
response = response['CheckDNSAvailabilityResult']
|
||||
self.fully_qualified_cname = str(response['FullyQualifiedCNAME'])
|
||||
self.available = bool(response['Available'])
|
||||
|
||||
|
||||
# Our naming convension produces this class name but api names it with more
|
||||
# capitals.
|
||||
class CheckDnsAvailabilityResponse(CheckDNSAvailabilityResponse): pass
|
||||
|
||||
|
||||
class CreateApplicationResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['CreateApplicationResponse']
|
||||
super(CreateApplicationResponse, self).__init__(response)
|
||||
|
||||
response = response['CreateApplicationResult']
|
||||
if response['Application']:
|
||||
self.application = ApplicationDescription(response['Application'])
|
||||
else:
|
||||
self.application = None
|
||||
|
||||
|
||||
class CreateApplicationVersionResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['CreateApplicationVersionResponse']
|
||||
super(CreateApplicationVersionResponse, self).__init__(response)
|
||||
|
||||
response = response['CreateApplicationVersionResult']
|
||||
if response['ApplicationVersion']:
|
||||
self.application_version = ApplicationVersionDescription(response['ApplicationVersion'])
|
||||
else:
|
||||
self.application_version = None
|
||||
|
||||
|
||||
class CreateConfigurationTemplateResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['CreateConfigurationTemplateResponse']
|
||||
super(CreateConfigurationTemplateResponse, self).__init__(response)
|
||||
|
||||
response = response['CreateConfigurationTemplateResult']
|
||||
self.application_name = str(response['ApplicationName'])
|
||||
self.date_created = datetime.fromtimestamp(response['DateCreated'])
|
||||
self.date_updated = datetime.fromtimestamp(response['DateUpdated'])
|
||||
self.deployment_status = str(response['DeploymentStatus'])
|
||||
self.description = str(response['Description'])
|
||||
self.environment_name = str(response['EnvironmentName'])
|
||||
self.option_settings = []
|
||||
if response['OptionSettings']:
|
||||
for member in response['OptionSettings']:
|
||||
option_setting = ConfigurationOptionSetting(member)
|
||||
self.option_settings.append(option_setting)
|
||||
self.solution_stack_name = str(response['SolutionStackName'])
|
||||
self.template_name = str(response['TemplateName'])
|
||||
|
||||
|
||||
class CreateEnvironmentResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['CreateEnvironmentResponse']
|
||||
super(CreateEnvironmentResponse, self).__init__(response)
|
||||
|
||||
response = response['CreateEnvironmentResult']
|
||||
self.application_name = str(response['ApplicationName'])
|
||||
self.cname = str(response['CNAME'])
|
||||
self.date_created = datetime.fromtimestamp(response['DateCreated'])
|
||||
self.date_updated = datetime.fromtimestamp(response['DateUpdated'])
|
||||
self.description = str(response['Description'])
|
||||
self.endpoint_url = str(response['EndpointURL'])
|
||||
self.environment_id = str(response['EnvironmentId'])
|
||||
self.environment_name = str(response['EnvironmentName'])
|
||||
self.health = str(response['Health'])
|
||||
if response['Resources']:
|
||||
self.resources = EnvironmentResourcesDescription(response['Resources'])
|
||||
else:
|
||||
self.resources = None
|
||||
self.solution_stack_name = str(response['SolutionStackName'])
|
||||
self.status = str(response['Status'])
|
||||
self.template_name = str(response['TemplateName'])
|
||||
self.version_label = str(response['VersionLabel'])
|
||||
|
||||
|
||||
class CreateStorageLocationResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['CreateStorageLocationResponse']
|
||||
super(CreateStorageLocationResponse, self).__init__(response)
|
||||
|
||||
response = response['CreateStorageLocationResult']
|
||||
self.s3_bucket = str(response['S3Bucket'])
|
||||
|
||||
|
||||
class DeleteApplicationResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['DeleteApplicationResponse']
|
||||
super(DeleteApplicationResponse, self).__init__(response)
|
||||
|
||||
|
||||
class DeleteApplicationVersionResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['DeleteApplicationVersionResponse']
|
||||
super(DeleteApplicationVersionResponse, self).__init__(response)
|
||||
|
||||
|
||||
class DeleteConfigurationTemplateResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['DeleteConfigurationTemplateResponse']
|
||||
super(DeleteConfigurationTemplateResponse, self).__init__(response)
|
||||
|
||||
|
||||
class DeleteEnvironmentConfigurationResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['DeleteEnvironmentConfigurationResponse']
|
||||
super(DeleteEnvironmentConfigurationResponse, self).__init__(response)
|
||||
|
||||
|
||||
class DescribeApplicationVersionsResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['DescribeApplicationVersionsResponse']
|
||||
super(DescribeApplicationVersionsResponse, self).__init__(response)
|
||||
|
||||
response = response['DescribeApplicationVersionsResult']
|
||||
self.application_versions = []
|
||||
if response['ApplicationVersions']:
|
||||
for member in response['ApplicationVersions']:
|
||||
application_version = ApplicationVersionDescription(member)
|
||||
self.application_versions.append(application_version)
|
||||
|
||||
|
||||
class DescribeApplicationsResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['DescribeApplicationsResponse']
|
||||
super(DescribeApplicationsResponse, self).__init__(response)
|
||||
|
||||
response = response['DescribeApplicationsResult']
|
||||
self.applications = []
|
||||
if response['Applications']:
|
||||
for member in response['Applications']:
|
||||
application = ApplicationDescription(member)
|
||||
self.applications.append(application)
|
||||
|
||||
|
||||
class DescribeConfigurationOptionsResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['DescribeConfigurationOptionsResponse']
|
||||
super(DescribeConfigurationOptionsResponse, self).__init__(response)
|
||||
|
||||
response = response['DescribeConfigurationOptionsResult']
|
||||
self.options = []
|
||||
if response['Options']:
|
||||
for member in response['Options']:
|
||||
option = ConfigurationOptionDescription(member)
|
||||
self.options.append(option)
|
||||
self.solution_stack_name = str(response['SolutionStackName'])
|
||||
|
||||
|
||||
class DescribeConfigurationSettingsResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['DescribeConfigurationSettingsResponse']
|
||||
super(DescribeConfigurationSettingsResponse, self).__init__(response)
|
||||
|
||||
response = response['DescribeConfigurationSettingsResult']
|
||||
self.configuration_settings = []
|
||||
if response['ConfigurationSettings']:
|
||||
for member in response['ConfigurationSettings']:
|
||||
configuration_setting = ConfigurationSettingsDescription(member)
|
||||
self.configuration_settings.append(configuration_setting)
|
||||
|
||||
|
||||
class DescribeEnvironmentResourcesResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['DescribeEnvironmentResourcesResponse']
|
||||
super(DescribeEnvironmentResourcesResponse, self).__init__(response)
|
||||
|
||||
response = response['DescribeEnvironmentResourcesResult']
|
||||
if response['EnvironmentResources']:
|
||||
self.environment_resources = EnvironmentResourceDescription(response['EnvironmentResources'])
|
||||
else:
|
||||
self.environment_resources = None
|
||||
|
||||
|
||||
class DescribeEnvironmentsResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['DescribeEnvironmentsResponse']
|
||||
super(DescribeEnvironmentsResponse, self).__init__(response)
|
||||
|
||||
response = response['DescribeEnvironmentsResult']
|
||||
self.environments = []
|
||||
if response['Environments']:
|
||||
for member in response['Environments']:
|
||||
environment = EnvironmentDescription(member)
|
||||
self.environments.append(environment)
|
||||
|
||||
|
||||
class DescribeEventsResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['DescribeEventsResponse']
|
||||
super(DescribeEventsResponse, self).__init__(response)
|
||||
|
||||
response = response['DescribeEventsResult']
|
||||
self.events = []
|
||||
if response['Events']:
|
||||
for member in response['Events']:
|
||||
event = EventDescription(member)
|
||||
self.events.append(event)
|
||||
self.next_tokent = str(response['NextToken'])
|
||||
|
||||
|
||||
class ListAvailableSolutionStacksResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['ListAvailableSolutionStacksResponse']
|
||||
super(ListAvailableSolutionStacksResponse, self).__init__(response)
|
||||
|
||||
response = response['ListAvailableSolutionStacksResult']
|
||||
self.solution_stack_details = []
|
||||
if response['SolutionStackDetails']:
|
||||
for member in response['SolutionStackDetails']:
|
||||
solution_stack_detail = SolutionStackDescription(member)
|
||||
self.solution_stack_details.append(solution_stack_detail)
|
||||
self.solution_stacks = []
|
||||
if response['SolutionStacks']:
|
||||
for member in response['SolutionStacks']:
|
||||
solution_stack = str(member)
|
||||
self.solution_stacks.append(solution_stack)
|
||||
|
||||
|
||||
class RebuildEnvironmentResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['RebuildEnvironmentResponse']
|
||||
super(RebuildEnvironmentResponse, self).__init__(response)
|
||||
|
||||
|
||||
class RequestEnvironmentInfoResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['RequestEnvironmentInfoResponse']
|
||||
super(RequestEnvironmentInfoResponse, self).__init__(response)
|
||||
|
||||
|
||||
class RestartAppServerResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['RestartAppServerResponse']
|
||||
super(RestartAppServerResponse, self).__init__(response)
|
||||
|
||||
|
||||
class RetrieveEnvironmentInfoResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['RetrieveEnvironmentInfoResponse']
|
||||
super(RetrieveEnvironmentInfoResponse, self).__init__(response)
|
||||
|
||||
response = response['RetrieveEnvironmentInfoResult']
|
||||
self.environment_info = []
|
||||
if response['EnvironmentInfo']:
|
||||
for member in response['EnvironmentInfo']:
|
||||
environment_info = EnvironmentInfoDescription(member)
|
||||
self.environment_info.append(environment_info)
|
||||
|
||||
|
||||
class SwapEnvironmentCNAMEsResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['SwapEnvironmentCNAMEsResponse']
|
||||
super(SwapEnvironmentCNAMEsResponse, self).__init__(response)
|
||||
|
||||
|
||||
class SwapEnvironmentCnamesResponse(SwapEnvironmentCNAMEsResponse): pass
|
||||
|
||||
|
||||
class TerminateEnvironmentResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['TerminateEnvironmentResponse']
|
||||
super(TerminateEnvironmentResponse, self).__init__(response)
|
||||
|
||||
response = response['TerminateEnvironmentResult']
|
||||
self.application_name = str(response['ApplicationName'])
|
||||
self.cname = str(response['CNAME'])
|
||||
self.date_created = datetime.fromtimestamp(response['DateCreated'])
|
||||
self.date_updated = datetime.fromtimestamp(response['DateUpdated'])
|
||||
self.description = str(response['Description'])
|
||||
self.endpoint_url = str(response['EndpointURL'])
|
||||
self.environment_id = str(response['EnvironmentId'])
|
||||
self.environment_name = str(response['EnvironmentName'])
|
||||
self.health = str(response['Health'])
|
||||
if response['Resources']:
|
||||
self.resources = EnvironmentResourcesDescription(response['Resources'])
|
||||
else:
|
||||
self.resources = None
|
||||
self.solution_stack_name = str(response['SolutionStackName'])
|
||||
self.status = str(response['Status'])
|
||||
self.template_name = str(response['TemplateName'])
|
||||
self.version_label = str(response['VersionLabel'])
|
||||
|
||||
|
||||
class UpdateApplicationResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['UpdateApplicationResponse']
|
||||
super(UpdateApplicationResponse, self).__init__(response)
|
||||
|
||||
response = response['UpdateApplicationResult']
|
||||
if response['Application']:
|
||||
self.application = ApplicationDescription(response['Application'])
|
||||
else:
|
||||
self.application = None
|
||||
|
||||
|
||||
class UpdateApplicationVersionResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['UpdateApplicationVersionResponse']
|
||||
super(UpdateApplicationVersionResponse, self).__init__(response)
|
||||
|
||||
response = response['UpdateApplicationVersionResult']
|
||||
if response['ApplicationVersion']:
|
||||
self.application_version = ApplicationVersionDescription(response['ApplicationVersion'])
|
||||
else:
|
||||
self.application_version = None
|
||||
|
||||
|
||||
class UpdateConfigurationTemplateResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['UpdateConfigurationTemplateResponse']
|
||||
super(UpdateConfigurationTemplateResponse, self).__init__(response)
|
||||
|
||||
response = response['UpdateConfigurationTemplateResult']
|
||||
self.application_name = str(response['ApplicationName'])
|
||||
self.date_created = datetime.fromtimestamp(response['DateCreated'])
|
||||
self.date_updated = datetime.fromtimestamp(response['DateUpdated'])
|
||||
self.deployment_status = str(response['DeploymentStatus'])
|
||||
self.description = str(response['Description'])
|
||||
self.environment_name = str(response['EnvironmentName'])
|
||||
self.option_settings = []
|
||||
if response['OptionSettings']:
|
||||
for member in response['OptionSettings']:
|
||||
option_setting = ConfigurationOptionSetting(member)
|
||||
self.option_settings.append(option_setting)
|
||||
self.solution_stack_name = str(response['SolutionStackName'])
|
||||
self.template_name = str(response['TemplateName'])
|
||||
|
||||
|
||||
class UpdateEnvironmentResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['UpdateEnvironmentResponse']
|
||||
super(UpdateEnvironmentResponse, self).__init__(response)
|
||||
|
||||
response = response['UpdateEnvironmentResult']
|
||||
self.application_name = str(response['ApplicationName'])
|
||||
self.cname = str(response['CNAME'])
|
||||
self.date_created = datetime.fromtimestamp(response['DateCreated'])
|
||||
self.date_updated = datetime.fromtimestamp(response['DateUpdated'])
|
||||
self.description = str(response['Description'])
|
||||
self.endpoint_url = str(response['EndpointURL'])
|
||||
self.environment_id = str(response['EnvironmentId'])
|
||||
self.environment_name = str(response['EnvironmentName'])
|
||||
self.health = str(response['Health'])
|
||||
if response['Resources']:
|
||||
self.resources = EnvironmentResourcesDescription(response['Resources'])
|
||||
else:
|
||||
self.resources = None
|
||||
self.solution_stack_name = str(response['SolutionStackName'])
|
||||
self.status = str(response['Status'])
|
||||
self.template_name = str(response['TemplateName'])
|
||||
self.version_label = str(response['VersionLabel'])
|
||||
|
||||
|
||||
class ValidateConfigurationSettingsResponse(Response):
|
||||
def __init__(self, response):
|
||||
response = response['ValidateConfigurationSettingsResponse']
|
||||
super(ValidateConfigurationSettingsResponse, self).__init__(response)
|
||||
|
||||
response = response['ValidateConfigurationSettingsResult']
|
||||
self.messages = []
|
||||
if response['Messages']:
|
||||
for member in response['Messages']:
|
||||
message = ValidationMessage(member)
|
||||
self.messages.append(message)
|
|
@ -1,29 +0,0 @@
|
|||
"""Wraps layer1 api methods and converts layer1 dict responses to objects."""
|
||||
from boto.beanstalk.layer1 import Layer1
|
||||
import boto.beanstalk.response
|
||||
from boto.exception import BotoServerError
|
||||
import boto.beanstalk.exception as exception
|
||||
|
||||
|
||||
def beanstalk_wrapper(func, name):
|
||||
def _wrapped_low_level_api(*args, **kwargs):
|
||||
try:
|
||||
response = func(*args, **kwargs)
|
||||
except BotoServerError, e:
|
||||
raise exception.simple(e)
|
||||
# Turn 'this_is_a_function_name' into 'ThisIsAFunctionNameResponse'.
|
||||
cls_name = ''.join([part.capitalize() for part in name.split('_')]) + 'Response'
|
||||
cls = getattr(boto.beanstalk.response, cls_name)
|
||||
return cls(response)
|
||||
return _wrapped_low_level_api
|
||||
|
||||
|
||||
class Layer1Wrapper(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.api = Layer1(*args, **kwargs)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return beanstalk_wrapper(getattr(self.api, name), name)
|
||||
except AttributeError:
|
||||
raise AttributeError("%s has no attribute %r" % (self, name))
|
|
@ -1,22 +0,0 @@
|
|||
# Copyright 2010 Google Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
File diff suppressed because it is too large
Load Diff
|
@ -1,68 +0,0 @@
|
|||
# Copyright (c) 2010-2011 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2010-2011, Eucalyptus Systems, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
from connection import CloudFormationConnection
|
||||
from boto.regioninfo import RegionInfo
|
||||
|
||||
RegionData = {
|
||||
'us-east-1': 'cloudformation.us-east-1.amazonaws.com',
|
||||
'us-west-1': 'cloudformation.us-west-1.amazonaws.com',
|
||||
'us-west-2': 'cloudformation.us-west-2.amazonaws.com',
|
||||
'sa-east-1': 'cloudformation.sa-east-1.amazonaws.com',
|
||||
'eu-west-1': 'cloudformation.eu-west-1.amazonaws.com',
|
||||
'ap-northeast-1': 'cloudformation.ap-northeast-1.amazonaws.com',
|
||||
'ap-southeast-1': 'cloudformation.ap-southeast-1.amazonaws.com',
|
||||
'ap-southeast-2': 'cloudformation.ap-southeast-2.amazonaws.com',
|
||||
}
|
||||
|
||||
|
||||
def regions():
|
||||
"""
|
||||
Get all available regions for the CloudFormation service.
|
||||
|
||||
:rtype: list
|
||||
:return: A list of :class:`boto.RegionInfo` instances
|
||||
"""
|
||||
regions = []
|
||||
for region_name in RegionData:
|
||||
region = RegionInfo(name=region_name,
|
||||
endpoint=RegionData[region_name],
|
||||
connection_cls=CloudFormationConnection)
|
||||
regions.append(region)
|
||||
return regions
|
||||
|
||||
|
||||
def connect_to_region(region_name, **kw_params):
|
||||
"""
|
||||
Given a valid region name, return a
|
||||
:class:`boto.cloudformation.CloudFormationConnection`.
|
||||
|
||||
:param str region_name: The name of the region to connect to.
|
||||
|
||||
:rtype: :class:`boto.cloudformation.CloudFormationConnection` or ``None``
|
||||
:return: A connection to the given region, or None if an invalid region
|
||||
name is given
|
||||
"""
|
||||
for region in regions():
|
||||
if region.name == region_name:
|
||||
return region.connect(**kw_params)
|
||||
return None
|
|
@ -1,370 +0,0 @@
|
|||
# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
import boto
|
||||
from boto.cloudformation.stack import Stack, StackSummary, StackEvent
|
||||
from boto.cloudformation.stack import StackResource, StackResourceSummary
|
||||
from boto.cloudformation.template import Template
|
||||
from boto.connection import AWSQueryConnection
|
||||
from boto.regioninfo import RegionInfo
|
||||
from boto.compat import json
|
||||
|
||||
|
||||
class CloudFormationConnection(AWSQueryConnection):
|
||||
|
||||
"""
|
||||
A Connection to the CloudFormation Service.
|
||||
"""
|
||||
APIVersion = boto.config.get('Boto', 'cfn_version', '2010-05-15')
|
||||
DefaultRegionName = boto.config.get('Boto', 'cfn_region_name', 'us-east-1')
|
||||
DefaultRegionEndpoint = boto.config.get('Boto', 'cfn_region_endpoint',
|
||||
'cloudformation.us-east-1.amazonaws.com')
|
||||
|
||||
valid_states = (
|
||||
'CREATE_IN_PROGRESS', 'CREATE_FAILED', 'CREATE_COMPLETE',
|
||||
'ROLLBACK_IN_PROGRESS', 'ROLLBACK_FAILED', 'ROLLBACK_COMPLETE',
|
||||
'DELETE_IN_PROGRESS', 'DELETE_FAILED', 'DELETE_COMPLETE',
|
||||
'UPDATE_IN_PROGRESS', 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS',
|
||||
'UPDATE_COMPLETE', 'UPDATE_ROLLBACK_IN_PROGRESS',
|
||||
'UPDATE_ROLLBACK_FAILED',
|
||||
'UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS',
|
||||
'UPDATE_ROLLBACK_COMPLETE')
|
||||
|
||||
def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
|
||||
is_secure=True, port=None, proxy=None, proxy_port=None,
|
||||
proxy_user=None, proxy_pass=None, debug=0,
|
||||
https_connection_factory=None, region=None, path='/',
|
||||
converter=None, security_token=None, validate_certs=True):
|
||||
if not region:
|
||||
region = RegionInfo(self, self.DefaultRegionName,
|
||||
self.DefaultRegionEndpoint, CloudFormationConnection)
|
||||
self.region = region
|
||||
AWSQueryConnection.__init__(self, aws_access_key_id,
|
||||
aws_secret_access_key,
|
||||
is_secure, port, proxy, proxy_port,
|
||||
proxy_user, proxy_pass,
|
||||
self.region.endpoint, debug,
|
||||
https_connection_factory, path,
|
||||
security_token,
|
||||
validate_certs=validate_certs)
|
||||
|
||||
def _required_auth_capability(self):
|
||||
return ['hmac-v4']
|
||||
|
||||
def encode_bool(self, v):
|
||||
v = bool(v)
|
||||
return {True: "true", False: "false"}[v]
|
||||
|
||||
def _build_create_or_update_params(self, stack_name, template_body,
|
||||
template_url, parameters,
|
||||
notification_arns, disable_rollback,
|
||||
timeout_in_minutes, capabilities, tags):
|
||||
"""
|
||||
Helper that creates JSON parameters needed by a Stack Create or
|
||||
Stack Update call.
|
||||
|
||||
:type stack_name: string
|
||||
:param stack_name: The name of the Stack, must be unique amoung running
|
||||
Stacks
|
||||
|
||||
:type template_body: string
|
||||
:param template_body: The template body (JSON string)
|
||||
|
||||
:type template_url: string
|
||||
:param template_url: An S3 URL of a stored template JSON document. If
|
||||
both the template_body and template_url are
|
||||
specified, the template_body takes precedence
|
||||
|
||||
:type parameters: list of tuples
|
||||
:param parameters: A list of (key, value) pairs for template input
|
||||
parameters.
|
||||
|
||||
:type notification_arns: list of strings
|
||||
:param notification_arns: A list of SNS topics to send Stack event
|
||||
notifications to.
|
||||
|
||||
:type disable_rollback: bool
|
||||
:param disable_rollback: Indicates whether or not to rollback on
|
||||
failure.
|
||||
|
||||
:type timeout_in_minutes: int
|
||||
:param timeout_in_minutes: Maximum amount of time to let the Stack
|
||||
spend creating itself. If this timeout is exceeded,
|
||||
the Stack will enter the CREATE_FAILED state.
|
||||
|
||||
:type capabilities: list
|
||||
:param capabilities: The list of capabilities you want to allow in
|
||||
the stack. Currently, the only valid capability is
|
||||
'CAPABILITY_IAM'.
|
||||
|
||||
:type tags: dict
|
||||
:param tags: A dictionary of (key, value) pairs of tags to
|
||||
associate with this stack.
|
||||
|
||||
:rtype: dict
|
||||
:return: JSON parameters represented as a Python dict.
|
||||
"""
|
||||
params = {'ContentType': "JSON", 'StackName': stack_name,
|
||||
'DisableRollback': self.encode_bool(disable_rollback)}
|
||||
if template_body:
|
||||
params['TemplateBody'] = template_body
|
||||
if template_url:
|
||||
params['TemplateURL'] = template_url
|
||||
if template_body and template_url:
|
||||
boto.log.warning("If both TemplateBody and TemplateURL are"
|
||||
" specified, only TemplateBody will be honored by the API")
|
||||
if len(parameters) > 0:
|
||||
for i, (key, value) in enumerate(parameters):
|
||||
params['Parameters.member.%d.ParameterKey' % (i + 1)] = key
|
||||
params['Parameters.member.%d.ParameterValue' % (i + 1)] = value
|
||||
if capabilities:
|
||||
for i, value in enumerate(capabilities):
|
||||
params['Capabilities.member.%d' % (i + 1)] = value
|
||||
if tags:
|
||||
for i, (key, value) in enumerate(tags.items()):
|
||||
params['Tags.member.%d.Key' % (i + 1)] = key
|
||||
params['Tags.member.%d.Value' % (i + 1)] = value
|
||||
if len(notification_arns) > 0:
|
||||
self.build_list_params(params, notification_arns,
|
||||
"NotificationARNs.member")
|
||||
if timeout_in_minutes:
|
||||
params['TimeoutInMinutes'] = int(timeout_in_minutes)
|
||||
return params
|
||||
|
||||
def create_stack(self, stack_name, template_body=None, template_url=None,
|
||||
parameters=[], notification_arns=[], disable_rollback=False,
|
||||
timeout_in_minutes=None, capabilities=None, tags=None):
|
||||
"""
|
||||
Creates a CloudFormation Stack as specified by the template.
|
||||
|
||||
:type stack_name: string
|
||||
:param stack_name: The name of the Stack, must be unique amoung running
|
||||
Stacks
|
||||
|
||||
:type template_body: string
|
||||
:param template_body: The template body (JSON string)
|
||||
|
||||
:type template_url: string
|
||||
:param template_url: An S3 URL of a stored template JSON document. If
|
||||
both the template_body and template_url are
|
||||
specified, the template_body takes precedence
|
||||
|
||||
:type parameters: list of tuples
|
||||
:param parameters: A list of (key, value) pairs for template input
|
||||
parameters.
|
||||
|
||||
:type notification_arns: list of strings
|
||||
:param notification_arns: A list of SNS topics to send Stack event
|
||||
notifications to.
|
||||
|
||||
:type disable_rollback: bool
|
||||
:param disable_rollback: Indicates whether or not to rollback on
|
||||
failure.
|
||||
|
||||
:type timeout_in_minutes: int
|
||||
:param timeout_in_minutes: Maximum amount of time to let the Stack
|
||||
spend creating itself. If this timeout is exceeded,
|
||||
the Stack will enter the CREATE_FAILED state.
|
||||
|
||||
:type capabilities: list
|
||||
:param capabilities: The list of capabilities you want to allow in
|
||||
the stack. Currently, the only valid capability is
|
||||
'CAPABILITY_IAM'.
|
||||
|
||||
:type tags: dict
|
||||
:param tags: A dictionary of (key, value) pairs of tags to
|
||||
associate with this stack.
|
||||
|
||||
:rtype: string
|
||||
:return: The unique Stack ID.
|
||||
"""
|
||||
params = self._build_create_or_update_params(stack_name,
|
||||
template_body, template_url, parameters, notification_arns,
|
||||
disable_rollback, timeout_in_minutes, capabilities, tags)
|
||||
response = self.make_request('CreateStack', params, '/', 'POST')
|
||||
body = response.read()
|
||||
if response.status == 200:
|
||||
body = json.loads(body)
|
||||
return body['CreateStackResponse']['CreateStackResult']['StackId']
|
||||
else:
|
||||
boto.log.error('%s %s' % (response.status, response.reason))
|
||||
boto.log.error('%s' % body)
|
||||
raise self.ResponseError(response.status, response.reason, body)
|
||||
|
||||
def update_stack(self, stack_name, template_body=None, template_url=None,
|
||||
parameters=[], notification_arns=[], disable_rollback=False,
|
||||
timeout_in_minutes=None, capabilities=None, tags=None):
|
||||
"""
|
||||
Updates a CloudFormation Stack as specified by the template.
|
||||
|
||||
:type stack_name: string
|
||||
:param stack_name: The name of the Stack, must be unique amoung running
|
||||
Stacks.
|
||||
|
||||
:type template_body: string
|
||||
:param template_body: The template body (JSON string)
|
||||
|
||||
:type template_url: string
|
||||
:param template_url: An S3 URL of a stored template JSON document. If
|
||||
both the template_body and template_url are
|
||||
specified, the template_body takes precedence.
|
||||
|
||||
:type parameters: list of tuples
|
||||
:param parameters: A list of (key, value) pairs for template input
|
||||
parameters.
|
||||
|
||||
:type notification_arns: list of strings
|
||||
:param notification_arns: A list of SNS topics to send Stack event
|
||||
notifications to.
|
||||
|
||||
:type disable_rollback: bool
|
||||
:param disable_rollback: Indicates whether or not to rollback on
|
||||
failure.
|
||||
|
||||
:type timeout_in_minutes: int
|
||||
:param timeout_in_minutes: Maximum amount of time to let the Stack
|
||||
spend creating itself. If this timeout is exceeded,
|
||||
the Stack will enter the CREATE_FAILED state
|
||||
|
||||
:type capabilities: list
|
||||
:param capabilities: The list of capabilities you want to allow in
|
||||
the stack. Currently, the only valid capability is
|
||||
'CAPABILITY_IAM'.
|
||||
|
||||
:type tags: dict
|
||||
:param tags: A dictionary of (key, value) pairs of tags to
|
||||
associate with this stack.
|
||||
|
||||
:rtype: string
|
||||
:return: The unique Stack ID.
|
||||
"""
|
||||
params = self._build_create_or_update_params(stack_name,
|
||||
template_body, template_url, parameters, notification_arns,
|
||||
disable_rollback, timeout_in_minutes, capabilities, tags)
|
||||
response = self.make_request('UpdateStack', params, '/', 'POST')
|
||||
body = response.read()
|
||||
if response.status == 200:
|
||||
body = json.loads(body)
|
||||
return body['UpdateStackResponse']['UpdateStackResult']['StackId']
|
||||
else:
|
||||
boto.log.error('%s %s' % (response.status, response.reason))
|
||||
boto.log.error('%s' % body)
|
||||
raise self.ResponseError(response.status, response.reason, body)
|
||||
|
||||
def delete_stack(self, stack_name_or_id):
|
||||
params = {'ContentType': "JSON", 'StackName': stack_name_or_id}
|
||||
# TODO: change this to get_status ?
|
||||
response = self.make_request('DeleteStack', params, '/', 'GET')
|
||||
body = response.read()
|
||||
if response.status == 200:
|
||||
return json.loads(body)
|
||||
else:
|
||||
boto.log.error('%s %s' % (response.status, response.reason))
|
||||
boto.log.error('%s' % body)
|
||||
raise self.ResponseError(response.status, response.reason, body)
|
||||
|
||||
def describe_stack_events(self, stack_name_or_id=None, next_token=None):
|
||||
params = {}
|
||||
if stack_name_or_id:
|
||||
params['StackName'] = stack_name_or_id
|
||||
if next_token:
|
||||
params['NextToken'] = next_token
|
||||
return self.get_list('DescribeStackEvents', params, [('member',
|
||||
StackEvent)])
|
||||
|
||||
def describe_stack_resource(self, stack_name_or_id, logical_resource_id):
|
||||
params = {'ContentType': "JSON", 'StackName': stack_name_or_id,
|
||||
'LogicalResourceId': logical_resource_id}
|
||||
response = self.make_request('DescribeStackResource', params,
|
||||
'/', 'GET')
|
||||
body = response.read()
|
||||
if response.status == 200:
|
||||
return json.loads(body)
|
||||
else:
|
||||
boto.log.error('%s %s' % (response.status, response.reason))
|
||||
boto.log.error('%s' % body)
|
||||
raise self.ResponseError(response.status, response.reason, body)
|
||||
|
||||
def describe_stack_resources(self, stack_name_or_id=None,
|
||||
logical_resource_id=None,
|
||||
physical_resource_id=None):
|
||||
params = {}
|
||||
if stack_name_or_id:
|
||||
params['StackName'] = stack_name_or_id
|
||||
if logical_resource_id:
|
||||
params['LogicalResourceId'] = logical_resource_id
|
||||
if physical_resource_id:
|
||||
params['PhysicalResourceId'] = physical_resource_id
|
||||
return self.get_list('DescribeStackResources', params,
|
||||
[('member', StackResource)])
|
||||
|
||||
def describe_stacks(self, stack_name_or_id=None):
|
||||
params = {}
|
||||
if stack_name_or_id:
|
||||
params['StackName'] = stack_name_or_id
|
||||
return self.get_list('DescribeStacks', params, [('member', Stack)])
|
||||
|
||||
def get_template(self, stack_name_or_id):
|
||||
params = {'ContentType': "JSON", 'StackName': stack_name_or_id}
|
||||
response = self.make_request('GetTemplate', params, '/', 'GET')
|
||||
body = response.read()
|
||||
if response.status == 200:
|
||||
return json.loads(body)
|
||||
else:
|
||||
boto.log.error('%s %s' % (response.status, response.reason))
|
||||
boto.log.error('%s' % body)
|
||||
raise self.ResponseError(response.status, response.reason, body)
|
||||
|
||||
def list_stack_resources(self, stack_name_or_id, next_token=None):
|
||||
params = {'StackName': stack_name_or_id}
|
||||
if next_token:
|
||||
params['NextToken'] = next_token
|
||||
return self.get_list('ListStackResources', params,
|
||||
[('member', StackResourceSummary)])
|
||||
|
||||
def list_stacks(self, stack_status_filters=[], next_token=None):
|
||||
params = {}
|
||||
if next_token:
|
||||
params['NextToken'] = next_token
|
||||
if len(stack_status_filters) > 0:
|
||||
self.build_list_params(params, stack_status_filters,
|
||||
"StackStatusFilter.member")
|
||||
|
||||
return self.get_list('ListStacks', params,
|
||||
[('member', StackSummary)])
|
||||
|
||||
def validate_template(self, template_body=None, template_url=None):
|
||||
params = {}
|
||||
if template_body:
|
||||
params['TemplateBody'] = template_body
|
||||
if template_url:
|
||||
params['TemplateURL'] = template_url
|
||||
if template_body and template_url:
|
||||
boto.log.warning("If both TemplateBody and TemplateURL are"
|
||||
" specified, only TemplateBody will be honored by the API")
|
||||
return self.get_object('ValidateTemplate', params, Template,
|
||||
verb="POST")
|
||||
|
||||
def cancel_update_stack(self, stack_name_or_id=None):
|
||||
params = {}
|
||||
if stack_name_or_id:
|
||||
params['StackName'] = stack_name_or_id
|
||||
return self.get_status('CancelUpdateStack', params)
|
|
@ -1,386 +0,0 @@
|
|||
from datetime import datetime
|
||||
|
||||
from boto.resultset import ResultSet
|
||||
|
||||
|
||||
class Stack(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.creation_time = None
|
||||
self.description = None
|
||||
self.disable_rollback = None
|
||||
self.notification_arns = []
|
||||
self.outputs = []
|
||||
self.parameters = []
|
||||
self.capabilities = []
|
||||
self.tags = []
|
||||
self.stack_id = None
|
||||
self.stack_status = None
|
||||
self.stack_name = None
|
||||
self.stack_name_reason = None
|
||||
self.timeout_in_minutes = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == "Parameters":
|
||||
self.parameters = ResultSet([('member', Parameter)])
|
||||
return self.parameters
|
||||
elif name == "Outputs":
|
||||
self.outputs = ResultSet([('member', Output)])
|
||||
return self.outputs
|
||||
elif name == "Capabilities":
|
||||
self.capabilities = ResultSet([('member', Capability)])
|
||||
return self.capabilities
|
||||
elif name == "Tags":
|
||||
self.tags = Tag()
|
||||
return self.tags
|
||||
elif name == 'NotificationARNs':
|
||||
self.notification_arns = ResultSet([('member', NotificationARN)])
|
||||
return self.notification_arns
|
||||
else:
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'CreationTime':
|
||||
try:
|
||||
self.creation_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
|
||||
except ValueError:
|
||||
self.creation_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
elif name == "Description":
|
||||
self.description = value
|
||||
elif name == "DisableRollback":
|
||||
if str(value).lower() == 'true':
|
||||
self.disable_rollback = True
|
||||
else:
|
||||
self.disable_rollback = False
|
||||
elif name == 'StackId':
|
||||
self.stack_id = value
|
||||
elif name == 'StackName':
|
||||
self.stack_name = value
|
||||
elif name == 'StackStatus':
|
||||
self.stack_status = value
|
||||
elif name == "StackStatusReason":
|
||||
self.stack_status_reason = value
|
||||
elif name == "TimeoutInMinutes":
|
||||
self.timeout_in_minutes = int(value)
|
||||
elif name == "member":
|
||||
pass
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def delete(self):
|
||||
return self.connection.delete_stack(stack_name_or_id=self.stack_id)
|
||||
|
||||
def describe_events(self, next_token=None):
|
||||
return self.connection.describe_stack_events(
|
||||
stack_name_or_id=self.stack_id,
|
||||
next_token=next_token
|
||||
)
|
||||
|
||||
def describe_resource(self, logical_resource_id):
|
||||
return self.connection.describe_stack_resource(
|
||||
stack_name_or_id=self.stack_id,
|
||||
logical_resource_id=logical_resource_id
|
||||
)
|
||||
|
||||
def describe_resources(self, logical_resource_id=None,
|
||||
physical_resource_id=None):
|
||||
return self.connection.describe_stack_resources(
|
||||
stack_name_or_id=self.stack_id,
|
||||
logical_resource_id=logical_resource_id,
|
||||
physical_resource_id=physical_resource_id
|
||||
)
|
||||
|
||||
def list_resources(self, next_token=None):
|
||||
return self.connection.list_stack_resources(
|
||||
stack_name_or_id=self.stack_id,
|
||||
next_token=next_token
|
||||
)
|
||||
|
||||
def update(self):
|
||||
rs = self.connection.describe_stacks(self.stack_id)
|
||||
if len(rs) == 1 and rs[0].stack_id == self.stack_id:
|
||||
self.__dict__.update(rs[0].__dict__)
|
||||
else:
|
||||
raise ValueError("%s is not a valid Stack ID or Name" %
|
||||
self.stack_id)
|
||||
|
||||
def get_template(self):
|
||||
return self.connection.get_template(stack_name_or_id=self.stack_id)
|
||||
|
||||
|
||||
class StackSummary(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.stack_id = None
|
||||
self.stack_status = None
|
||||
self.stack_name = None
|
||||
self.creation_time = None
|
||||
self.deletion_time = None
|
||||
self.template_description = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'StackId':
|
||||
self.stack_id = value
|
||||
elif name == 'StackStatus':
|
||||
self.stack_status = value
|
||||
elif name == 'StackName':
|
||||
self.stack_name = value
|
||||
elif name == 'CreationTime':
|
||||
try:
|
||||
self.creation_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
|
||||
except ValueError:
|
||||
self.creation_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
elif name == "DeletionTime":
|
||||
try:
|
||||
self.deletion_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
|
||||
except ValueError:
|
||||
self.deletion_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
elif name == 'TemplateDescription':
|
||||
self.template_description = value
|
||||
elif name == "member":
|
||||
pass
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class Parameter(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = None
|
||||
self.key = None
|
||||
self.value = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == "ParameterKey":
|
||||
self.key = value
|
||||
elif name == "ParameterValue":
|
||||
self.value = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def __repr__(self):
|
||||
return "Parameter:\"%s\"=\"%s\"" % (self.key, self.value)
|
||||
|
||||
|
||||
class Output(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.description = None
|
||||
self.key = None
|
||||
self.value = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == "Description":
|
||||
self.description = value
|
||||
elif name == "OutputKey":
|
||||
self.key = value
|
||||
elif name == "OutputValue":
|
||||
self.value = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def __repr__(self):
|
||||
return "Output:\"%s\"=\"%s\"" % (self.key, self.value)
|
||||
|
||||
|
||||
class Capability(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = None
|
||||
self.value = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
self.value = value
|
||||
|
||||
def __repr__(self):
|
||||
return "Capability:\"%s\"" % (self.value)
|
||||
|
||||
|
||||
class Tag(dict):
|
||||
|
||||
def __init__(self, connection=None):
|
||||
dict.__init__(self)
|
||||
self.connection = connection
|
||||
self._current_key = None
|
||||
self._current_value = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == "Key":
|
||||
self._current_key = value
|
||||
elif name == "Value":
|
||||
self._current_value = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
if self._current_key and self._current_value:
|
||||
self[self._current_key] = self._current_value
|
||||
self._current_key = None
|
||||
self._current_value = None
|
||||
|
||||
|
||||
class NotificationARN(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = None
|
||||
self.value = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
self.value = value
|
||||
|
||||
def __repr__(self):
|
||||
return "NotificationARN:\"%s\"" % (self.value)
|
||||
|
||||
|
||||
class StackResource(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.description = None
|
||||
self.logical_resource_id = None
|
||||
self.physical_resource_id = None
|
||||
self.resource_status = None
|
||||
self.resource_status_reason = None
|
||||
self.resource_type = None
|
||||
self.stack_id = None
|
||||
self.stack_name = None
|
||||
self.timestamp = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == "Description":
|
||||
self.description = value
|
||||
elif name == "LogicalResourceId":
|
||||
self.logical_resource_id = value
|
||||
elif name == "PhysicalResourceId":
|
||||
self.physical_resource_id = value
|
||||
elif name == "ResourceStatus":
|
||||
self.resource_status = value
|
||||
elif name == "ResourceStatusReason":
|
||||
self.resource_status_reason = value
|
||||
elif name == "ResourceType":
|
||||
self.resource_type = value
|
||||
elif name == "StackId":
|
||||
self.stack_id = value
|
||||
elif name == "StackName":
|
||||
self.stack_name = value
|
||||
elif name == "Timestamp":
|
||||
try:
|
||||
self.timestamp = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
|
||||
except ValueError:
|
||||
self.timestamp = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def __repr__(self):
|
||||
return "StackResource:%s (%s)" % (self.logical_resource_id,
|
||||
self.resource_type)
|
||||
|
||||
|
||||
class StackResourceSummary(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.last_updated_time = None
|
||||
self.logical_resource_id = None
|
||||
self.physical_resource_id = None
|
||||
self.resource_status = None
|
||||
self.resource_status_reason = None
|
||||
self.resource_type = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == "LastUpdatedTime":
|
||||
try:
|
||||
self.last_updated_time = datetime.strptime(
|
||||
value,
|
||||
'%Y-%m-%dT%H:%M:%SZ'
|
||||
)
|
||||
except ValueError:
|
||||
self.last_updated_time = datetime.strptime(
|
||||
value,
|
||||
'%Y-%m-%dT%H:%M:%S.%fZ'
|
||||
)
|
||||
elif name == "LogicalResourceId":
|
||||
self.logical_resource_id = value
|
||||
elif name == "PhysicalResourceId":
|
||||
self.physical_resource_id = value
|
||||
elif name == "ResourceStatus":
|
||||
self.resource_status = value
|
||||
elif name == "ResourceStatusReason":
|
||||
self.resource_status_reason = value
|
||||
elif name == "ResourceType":
|
||||
self.resource_type = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def __repr__(self):
|
||||
return "StackResourceSummary:%s (%s)" % (self.logical_resource_id,
|
||||
self.resource_type)
|
||||
|
||||
|
||||
class StackEvent(object):
|
||||
valid_states = ("CREATE_IN_PROGRESS", "CREATE_FAILED", "CREATE_COMPLETE",
|
||||
"DELETE_IN_PROGRESS", "DELETE_FAILED", "DELETE_COMPLETE")
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.event_id = None
|
||||
self.logical_resource_id = None
|
||||
self.physical_resource_id = None
|
||||
self.resource_properties = None
|
||||
self.resource_status = None
|
||||
self.resource_status_reason = None
|
||||
self.resource_type = None
|
||||
self.stack_id = None
|
||||
self.stack_name = None
|
||||
self.timestamp = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == "EventId":
|
||||
self.event_id = value
|
||||
elif name == "LogicalResourceId":
|
||||
self.logical_resource_id = value
|
||||
elif name == "PhysicalResourceId":
|
||||
self.physical_resource_id = value
|
||||
elif name == "ResourceProperties":
|
||||
self.resource_properties = value
|
||||
elif name == "ResourceStatus":
|
||||
self.resource_status = value
|
||||
elif name == "ResourceStatusReason":
|
||||
self.resource_status_reason = value
|
||||
elif name == "ResourceType":
|
||||
self.resource_type = value
|
||||
elif name == "StackId":
|
||||
self.stack_id = value
|
||||
elif name == "StackName":
|
||||
self.stack_name = value
|
||||
elif name == "Timestamp":
|
||||
try:
|
||||
self.timestamp = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
|
||||
except ValueError:
|
||||
self.timestamp = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def __repr__(self):
|
||||
return "StackEvent %s %s %s" % (self.resource_type,
|
||||
self.logical_resource_id, self.resource_status)
|
|
@ -1,43 +0,0 @@
|
|||
from boto.resultset import ResultSet
|
||||
|
||||
class Template:
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.description = None
|
||||
self.template_parameters = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == "Parameters":
|
||||
self.template_parameters = ResultSet([('member', TemplateParameter)])
|
||||
return self.template_parameters
|
||||
else:
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == "Description":
|
||||
self.description = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
class TemplateParameter:
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
self.default_value = None
|
||||
self.description = None
|
||||
self.no_echo = None
|
||||
self.parameter_key = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == "DefaultValue":
|
||||
self.default_value = value
|
||||
elif name == "Description":
|
||||
self.description = value
|
||||
elif name == "NoEcho":
|
||||
self.no_echo = bool(value)
|
||||
elif name == "ParameterKey":
|
||||
self.parameter_key = value
|
||||
else:
|
||||
setattr(self, name, value)
|
|
@ -1,324 +0,0 @@
|
|||
# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
import xml.sax
|
||||
import time
|
||||
import boto
|
||||
from boto.connection import AWSAuthConnection
|
||||
from boto import handler
|
||||
from boto.cloudfront.distribution import Distribution, DistributionSummary, DistributionConfig
|
||||
from boto.cloudfront.distribution import StreamingDistribution, StreamingDistributionSummary, StreamingDistributionConfig
|
||||
from boto.cloudfront.identity import OriginAccessIdentity
|
||||
from boto.cloudfront.identity import OriginAccessIdentitySummary
|
||||
from boto.cloudfront.identity import OriginAccessIdentityConfig
|
||||
from boto.cloudfront.invalidation import InvalidationBatch, InvalidationSummary, InvalidationListResultSet
|
||||
from boto.resultset import ResultSet
|
||||
from boto.cloudfront.exception import CloudFrontServerError
|
||||
|
||||
|
||||
class CloudFrontConnection(AWSAuthConnection):
|
||||
|
||||
DefaultHost = 'cloudfront.amazonaws.com'
|
||||
Version = '2010-11-01'
|
||||
|
||||
def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
|
||||
port=None, proxy=None, proxy_port=None,
|
||||
host=DefaultHost, debug=0, security_token=None,
|
||||
validate_certs=True):
|
||||
AWSAuthConnection.__init__(self, host,
|
||||
aws_access_key_id, aws_secret_access_key,
|
||||
True, port, proxy, proxy_port, debug=debug,
|
||||
security_token=security_token,
|
||||
validate_certs=validate_certs)
|
||||
|
||||
def get_etag(self, response):
|
||||
response_headers = response.msg
|
||||
for key in response_headers.keys():
|
||||
if key.lower() == 'etag':
|
||||
return response_headers[key]
|
||||
return None
|
||||
|
||||
def _required_auth_capability(self):
|
||||
return ['cloudfront']
|
||||
|
||||
# Generics
|
||||
|
||||
def _get_all_objects(self, resource, tags, result_set_class=None,
|
||||
result_set_kwargs=None):
|
||||
if not tags:
|
||||
tags = [('DistributionSummary', DistributionSummary)]
|
||||
response = self.make_request('GET', '/%s/%s' % (self.Version,
|
||||
resource))
|
||||
body = response.read()
|
||||
boto.log.debug(body)
|
||||
if response.status >= 300:
|
||||
raise CloudFrontServerError(response.status, response.reason, body)
|
||||
rs_class = result_set_class or ResultSet
|
||||
rs_kwargs = result_set_kwargs or dict()
|
||||
rs = rs_class(tags, **rs_kwargs)
|
||||
h = handler.XmlHandler(rs, self)
|
||||
xml.sax.parseString(body, h)
|
||||
return rs
|
||||
|
||||
def _get_info(self, id, resource, dist_class):
|
||||
uri = '/%s/%s/%s' % (self.Version, resource, id)
|
||||
response = self.make_request('GET', uri)
|
||||
body = response.read()
|
||||
boto.log.debug(body)
|
||||
if response.status >= 300:
|
||||
raise CloudFrontServerError(response.status, response.reason, body)
|
||||
d = dist_class(connection=self)
|
||||
response_headers = response.msg
|
||||
for key in response_headers.keys():
|
||||
if key.lower() == 'etag':
|
||||
d.etag = response_headers[key]
|
||||
h = handler.XmlHandler(d, self)
|
||||
xml.sax.parseString(body, h)
|
||||
return d
|
||||
|
||||
def _get_config(self, id, resource, config_class):
|
||||
uri = '/%s/%s/%s/config' % (self.Version, resource, id)
|
||||
response = self.make_request('GET', uri)
|
||||
body = response.read()
|
||||
boto.log.debug(body)
|
||||
if response.status >= 300:
|
||||
raise CloudFrontServerError(response.status, response.reason, body)
|
||||
d = config_class(connection=self)
|
||||
d.etag = self.get_etag(response)
|
||||
h = handler.XmlHandler(d, self)
|
||||
xml.sax.parseString(body, h)
|
||||
return d
|
||||
|
||||
def _set_config(self, distribution_id, etag, config):
|
||||
if isinstance(config, StreamingDistributionConfig):
|
||||
resource = 'streaming-distribution'
|
||||
else:
|
||||
resource = 'distribution'
|
||||
uri = '/%s/%s/%s/config' % (self.Version, resource, distribution_id)
|
||||
headers = {'If-Match': etag, 'Content-Type': 'text/xml'}
|
||||
response = self.make_request('PUT', uri, headers, config.to_xml())
|
||||
body = response.read()
|
||||
boto.log.debug(body)
|
||||
if response.status != 200:
|
||||
raise CloudFrontServerError(response.status, response.reason, body)
|
||||
return self.get_etag(response)
|
||||
|
||||
def _create_object(self, config, resource, dist_class):
|
||||
response = self.make_request('POST', '/%s/%s' % (self.Version,
|
||||
resource),
|
||||
{'Content-Type': 'text/xml'},
|
||||
data=config.to_xml())
|
||||
body = response.read()
|
||||
boto.log.debug(body)
|
||||
if response.status == 201:
|
||||
d = dist_class(connection=self)
|
||||
h = handler.XmlHandler(d, self)
|
||||
xml.sax.parseString(body, h)
|
||||
d.etag = self.get_etag(response)
|
||||
return d
|
||||
else:
|
||||
raise CloudFrontServerError(response.status, response.reason, body)
|
||||
|
||||
def _delete_object(self, id, etag, resource):
|
||||
uri = '/%s/%s/%s' % (self.Version, resource, id)
|
||||
response = self.make_request('DELETE', uri, {'If-Match': etag})
|
||||
body = response.read()
|
||||
boto.log.debug(body)
|
||||
if response.status != 204:
|
||||
raise CloudFrontServerError(response.status, response.reason, body)
|
||||
|
||||
# Distributions
|
||||
|
||||
def get_all_distributions(self):
|
||||
tags = [('DistributionSummary', DistributionSummary)]
|
||||
return self._get_all_objects('distribution', tags)
|
||||
|
||||
def get_distribution_info(self, distribution_id):
|
||||
return self._get_info(distribution_id, 'distribution', Distribution)
|
||||
|
||||
def get_distribution_config(self, distribution_id):
|
||||
return self._get_config(distribution_id, 'distribution',
|
||||
DistributionConfig)
|
||||
|
||||
def set_distribution_config(self, distribution_id, etag, config):
|
||||
return self._set_config(distribution_id, etag, config)
|
||||
|
||||
def create_distribution(self, origin, enabled, caller_reference='',
|
||||
cnames=None, comment='', trusted_signers=None):
|
||||
config = DistributionConfig(origin=origin, enabled=enabled,
|
||||
caller_reference=caller_reference,
|
||||
cnames=cnames, comment=comment,
|
||||
trusted_signers=trusted_signers)
|
||||
return self._create_object(config, 'distribution', Distribution)
|
||||
|
||||
def delete_distribution(self, distribution_id, etag):
|
||||
return self._delete_object(distribution_id, etag, 'distribution')
|
||||
|
||||
# Streaming Distributions
|
||||
|
||||
def get_all_streaming_distributions(self):
|
||||
tags = [('StreamingDistributionSummary', StreamingDistributionSummary)]
|
||||
return self._get_all_objects('streaming-distribution', tags)
|
||||
|
||||
def get_streaming_distribution_info(self, distribution_id):
|
||||
return self._get_info(distribution_id, 'streaming-distribution',
|
||||
StreamingDistribution)
|
||||
|
||||
def get_streaming_distribution_config(self, distribution_id):
|
||||
return self._get_config(distribution_id, 'streaming-distribution',
|
||||
StreamingDistributionConfig)
|
||||
|
||||
def set_streaming_distribution_config(self, distribution_id, etag, config):
|
||||
return self._set_config(distribution_id, etag, config)
|
||||
|
||||
def create_streaming_distribution(self, origin, enabled,
|
||||
caller_reference='',
|
||||
cnames=None, comment='',
|
||||
trusted_signers=None):
|
||||
config = StreamingDistributionConfig(origin=origin, enabled=enabled,
|
||||
caller_reference=caller_reference,
|
||||
cnames=cnames, comment=comment,
|
||||
trusted_signers=trusted_signers)
|
||||
return self._create_object(config, 'streaming-distribution',
|
||||
StreamingDistribution)
|
||||
|
||||
def delete_streaming_distribution(self, distribution_id, etag):
|
||||
return self._delete_object(distribution_id, etag,
|
||||
'streaming-distribution')
|
||||
|
||||
# Origin Access Identity
|
||||
|
||||
def get_all_origin_access_identity(self):
|
||||
tags = [('CloudFrontOriginAccessIdentitySummary',
|
||||
OriginAccessIdentitySummary)]
|
||||
return self._get_all_objects('origin-access-identity/cloudfront', tags)
|
||||
|
||||
def get_origin_access_identity_info(self, access_id):
|
||||
return self._get_info(access_id, 'origin-access-identity/cloudfront',
|
||||
OriginAccessIdentity)
|
||||
|
||||
def get_origin_access_identity_config(self, access_id):
|
||||
return self._get_config(access_id,
|
||||
'origin-access-identity/cloudfront',
|
||||
OriginAccessIdentityConfig)
|
||||
|
||||
def set_origin_access_identity_config(self, access_id,
|
||||
etag, config):
|
||||
return self._set_config(access_id, etag, config)
|
||||
|
||||
def create_origin_access_identity(self, caller_reference='', comment=''):
|
||||
config = OriginAccessIdentityConfig(caller_reference=caller_reference,
|
||||
comment=comment)
|
||||
return self._create_object(config, 'origin-access-identity/cloudfront',
|
||||
OriginAccessIdentity)
|
||||
|
||||
def delete_origin_access_identity(self, access_id, etag):
|
||||
return self._delete_object(access_id, etag,
|
||||
'origin-access-identity/cloudfront')
|
||||
|
||||
# Object Invalidation
|
||||
|
||||
def create_invalidation_request(self, distribution_id, paths,
|
||||
caller_reference=None):
|
||||
"""Creates a new invalidation request
|
||||
:see: http://goo.gl/8vECq
|
||||
"""
|
||||
# We allow you to pass in either an array or
|
||||
# an InvalidationBatch object
|
||||
if not isinstance(paths, InvalidationBatch):
|
||||
paths = InvalidationBatch(paths)
|
||||
paths.connection = self
|
||||
uri = '/%s/distribution/%s/invalidation' % (self.Version,
|
||||
distribution_id)
|
||||
response = self.make_request('POST', uri,
|
||||
{'Content-Type': 'text/xml'},
|
||||
data=paths.to_xml())
|
||||
body = response.read()
|
||||
if response.status == 201:
|
||||
h = handler.XmlHandler(paths, self)
|
||||
xml.sax.parseString(body, h)
|
||||
return paths
|
||||
else:
|
||||
raise CloudFrontServerError(response.status, response.reason, body)
|
||||
|
||||
def invalidation_request_status(self, distribution_id,
|
||||
request_id, caller_reference=None):
|
||||
uri = '/%s/distribution/%s/invalidation/%s' % (self.Version,
|
||||
distribution_id,
|
||||
request_id)
|
||||
response = self.make_request('GET', uri, {'Content-Type': 'text/xml'})
|
||||
body = response.read()
|
||||
if response.status == 200:
|
||||
paths = InvalidationBatch([])
|
||||
h = handler.XmlHandler(paths, self)
|
||||
xml.sax.parseString(body, h)
|
||||
return paths
|
||||
else:
|
||||
raise CloudFrontServerError(response.status, response.reason, body)
|
||||
|
||||
def get_invalidation_requests(self, distribution_id, marker=None,
|
||||
max_items=None):
|
||||
"""
|
||||
Get all invalidation requests for a given CloudFront distribution.
|
||||
This returns an instance of an InvalidationListResultSet that
|
||||
automatically handles all of the result paging, etc. from CF - you just
|
||||
need to keep iterating until there are no more results.
|
||||
|
||||
:type distribution_id: string
|
||||
:param distribution_id: The id of the CloudFront distribution
|
||||
|
||||
:type marker: string
|
||||
:param marker: Use this only when paginating results and only in
|
||||
follow-up request after you've received a response where
|
||||
the results are truncated. Set this to the value of the
|
||||
Marker element in the response you just received.
|
||||
|
||||
:type max_items: int
|
||||
:param max_items: Use this only when paginating results and only in a
|
||||
follow-up request to indicate the maximum number of
|
||||
invalidation requests you want in the response. You
|
||||
will need to pass the next_marker property from the
|
||||
previous InvalidationListResultSet response in the
|
||||
follow-up request in order to get the next 'page' of
|
||||
results.
|
||||
|
||||
:rtype: :class:`boto.cloudfront.invalidation.InvalidationListResultSet`
|
||||
:returns: An InvalidationListResultSet iterator that lists invalidation
|
||||
requests for a given CloudFront distribution. Automatically
|
||||
handles paging the results.
|
||||
"""
|
||||
uri = 'distribution/%s/invalidation' % distribution_id
|
||||
params = dict()
|
||||
if marker:
|
||||
params['Marker'] = marker
|
||||
if max_items:
|
||||
params['MaxItems'] = max_items
|
||||
if params:
|
||||
uri += '?%s=%s' % params.popitem()
|
||||
for k, v in params.items():
|
||||
uri += '&%s=%s' % (k, v)
|
||||
tags=[('InvalidationSummary', InvalidationSummary)]
|
||||
rs_class = InvalidationListResultSet
|
||||
rs_kwargs = dict(connection=self, distribution_id=distribution_id,
|
||||
max_items=max_items, marker=marker)
|
||||
return self._get_all_objects(uri, tags, result_set_class=rs_class,
|
||||
result_set_kwargs=rs_kwargs)
|
|
@ -1,747 +0,0 @@
|
|||
# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
import uuid
|
||||
import base64
|
||||
import time
|
||||
from boto.compat import json
|
||||
from boto.cloudfront.identity import OriginAccessIdentity
|
||||
from boto.cloudfront.object import Object, StreamingObject
|
||||
from boto.cloudfront.signers import ActiveTrustedSigners, TrustedSigners
|
||||
from boto.cloudfront.logging import LoggingInfo
|
||||
from boto.cloudfront.origin import S3Origin, CustomOrigin
|
||||
from boto.s3.acl import ACL
|
||||
|
||||
class DistributionConfig(object):
|
||||
|
||||
def __init__(self, connection=None, origin=None, enabled=False,
|
||||
caller_reference='', cnames=None, comment='',
|
||||
trusted_signers=None, default_root_object=None,
|
||||
logging=None):
|
||||
"""
|
||||
:param origin: Origin information to associate with the
|
||||
distribution. If your distribution will use
|
||||
an Amazon S3 origin, then this should be an
|
||||
S3Origin object. If your distribution will use
|
||||
a custom origin (non Amazon S3), then this
|
||||
should be a CustomOrigin object.
|
||||
:type origin: :class:`boto.cloudfront.origin.S3Origin` or
|
||||
:class:`boto.cloudfront.origin.CustomOrigin`
|
||||
|
||||
:param enabled: Whether the distribution is enabled to accept
|
||||
end user requests for content.
|
||||
:type enabled: bool
|
||||
|
||||
:param caller_reference: A unique number that ensures the
|
||||
request can't be replayed. If no
|
||||
caller_reference is provided, boto
|
||||
will generate a type 4 UUID for use
|
||||
as the caller reference.
|
||||
:type enabled: str
|
||||
|
||||
:param cnames: A CNAME alias you want to associate with this
|
||||
distribution. You can have up to 10 CNAME aliases
|
||||
per distribution.
|
||||
:type enabled: array of str
|
||||
|
||||
:param comment: Any comments you want to include about the
|
||||
distribution.
|
||||
:type comment: str
|
||||
|
||||
:param trusted_signers: Specifies any AWS accounts you want to
|
||||
permit to create signed URLs for private
|
||||
content. If you want the distribution to
|
||||
use signed URLs, this should contain a
|
||||
TrustedSigners object; if you want the
|
||||
distribution to use basic URLs, leave
|
||||
this None.
|
||||
:type trusted_signers: :class`boto.cloudfront.signers.TrustedSigners`
|
||||
|
||||
:param default_root_object: Designates a default root object.
|
||||
Only include a DefaultRootObject value
|
||||
if you are going to assign a default
|
||||
root object for the distribution.
|
||||
:type comment: str
|
||||
|
||||
:param logging: Controls whether access logs are written for the
|
||||
distribution. If you want to turn on access logs,
|
||||
this should contain a LoggingInfo object; otherwise
|
||||
it should contain None.
|
||||
:type logging: :class`boto.cloudfront.logging.LoggingInfo`
|
||||
|
||||
"""
|
||||
self.connection = connection
|
||||
self.origin = origin
|
||||
self.enabled = enabled
|
||||
if caller_reference:
|
||||
self.caller_reference = caller_reference
|
||||
else:
|
||||
self.caller_reference = str(uuid.uuid4())
|
||||
self.cnames = []
|
||||
if cnames:
|
||||
self.cnames = cnames
|
||||
self.comment = comment
|
||||
self.trusted_signers = trusted_signers
|
||||
self.logging = logging
|
||||
self.default_root_object = default_root_object
|
||||
|
||||
def to_xml(self):
|
||||
s = '<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
s += '<DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2010-07-15/">\n'
|
||||
if self.origin:
|
||||
s += self.origin.to_xml()
|
||||
s += ' <CallerReference>%s</CallerReference>\n' % self.caller_reference
|
||||
for cname in self.cnames:
|
||||
s += ' <CNAME>%s</CNAME>\n' % cname
|
||||
if self.comment:
|
||||
s += ' <Comment>%s</Comment>\n' % self.comment
|
||||
s += ' <Enabled>'
|
||||
if self.enabled:
|
||||
s += 'true'
|
||||
else:
|
||||
s += 'false'
|
||||
s += '</Enabled>\n'
|
||||
if self.trusted_signers:
|
||||
s += '<TrustedSigners>\n'
|
||||
for signer in self.trusted_signers:
|
||||
if signer == 'Self':
|
||||
s += ' <Self></Self>\n'
|
||||
else:
|
||||
s += ' <AwsAccountNumber>%s</AwsAccountNumber>\n' % signer
|
||||
s += '</TrustedSigners>\n'
|
||||
if self.logging:
|
||||
s += '<Logging>\n'
|
||||
s += ' <Bucket>%s</Bucket>\n' % self.logging.bucket
|
||||
s += ' <Prefix>%s</Prefix>\n' % self.logging.prefix
|
||||
s += '</Logging>\n'
|
||||
if self.default_root_object:
|
||||
dro = self.default_root_object
|
||||
s += '<DefaultRootObject>%s</DefaultRootObject>\n' % dro
|
||||
s += '</DistributionConfig>\n'
|
||||
return s
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'TrustedSigners':
|
||||
self.trusted_signers = TrustedSigners()
|
||||
return self.trusted_signers
|
||||
elif name == 'Logging':
|
||||
self.logging = LoggingInfo()
|
||||
return self.logging
|
||||
elif name == 'S3Origin':
|
||||
self.origin = S3Origin()
|
||||
return self.origin
|
||||
elif name == 'CustomOrigin':
|
||||
self.origin = CustomOrigin()
|
||||
return self.origin
|
||||
else:
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'CNAME':
|
||||
self.cnames.append(value)
|
||||
elif name == 'Comment':
|
||||
self.comment = value
|
||||
elif name == 'Enabled':
|
||||
if value.lower() == 'true':
|
||||
self.enabled = True
|
||||
else:
|
||||
self.enabled = False
|
||||
elif name == 'CallerReference':
|
||||
self.caller_reference = value
|
||||
elif name == 'DefaultRootObject':
|
||||
self.default_root_object = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
class StreamingDistributionConfig(DistributionConfig):
|
||||
|
||||
def __init__(self, connection=None, origin='', enabled=False,
|
||||
caller_reference='', cnames=None, comment='',
|
||||
trusted_signers=None, logging=None):
|
||||
DistributionConfig.__init__(self, connection=connection,
|
||||
origin=origin, enabled=enabled,
|
||||
caller_reference=caller_reference,
|
||||
cnames=cnames, comment=comment,
|
||||
trusted_signers=trusted_signers,
|
||||
logging=logging)
|
||||
def to_xml(self):
|
||||
s = '<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
s += '<StreamingDistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2010-07-15/">\n'
|
||||
if self.origin:
|
||||
s += self.origin.to_xml()
|
||||
s += ' <CallerReference>%s</CallerReference>\n' % self.caller_reference
|
||||
for cname in self.cnames:
|
||||
s += ' <CNAME>%s</CNAME>\n' % cname
|
||||
if self.comment:
|
||||
s += ' <Comment>%s</Comment>\n' % self.comment
|
||||
s += ' <Enabled>'
|
||||
if self.enabled:
|
||||
s += 'true'
|
||||
else:
|
||||
s += 'false'
|
||||
s += '</Enabled>\n'
|
||||
if self.trusted_signers:
|
||||
s += '<TrustedSigners>\n'
|
||||
for signer in self.trusted_signers:
|
||||
if signer == 'Self':
|
||||
s += ' <Self/>\n'
|
||||
else:
|
||||
s += ' <AwsAccountNumber>%s</AwsAccountNumber>\n' % signer
|
||||
s += '</TrustedSigners>\n'
|
||||
if self.logging:
|
||||
s += '<Logging>\n'
|
||||
s += ' <Bucket>%s</Bucket>\n' % self.logging.bucket
|
||||
s += ' <Prefix>%s</Prefix>\n' % self.logging.prefix
|
||||
s += '</Logging>\n'
|
||||
s += '</StreamingDistributionConfig>\n'
|
||||
return s
|
||||
|
||||
class DistributionSummary(object):
|
||||
|
||||
def __init__(self, connection=None, domain_name='', id='',
|
||||
last_modified_time=None, status='', origin=None,
|
||||
cname='', comment='', enabled=False):
|
||||
self.connection = connection
|
||||
self.domain_name = domain_name
|
||||
self.id = id
|
||||
self.last_modified_time = last_modified_time
|
||||
self.status = status
|
||||
self.origin = origin
|
||||
self.enabled = enabled
|
||||
self.cnames = []
|
||||
if cname:
|
||||
self.cnames.append(cname)
|
||||
self.comment = comment
|
||||
self.trusted_signers = None
|
||||
self.etag = None
|
||||
self.streaming = False
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'TrustedSigners':
|
||||
self.trusted_signers = TrustedSigners()
|
||||
return self.trusted_signers
|
||||
elif name == 'S3Origin':
|
||||
self.origin = S3Origin()
|
||||
return self.origin
|
||||
elif name == 'CustomOrigin':
|
||||
self.origin = CustomOrigin()
|
||||
return self.origin
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Id':
|
||||
self.id = value
|
||||
elif name == 'Status':
|
||||
self.status = value
|
||||
elif name == 'LastModifiedTime':
|
||||
self.last_modified_time = value
|
||||
elif name == 'DomainName':
|
||||
self.domain_name = value
|
||||
elif name == 'Origin':
|
||||
self.origin = value
|
||||
elif name == 'CNAME':
|
||||
self.cnames.append(value)
|
||||
elif name == 'Comment':
|
||||
self.comment = value
|
||||
elif name == 'Enabled':
|
||||
if value.lower() == 'true':
|
||||
self.enabled = True
|
||||
else:
|
||||
self.enabled = False
|
||||
elif name == 'StreamingDistributionSummary':
|
||||
self.streaming = True
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def get_distribution(self):
|
||||
return self.connection.get_distribution_info(self.id)
|
||||
|
||||
class StreamingDistributionSummary(DistributionSummary):
|
||||
|
||||
def get_distribution(self):
|
||||
return self.connection.get_streaming_distribution_info(self.id)
|
||||
|
||||
class Distribution(object):
|
||||
|
||||
def __init__(self, connection=None, config=None, domain_name='',
|
||||
id='', last_modified_time=None, status=''):
|
||||
self.connection = connection
|
||||
self.config = config
|
||||
self.domain_name = domain_name
|
||||
self.id = id
|
||||
self.last_modified_time = last_modified_time
|
||||
self.status = status
|
||||
self.in_progress_invalidation_batches = 0
|
||||
self.active_signers = None
|
||||
self.etag = None
|
||||
self._bucket = None
|
||||
self._object_class = Object
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'DistributionConfig':
|
||||
self.config = DistributionConfig()
|
||||
return self.config
|
||||
elif name == 'ActiveTrustedSigners':
|
||||
self.active_signers = ActiveTrustedSigners()
|
||||
return self.active_signers
|
||||
else:
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Id':
|
||||
self.id = value
|
||||
elif name == 'LastModifiedTime':
|
||||
self.last_modified_time = value
|
||||
elif name == 'Status':
|
||||
self.status = value
|
||||
elif name == 'InProgressInvalidationBatches':
|
||||
self.in_progress_invalidation_batches = int(value)
|
||||
elif name == 'DomainName':
|
||||
self.domain_name = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def update(self, enabled=None, cnames=None, comment=None):
|
||||
"""
|
||||
Update the configuration of the Distribution. The only values
|
||||
of the DistributionConfig that can be directly updated are:
|
||||
|
||||
* CNAMES
|
||||
* Comment
|
||||
* Whether the Distribution is enabled or not
|
||||
|
||||
Any changes to the ``trusted_signers`` or ``origin`` properties of
|
||||
this distribution's current config object will also be included in
|
||||
the update. Therefore, to set the origin access identity for this
|
||||
distribution, set ``Distribution.config.origin.origin_access_identity``
|
||||
before calling this update method.
|
||||
|
||||
:type enabled: bool
|
||||
:param enabled: Whether the Distribution is active or not.
|
||||
|
||||
:type cnames: list of str
|
||||
:param cnames: The DNS CNAME's associated with this
|
||||
Distribution. Maximum of 10 values.
|
||||
|
||||
:type comment: str or unicode
|
||||
:param comment: The comment associated with the Distribution.
|
||||
|
||||
"""
|
||||
new_config = DistributionConfig(self.connection, self.config.origin,
|
||||
self.config.enabled, self.config.caller_reference,
|
||||
self.config.cnames, self.config.comment,
|
||||
self.config.trusted_signers,
|
||||
self.config.default_root_object)
|
||||
if enabled != None:
|
||||
new_config.enabled = enabled
|
||||
if cnames != None:
|
||||
new_config.cnames = cnames
|
||||
if comment != None:
|
||||
new_config.comment = comment
|
||||
self.etag = self.connection.set_distribution_config(self.id, self.etag, new_config)
|
||||
self.config = new_config
|
||||
self._object_class = Object
|
||||
|
||||
def enable(self):
|
||||
"""
|
||||
Activate the Distribution. A convenience wrapper around
|
||||
the update method.
|
||||
"""
|
||||
self.update(enabled=True)
|
||||
|
||||
def disable(self):
|
||||
"""
|
||||
Deactivate the Distribution. A convenience wrapper around
|
||||
the update method.
|
||||
"""
|
||||
self.update(enabled=False)
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
Delete this CloudFront Distribution. The content
|
||||
associated with the Distribution is not deleted from
|
||||
the underlying Origin bucket in S3.
|
||||
"""
|
||||
self.connection.delete_distribution(self.id, self.etag)
|
||||
|
||||
def _get_bucket(self):
|
||||
if isinstance(self.config.origin, S3Origin):
|
||||
if not self._bucket:
|
||||
bucket_dns_name = self.config.origin.dns_name
|
||||
bucket_name = bucket_dns_name.replace('.s3.amazonaws.com', '')
|
||||
from boto.s3.connection import S3Connection
|
||||
s3 = S3Connection(self.connection.aws_access_key_id,
|
||||
self.connection.aws_secret_access_key,
|
||||
proxy=self.connection.proxy,
|
||||
proxy_port=self.connection.proxy_port,
|
||||
proxy_user=self.connection.proxy_user,
|
||||
proxy_pass=self.connection.proxy_pass)
|
||||
self._bucket = s3.get_bucket(bucket_name)
|
||||
self._bucket.distribution = self
|
||||
self._bucket.set_key_class(self._object_class)
|
||||
return self._bucket
|
||||
else:
|
||||
raise NotImplementedError('Unable to get_objects on CustomOrigin')
|
||||
|
||||
def get_objects(self):
|
||||
"""
|
||||
Return a list of all content objects in this distribution.
|
||||
|
||||
:rtype: list of :class:`boto.cloudfront.object.Object`
|
||||
:return: The content objects
|
||||
"""
|
||||
bucket = self._get_bucket()
|
||||
objs = []
|
||||
for key in bucket:
|
||||
objs.append(key)
|
||||
return objs
|
||||
|
||||
def set_permissions(self, object, replace=False):
|
||||
"""
|
||||
Sets the S3 ACL grants for the given object to the appropriate
|
||||
value based on the type of Distribution. If the Distribution
|
||||
is serving private content the ACL will be set to include the
|
||||
Origin Access Identity associated with the Distribution. If
|
||||
the Distribution is serving public content the content will
|
||||
be set up with "public-read".
|
||||
|
||||
:type object: :class:`boto.cloudfront.object.Object`
|
||||
:param enabled: The Object whose ACL is being set
|
||||
|
||||
:type replace: bool
|
||||
:param replace: If False, the Origin Access Identity will be
|
||||
appended to the existing ACL for the object.
|
||||
If True, the ACL for the object will be
|
||||
completely replaced with one that grants
|
||||
READ permission to the Origin Access Identity.
|
||||
|
||||
"""
|
||||
if isinstance(self.config.origin, S3Origin):
|
||||
if self.config.origin.origin_access_identity:
|
||||
id = self.config.origin.origin_access_identity.split('/')[-1]
|
||||
oai = self.connection.get_origin_access_identity_info(id)
|
||||
policy = object.get_acl()
|
||||
if replace:
|
||||
policy.acl = ACL()
|
||||
policy.acl.add_user_grant('READ', oai.s3_user_id)
|
||||
object.set_acl(policy)
|
||||
else:
|
||||
object.set_canned_acl('public-read')
|
||||
|
||||
def set_permissions_all(self, replace=False):
|
||||
"""
|
||||
Sets the S3 ACL grants for all objects in the Distribution
|
||||
to the appropriate value based on the type of Distribution.
|
||||
|
||||
:type replace: bool
|
||||
:param replace: If False, the Origin Access Identity will be
|
||||
appended to the existing ACL for the object.
|
||||
If True, the ACL for the object will be
|
||||
completely replaced with one that grants
|
||||
READ permission to the Origin Access Identity.
|
||||
|
||||
"""
|
||||
bucket = self._get_bucket()
|
||||
for key in bucket:
|
||||
self.set_permissions(key, replace)
|
||||
|
||||
def add_object(self, name, content, headers=None, replace=True):
|
||||
"""
|
||||
Adds a new content object to the Distribution. The content
|
||||
for the object will be copied to a new Key in the S3 Bucket
|
||||
and the permissions will be set appropriately for the type
|
||||
of Distribution.
|
||||
|
||||
:type name: str or unicode
|
||||
:param name: The name or key of the new object.
|
||||
|
||||
:type content: file-like object
|
||||
:param content: A file-like object that contains the content
|
||||
for the new object.
|
||||
|
||||
:type headers: dict
|
||||
:param headers: A dictionary containing additional headers
|
||||
you would like associated with the new
|
||||
object in S3.
|
||||
|
||||
:rtype: :class:`boto.cloudfront.object.Object`
|
||||
:return: The newly created object.
|
||||
"""
|
||||
if self.config.origin.origin_access_identity:
|
||||
policy = 'private'
|
||||
else:
|
||||
policy = 'public-read'
|
||||
bucket = self._get_bucket()
|
||||
object = bucket.new_key(name)
|
||||
object.set_contents_from_file(content, headers=headers, policy=policy)
|
||||
if self.config.origin.origin_access_identity:
|
||||
self.set_permissions(object, replace)
|
||||
return object
|
||||
|
||||
def create_signed_url(self, url, keypair_id,
|
||||
expire_time=None, valid_after_time=None,
|
||||
ip_address=None, policy_url=None,
|
||||
private_key_file=None, private_key_string=None):
|
||||
"""
|
||||
Creates a signed CloudFront URL that is only valid within the specified
|
||||
parameters.
|
||||
|
||||
:type url: str
|
||||
:param url: The URL of the protected object.
|
||||
|
||||
:type keypair_id: str
|
||||
:param keypair_id: The keypair ID of the Amazon KeyPair used to sign
|
||||
theURL. This ID MUST correspond to the private key
|
||||
specified with private_key_file or private_key_string.
|
||||
|
||||
:type expire_time: int
|
||||
:param expire_time: The expiry time of the URL. If provided, the URL
|
||||
will expire after the time has passed. If not provided the URL will
|
||||
never expire. Format is a unix epoch.
|
||||
Use time.time() + duration_in_sec.
|
||||
|
||||
:type valid_after_time: int
|
||||
:param valid_after_time: If provided, the URL will not be valid until
|
||||
after valid_after_time. Format is a unix epoch.
|
||||
Use time.time() + secs_until_valid.
|
||||
|
||||
:type ip_address: str
|
||||
:param ip_address: If provided, only allows access from the specified
|
||||
IP address. Use '192.168.0.10' for a single IP or
|
||||
use '192.168.0.0/24' CIDR notation for a subnet.
|
||||
|
||||
:type policy_url: str
|
||||
:param policy_url: If provided, allows the signature to contain
|
||||
wildcard globs in the URL. For example, you could
|
||||
provide: 'http://example.com/media/\*' and the policy
|
||||
and signature would allow access to all contents of
|
||||
the media subdirectory. If not specified, only
|
||||
allow access to the exact url provided in 'url'.
|
||||
|
||||
:type private_key_file: str or file object.
|
||||
:param private_key_file: If provided, contains the filename of the
|
||||
private key file used for signing or an open
|
||||
file object containing the private key
|
||||
contents. Only one of private_key_file or
|
||||
private_key_string can be provided.
|
||||
|
||||
:type private_key_string: str
|
||||
:param private_key_string: If provided, contains the private key string
|
||||
used for signing. Only one of private_key_file or
|
||||
private_key_string can be provided.
|
||||
|
||||
:rtype: str
|
||||
:return: The signed URL.
|
||||
"""
|
||||
# Get the required parameters
|
||||
params = self._create_signing_params(
|
||||
url=url, keypair_id=keypair_id, expire_time=expire_time,
|
||||
valid_after_time=valid_after_time, ip_address=ip_address,
|
||||
policy_url=policy_url, private_key_file=private_key_file,
|
||||
private_key_string=private_key_string)
|
||||
|
||||
#combine these into a full url
|
||||
if "?" in url:
|
||||
sep = "&"
|
||||
else:
|
||||
sep = "?"
|
||||
signed_url_params = []
|
||||
for key in ["Expires", "Policy", "Signature", "Key-Pair-Id"]:
|
||||
if key in params:
|
||||
param = "%s=%s" % (key, params[key])
|
||||
signed_url_params.append(param)
|
||||
signed_url = url + sep + "&".join(signed_url_params)
|
||||
return signed_url
|
||||
|
||||
def _create_signing_params(self, url, keypair_id,
|
||||
expire_time=None, valid_after_time=None,
|
||||
ip_address=None, policy_url=None,
|
||||
private_key_file=None, private_key_string=None):
|
||||
"""
|
||||
Creates the required URL parameters for a signed URL.
|
||||
"""
|
||||
params = {}
|
||||
# Check if we can use a canned policy
|
||||
if expire_time and not valid_after_time and not ip_address and not policy_url:
|
||||
# we manually construct this policy string to ensure formatting
|
||||
# matches signature
|
||||
policy = self._canned_policy(url, expire_time)
|
||||
params["Expires"] = str(expire_time)
|
||||
else:
|
||||
# If no policy_url is specified, default to the full url.
|
||||
if policy_url is None:
|
||||
policy_url = url
|
||||
# Can't use canned policy
|
||||
policy = self._custom_policy(policy_url, expires=expire_time,
|
||||
valid_after=valid_after_time,
|
||||
ip_address=ip_address)
|
||||
|
||||
encoded_policy = self._url_base64_encode(policy)
|
||||
params["Policy"] = encoded_policy
|
||||
#sign the policy
|
||||
signature = self._sign_string(policy, private_key_file, private_key_string)
|
||||
#now base64 encode the signature (URL safe as well)
|
||||
encoded_signature = self._url_base64_encode(signature)
|
||||
params["Signature"] = encoded_signature
|
||||
params["Key-Pair-Id"] = keypair_id
|
||||
return params
|
||||
|
||||
@staticmethod
|
||||
def _canned_policy(resource, expires):
|
||||
"""
|
||||
Creates a canned policy string.
|
||||
"""
|
||||
policy = ('{"Statement":[{"Resource":"%(resource)s",'
|
||||
'"Condition":{"DateLessThan":{"AWS:EpochTime":'
|
||||
'%(expires)s}}}]}' % locals())
|
||||
return policy
|
||||
|
||||
@staticmethod
|
||||
def _custom_policy(resource, expires=None, valid_after=None, ip_address=None):
|
||||
"""
|
||||
Creates a custom policy string based on the supplied parameters.
|
||||
"""
|
||||
condition = {}
|
||||
# SEE: http://docs.amazonwebservices.com/AmazonCloudFront/latest/DeveloperGuide/RestrictingAccessPrivateContent.html#CustomPolicy
|
||||
# The 'DateLessThan' property is required.
|
||||
if not expires:
|
||||
# Defaults to ONE day
|
||||
expires = int(time.time()) + 86400
|
||||
condition["DateLessThan"] = {"AWS:EpochTime": expires}
|
||||
if valid_after:
|
||||
condition["DateGreaterThan"] = {"AWS:EpochTime": valid_after}
|
||||
if ip_address:
|
||||
if '/' not in ip_address:
|
||||
ip_address += "/32"
|
||||
condition["IpAddress"] = {"AWS:SourceIp": ip_address}
|
||||
policy = {"Statement": [{
|
||||
"Resource": resource,
|
||||
"Condition": condition}]}
|
||||
return json.dumps(policy, separators=(",", ":"))
|
||||
|
||||
@staticmethod
|
||||
def _sign_string(message, private_key_file=None, private_key_string=None):
|
||||
"""
|
||||
Signs a string for use with Amazon CloudFront.
|
||||
Requires the rsa library be installed.
|
||||
"""
|
||||
try:
|
||||
import rsa
|
||||
except ImportError:
|
||||
raise NotImplementedError("Boto depends on the python rsa "
|
||||
"library to generate signed URLs for "
|
||||
"CloudFront")
|
||||
# Make sure only one of private_key_file and private_key_string is set
|
||||
if private_key_file and private_key_string:
|
||||
raise ValueError("Only specify the private_key_file or the private_key_string not both")
|
||||
if not private_key_file and not private_key_string:
|
||||
raise ValueError("You must specify one of private_key_file or private_key_string")
|
||||
# If private_key_file is a file name, open it and read it
|
||||
if private_key_string is None:
|
||||
if isinstance(private_key_file, basestring):
|
||||
with open(private_key_file, 'r') as file_handle:
|
||||
private_key_string = file_handle.read()
|
||||
# Otherwise, treat it like a file
|
||||
else:
|
||||
private_key_string = private_key_file.read()
|
||||
|
||||
# Sign it!
|
||||
private_key = rsa.PrivateKey.load_pkcs1(private_key_string)
|
||||
signature = rsa.sign(str(message), private_key, 'SHA-1')
|
||||
return signature
|
||||
|
||||
@staticmethod
|
||||
def _url_base64_encode(msg):
|
||||
"""
|
||||
Base64 encodes a string using the URL-safe characters specified by
|
||||
Amazon.
|
||||
"""
|
||||
msg_base64 = base64.b64encode(msg)
|
||||
msg_base64 = msg_base64.replace('+', '-')
|
||||
msg_base64 = msg_base64.replace('=', '_')
|
||||
msg_base64 = msg_base64.replace('/', '~')
|
||||
return msg_base64
|
||||
|
||||
class StreamingDistribution(Distribution):
|
||||
|
||||
def __init__(self, connection=None, config=None, domain_name='',
|
||||
id='', last_modified_time=None, status=''):
|
||||
Distribution.__init__(self, connection, config, domain_name,
|
||||
id, last_modified_time, status)
|
||||
self._object_class = StreamingObject
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'StreamingDistributionConfig':
|
||||
self.config = StreamingDistributionConfig()
|
||||
return self.config
|
||||
else:
|
||||
return Distribution.startElement(self, name, attrs, connection)
|
||||
|
||||
def update(self, enabled=None, cnames=None, comment=None):
|
||||
"""
|
||||
Update the configuration of the StreamingDistribution. The only values
|
||||
of the StreamingDistributionConfig that can be directly updated are:
|
||||
|
||||
* CNAMES
|
||||
* Comment
|
||||
* Whether the Distribution is enabled or not
|
||||
|
||||
Any changes to the ``trusted_signers`` or ``origin`` properties of
|
||||
this distribution's current config object will also be included in
|
||||
the update. Therefore, to set the origin access identity for this
|
||||
distribution, set
|
||||
``StreamingDistribution.config.origin.origin_access_identity``
|
||||
before calling this update method.
|
||||
|
||||
:type enabled: bool
|
||||
:param enabled: Whether the StreamingDistribution is active or not.
|
||||
|
||||
:type cnames: list of str
|
||||
:param cnames: The DNS CNAME's associated with this
|
||||
Distribution. Maximum of 10 values.
|
||||
|
||||
:type comment: str or unicode
|
||||
:param comment: The comment associated with the Distribution.
|
||||
|
||||
"""
|
||||
new_config = StreamingDistributionConfig(self.connection,
|
||||
self.config.origin,
|
||||
self.config.enabled,
|
||||
self.config.caller_reference,
|
||||
self.config.cnames,
|
||||
self.config.comment,
|
||||
self.config.trusted_signers)
|
||||
if enabled != None:
|
||||
new_config.enabled = enabled
|
||||
if cnames != None:
|
||||
new_config.cnames = cnames
|
||||
if comment != None:
|
||||
new_config.comment = comment
|
||||
self.etag = self.connection.set_streaming_distribution_config(self.id,
|
||||
self.etag,
|
||||
new_config)
|
||||
self.config = new_config
|
||||
self._object_class = StreamingObject
|
||||
|
||||
def delete(self):
|
||||
self.connection.delete_streaming_distribution(self.id, self.etag)
|
||||
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
from boto.exception import BotoServerError
|
||||
|
||||
class CloudFrontServerError(BotoServerError):
|
||||
|
||||
pass
|
|
@ -1,122 +0,0 @@
|
|||
# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
import uuid
|
||||
|
||||
class OriginAccessIdentity:
|
||||
|
||||
def __init__(self, connection=None, config=None, id='',
|
||||
s3_user_id='', comment=''):
|
||||
self.connection = connection
|
||||
self.config = config
|
||||
self.id = id
|
||||
self.s3_user_id = s3_user_id
|
||||
self.comment = comment
|
||||
self.etag = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'CloudFrontOriginAccessIdentityConfig':
|
||||
self.config = OriginAccessIdentityConfig()
|
||||
return self.config
|
||||
else:
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Id':
|
||||
self.id = value
|
||||
elif name == 'S3CanonicalUserId':
|
||||
self.s3_user_id = value
|
||||
elif name == 'Comment':
|
||||
self.comment = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def update(self, comment=None):
|
||||
new_config = OriginAccessIdentityConfig(self.connection,
|
||||
self.config.caller_reference,
|
||||
self.config.comment)
|
||||
if comment != None:
|
||||
new_config.comment = comment
|
||||
self.etag = self.connection.set_origin_identity_config(self.id, self.etag, new_config)
|
||||
self.config = new_config
|
||||
|
||||
def delete(self):
|
||||
return self.connection.delete_origin_access_identity(self.id, self.etag)
|
||||
|
||||
def uri(self):
|
||||
return 'origin-access-identity/cloudfront/%s' % self.id
|
||||
|
||||
class OriginAccessIdentityConfig:
|
||||
|
||||
def __init__(self, connection=None, caller_reference='', comment=''):
|
||||
self.connection = connection
|
||||
if caller_reference:
|
||||
self.caller_reference = caller_reference
|
||||
else:
|
||||
self.caller_reference = str(uuid.uuid4())
|
||||
self.comment = comment
|
||||
|
||||
def to_xml(self):
|
||||
s = '<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
s += '<CloudFrontOriginAccessIdentityConfig xmlns="http://cloudfront.amazonaws.com/doc/2009-09-09/">\n'
|
||||
s += ' <CallerReference>%s</CallerReference>\n' % self.caller_reference
|
||||
if self.comment:
|
||||
s += ' <Comment>%s</Comment>\n' % self.comment
|
||||
s += '</CloudFrontOriginAccessIdentityConfig>\n'
|
||||
return s
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Comment':
|
||||
self.comment = value
|
||||
elif name == 'CallerReference':
|
||||
self.caller_reference = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
class OriginAccessIdentitySummary:
|
||||
|
||||
def __init__(self, connection=None, id='',
|
||||
s3_user_id='', comment=''):
|
||||
self.connection = connection
|
||||
self.id = id
|
||||
self.s3_user_id = s3_user_id
|
||||
self.comment = comment
|
||||
self.etag = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Id':
|
||||
self.id = value
|
||||
elif name == 'S3CanonicalUserId':
|
||||
self.s3_user_id = value
|
||||
elif name == 'Comment':
|
||||
self.comment = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def get_origin_access_identity(self):
|
||||
return self.connection.get_origin_access_identity_info(self.id)
|
||||
|
|
@ -1,216 +0,0 @@
|
|||
# Copyright (c) 2006-2010 Chris Moyer http://coredumped.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
import uuid
|
||||
import urllib
|
||||
|
||||
from boto.resultset import ResultSet
|
||||
|
||||
|
||||
class InvalidationBatch(object):
|
||||
"""A simple invalidation request.
|
||||
:see: http://docs.amazonwebservices.com/AmazonCloudFront/2010-08-01/APIReference/index.html?InvalidationBatchDatatype.html
|
||||
"""
|
||||
|
||||
def __init__(self, paths=None, connection=None, distribution=None, caller_reference=''):
|
||||
"""Create a new invalidation request:
|
||||
:paths: An array of paths to invalidate
|
||||
"""
|
||||
self.paths = paths or []
|
||||
self.distribution = distribution
|
||||
self.caller_reference = caller_reference
|
||||
if not self.caller_reference:
|
||||
self.caller_reference = str(uuid.uuid4())
|
||||
|
||||
# If we passed in a distribution,
|
||||
# then we use that as the connection object
|
||||
if distribution:
|
||||
self.connection = distribution
|
||||
else:
|
||||
self.connection = connection
|
||||
|
||||
def __repr__(self):
|
||||
return '<InvalidationBatch: %s>' % self.id
|
||||
|
||||
def add(self, path):
|
||||
"""Add another path to this invalidation request"""
|
||||
return self.paths.append(path)
|
||||
|
||||
def remove(self, path):
|
||||
"""Remove a path from this invalidation request"""
|
||||
return self.paths.remove(path)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.paths)
|
||||
|
||||
def __getitem__(self, i):
|
||||
return self.paths[i]
|
||||
|
||||
def __setitem__(self, k, v):
|
||||
self.paths[k] = v
|
||||
|
||||
def escape(self, p):
|
||||
"""Escape a path, make sure it begins with a slash and contains no invalid characters"""
|
||||
if not p[0] == "/":
|
||||
p = "/%s" % p
|
||||
return urllib.quote(p)
|
||||
|
||||
def to_xml(self):
|
||||
"""Get this batch as XML"""
|
||||
assert self.connection != None
|
||||
s = '<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
s += '<InvalidationBatch xmlns="http://cloudfront.amazonaws.com/doc/%s/">\n' % self.connection.Version
|
||||
for p in self.paths:
|
||||
s += ' <Path>%s</Path>\n' % self.escape(p)
|
||||
s += ' <CallerReference>%s</CallerReference>\n' % self.caller_reference
|
||||
s += '</InvalidationBatch>\n'
|
||||
return s
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == "InvalidationBatch":
|
||||
self.paths = []
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Path':
|
||||
self.paths.append(value)
|
||||
elif name == "Status":
|
||||
self.status = value
|
||||
elif name == "Id":
|
||||
self.id = value
|
||||
elif name == "CreateTime":
|
||||
self.create_time = value
|
||||
elif name == "CallerReference":
|
||||
self.caller_reference = value
|
||||
return None
|
||||
|
||||
|
||||
class InvalidationListResultSet(object):
|
||||
"""
|
||||
A resultset for listing invalidations on a given CloudFront distribution.
|
||||
Implements the iterator interface and transparently handles paging results
|
||||
from CF so even if you have many thousands of invalidations on the
|
||||
distribution you can iterate over all invalidations in a reasonably
|
||||
efficient manner.
|
||||
"""
|
||||
def __init__(self, markers=None, connection=None, distribution_id=None,
|
||||
invalidations=None, marker='', next_marker=None,
|
||||
max_items=None, is_truncated=False):
|
||||
self.markers = markers or []
|
||||
self.connection = connection
|
||||
self.distribution_id = distribution_id
|
||||
self.marker = marker
|
||||
self.next_marker = next_marker
|
||||
self.max_items = max_items
|
||||
self.auto_paginate = max_items is None
|
||||
self.is_truncated = is_truncated
|
||||
self._inval_cache = invalidations or []
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
A generator function for listing invalidation requests for a given
|
||||
CloudFront distribution.
|
||||
"""
|
||||
conn = self.connection
|
||||
distribution_id = self.distribution_id
|
||||
result_set = self
|
||||
for inval in result_set._inval_cache:
|
||||
yield inval
|
||||
if not self.auto_paginate:
|
||||
return
|
||||
while result_set.is_truncated:
|
||||
result_set = conn.get_invalidation_requests(distribution_id,
|
||||
marker=result_set.next_marker,
|
||||
max_items=result_set.max_items)
|
||||
for i in result_set._inval_cache:
|
||||
yield i
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
for root_elem, handler in self.markers:
|
||||
if name == root_elem:
|
||||
obj = handler(connection, distribution_id=self.distribution_id)
|
||||
self._inval_cache.append(obj)
|
||||
return obj
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'IsTruncated':
|
||||
self.is_truncated = self.to_boolean(value)
|
||||
elif name == 'Marker':
|
||||
self.marker = value
|
||||
elif name == 'NextMarker':
|
||||
self.next_marker = value
|
||||
elif name == 'MaxItems':
|
||||
self.max_items = int(value)
|
||||
|
||||
def to_boolean(self, value, true_value='true'):
|
||||
if value == true_value:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
class InvalidationSummary(object):
|
||||
"""
|
||||
Represents InvalidationSummary complex type in CloudFront API that lists
|
||||
the id and status of a given invalidation request.
|
||||
"""
|
||||
def __init__(self, connection=None, distribution_id=None, id='',
|
||||
status=''):
|
||||
self.connection = connection
|
||||
self.distribution_id = distribution_id
|
||||
self.id = id
|
||||
self.status = status
|
||||
|
||||
def __repr__(self):
|
||||
return '<InvalidationSummary: %s>' % self.id
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Id':
|
||||
self.id = value
|
||||
elif name == 'Status':
|
||||
self.status = value
|
||||
|
||||
def get_distribution(self):
|
||||
"""
|
||||
Returns a Distribution object representing the parent CloudFront
|
||||
distribution of the invalidation request listed in the
|
||||
InvalidationSummary.
|
||||
|
||||
:rtype: :class:`boto.cloudfront.distribution.Distribution`
|
||||
:returns: A Distribution object representing the parent CloudFront
|
||||
distribution of the invalidation request listed in the
|
||||
InvalidationSummary
|
||||
"""
|
||||
return self.connection.get_distribution_info(self.distribution_id)
|
||||
|
||||
def get_invalidation_request(self):
|
||||
"""
|
||||
Returns an InvalidationBatch object representing the invalidation
|
||||
request referred to in the InvalidationSummary.
|
||||
|
||||
:rtype: :class:`boto.cloudfront.invalidation.InvalidationBatch`
|
||||
:returns: An InvalidationBatch object representing the invalidation
|
||||
request referred to by the InvalidationSummary
|
||||
"""
|
||||
return self.connection.invalidation_request_status(
|
||||
self.distribution_id, self.id)
|
|
@ -1,38 +0,0 @@
|
|||
# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
class LoggingInfo(object):
|
||||
|
||||
def __init__(self, bucket='', prefix=''):
|
||||
self.bucket = bucket
|
||||
self.prefix = prefix
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Bucket':
|
||||
self.bucket = value
|
||||
elif name == 'Prefix':
|
||||
self.prefix = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
from boto.s3.key import Key
|
||||
|
||||
class Object(Key):
|
||||
|
||||
def __init__(self, bucket, name=None):
|
||||
Key.__init__(self, bucket, name=name)
|
||||
self.distribution = bucket.distribution
|
||||
|
||||
def __repr__(self):
|
||||
return '<Object: %s/%s>' % (self.distribution.config.origin, self.name)
|
||||
|
||||
def url(self, scheme='http'):
|
||||
url = '%s://' % scheme
|
||||
url += self.distribution.domain_name
|
||||
if scheme.lower().startswith('rtmp'):
|
||||
url += '/cfx/st/'
|
||||
else:
|
||||
url += '/'
|
||||
url += self.name
|
||||
return url
|
||||
|
||||
class StreamingObject(Object):
|
||||
|
||||
def url(self, scheme='rtmp'):
|
||||
return Object.url(self, scheme)
|
||||
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2010, Eucalyptus Systems, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
from identity import OriginAccessIdentity
|
||||
|
||||
def get_oai_value(origin_access_identity):
|
||||
if isinstance(origin_access_identity, OriginAccessIdentity):
|
||||
return origin_access_identity.uri()
|
||||
else:
|
||||
return origin_access_identity
|
||||
|
||||
class S3Origin(object):
|
||||
"""
|
||||
Origin information to associate with the distribution.
|
||||
If your distribution will use an Amazon S3 origin,
|
||||
then you use the S3Origin element.
|
||||
"""
|
||||
|
||||
def __init__(self, dns_name=None, origin_access_identity=None):
|
||||
"""
|
||||
:param dns_name: The DNS name of your Amazon S3 bucket to
|
||||
associate with the distribution.
|
||||
For example: mybucket.s3.amazonaws.com.
|
||||
:type dns_name: str
|
||||
|
||||
:param origin_access_identity: The CloudFront origin access
|
||||
identity to associate with the
|
||||
distribution. If you want the
|
||||
distribution to serve private content,
|
||||
include this element; if you want the
|
||||
distribution to serve public content,
|
||||
remove this element.
|
||||
:type origin_access_identity: str
|
||||
|
||||
"""
|
||||
self.dns_name = dns_name
|
||||
self.origin_access_identity = origin_access_identity
|
||||
|
||||
def __repr__(self):
|
||||
return '<S3Origin: %s>' % self.dns_name
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'DNSName':
|
||||
self.dns_name = value
|
||||
elif name == 'OriginAccessIdentity':
|
||||
self.origin_access_identity = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def to_xml(self):
|
||||
s = ' <S3Origin>\n'
|
||||
s += ' <DNSName>%s</DNSName>\n' % self.dns_name
|
||||
if self.origin_access_identity:
|
||||
val = get_oai_value(self.origin_access_identity)
|
||||
s += ' <OriginAccessIdentity>%s</OriginAccessIdentity>\n' % val
|
||||
s += ' </S3Origin>\n'
|
||||
return s
|
||||
|
||||
class CustomOrigin(object):
|
||||
"""
|
||||
Origin information to associate with the distribution.
|
||||
If your distribution will use a non-Amazon S3 origin,
|
||||
then you use the CustomOrigin element.
|
||||
"""
|
||||
|
||||
def __init__(self, dns_name=None, http_port=80, https_port=443,
|
||||
origin_protocol_policy=None):
|
||||
"""
|
||||
:param dns_name: The DNS name of your Amazon S3 bucket to
|
||||
associate with the distribution.
|
||||
For example: mybucket.s3.amazonaws.com.
|
||||
:type dns_name: str
|
||||
|
||||
:param http_port: The HTTP port the custom origin listens on.
|
||||
:type http_port: int
|
||||
|
||||
:param https_port: The HTTPS port the custom origin listens on.
|
||||
:type http_port: int
|
||||
|
||||
:param origin_protocol_policy: The origin protocol policy to
|
||||
apply to your origin. If you
|
||||
specify http-only, CloudFront
|
||||
will use HTTP only to access the origin.
|
||||
If you specify match-viewer, CloudFront
|
||||
will fetch from your origin using HTTP
|
||||
or HTTPS, based on the protocol of the
|
||||
viewer request.
|
||||
:type origin_protocol_policy: str
|
||||
|
||||
"""
|
||||
self.dns_name = dns_name
|
||||
self.http_port = http_port
|
||||
self.https_port = https_port
|
||||
self.origin_protocol_policy = origin_protocol_policy
|
||||
|
||||
def __repr__(self):
|
||||
return '<CustomOrigin: %s>' % self.dns_name
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'DNSName':
|
||||
self.dns_name = value
|
||||
elif name == 'HTTPPort':
|
||||
try:
|
||||
self.http_port = int(value)
|
||||
except ValueError:
|
||||
self.http_port = value
|
||||
elif name == 'HTTPSPort':
|
||||
try:
|
||||
self.https_port = int(value)
|
||||
except ValueError:
|
||||
self.https_port = value
|
||||
elif name == 'OriginProtocolPolicy':
|
||||
self.origin_protocol_policy = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def to_xml(self):
|
||||
s = ' <CustomOrigin>\n'
|
||||
s += ' <DNSName>%s</DNSName>\n' % self.dns_name
|
||||
s += ' <HTTPPort>%d</HTTPPort>\n' % self.http_port
|
||||
s += ' <HTTPSPort>%d</HTTPSPort>\n' % self.https_port
|
||||
s += ' <OriginProtocolPolicy>%s</OriginProtocolPolicy>\n' % self.origin_protocol_policy
|
||||
s += ' </CustomOrigin>\n'
|
||||
return s
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
class Signer:
|
||||
|
||||
def __init__(self):
|
||||
self.id = None
|
||||
self.key_pair_ids = []
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Self':
|
||||
self.id = 'Self'
|
||||
elif name == 'AwsAccountNumber':
|
||||
self.id = value
|
||||
elif name == 'KeyPairId':
|
||||
self.key_pair_ids.append(value)
|
||||
|
||||
class ActiveTrustedSigners(list):
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'Signer':
|
||||
s = Signer()
|
||||
self.append(s)
|
||||
return s
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
pass
|
||||
|
||||
class TrustedSigners(list):
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Self':
|
||||
self.append(name)
|
||||
elif name == 'AwsAccountNumber':
|
||||
self.append(value)
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
from boto.regioninfo import RegionInfo
|
||||
|
||||
|
||||
def regions():
|
||||
"""
|
||||
Get all available regions for the Amazon CloudSearch service.
|
||||
|
||||
:rtype: list
|
||||
:return: A list of :class:`boto.regioninfo.RegionInfo`
|
||||
"""
|
||||
import boto.cloudsearch.layer1
|
||||
return [RegionInfo(name='us-east-1',
|
||||
endpoint='cloudsearch.us-east-1.amazonaws.com',
|
||||
connection_cls=boto.cloudsearch.layer1.Layer1),
|
||||
RegionInfo(name='eu-west-1',
|
||||
endpoint='cloudsearch.eu-west-1.amazonaws.com',
|
||||
connection_cls=boto.cloudsearch.layer1.Layer1),
|
||||
RegionInfo(name='us-west-1',
|
||||
endpoint='cloudsearch.us-west-1.amazonaws.com',
|
||||
connection_cls=boto.cloudsearch.layer1.Layer1),
|
||||
RegionInfo(name='us-west-2',
|
||||
endpoint='cloudsearch.us-west-2.amazonaws.com',
|
||||
connection_cls=boto.cloudsearch.layer1.Layer1),
|
||||
RegionInfo(name='ap-southeast-1',
|
||||
endpoint='cloudsearch.ap-southeast-1.amazonaws.com',
|
||||
connection_cls=boto.cloudsearch.layer1.Layer1),
|
||||
|
||||
]
|
||||
|
||||
|
||||
def connect_to_region(region_name, **kw_params):
|
||||
for region in regions():
|
||||
if region.name == region_name:
|
||||
return region.connect(**kw_params)
|
||||
return None
|
|
@ -1,269 +0,0 @@
|
|||
# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
import boto.exception
|
||||
from boto.compat import json
|
||||
import requests
|
||||
import boto
|
||||
|
||||
class SearchServiceException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class CommitMismatchError(Exception):
|
||||
pass
|
||||
|
||||
class EncodingError(Exception):
|
||||
"""
|
||||
Content sent for Cloud Search indexing was incorrectly encoded.
|
||||
|
||||
This usually happens when a document is marked as unicode but non-unicode
|
||||
characters are present.
|
||||
"""
|
||||
pass
|
||||
|
||||
class ContentTooLongError(Exception):
|
||||
"""
|
||||
Content sent for Cloud Search indexing was too long
|
||||
|
||||
This will usually happen when documents queued for indexing add up to more
|
||||
than the limit allowed per upload batch (5MB)
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
class DocumentServiceConnection(object):
|
||||
"""
|
||||
A CloudSearch document service.
|
||||
|
||||
The DocumentServiceConection is used to add, remove and update documents in
|
||||
CloudSearch. Commands are uploaded to CloudSearch in SDF (Search Document Format).
|
||||
|
||||
To generate an appropriate SDF, use :func:`add` to add or update documents,
|
||||
as well as :func:`delete` to remove documents.
|
||||
|
||||
Once the set of documents is ready to be index, use :func:`commit` to send the
|
||||
commands to CloudSearch.
|
||||
|
||||
If there are a lot of documents to index, it may be preferable to split the
|
||||
generation of SDF data and the actual uploading into CloudSearch. Retrieve
|
||||
the current SDF with :func:`get_sdf`. If this file is the uploaded into S3,
|
||||
it can be retrieved back afterwards for upload into CloudSearch using
|
||||
:func:`add_sdf_from_s3`.
|
||||
|
||||
The SDF is not cleared after a :func:`commit`. If you wish to continue
|
||||
using the DocumentServiceConnection for another batch upload of commands,
|
||||
you will need to :func:`clear_sdf` first to stop the previous batch of
|
||||
commands from being uploaded again.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, domain=None, endpoint=None):
|
||||
self.domain = domain
|
||||
self.endpoint = endpoint
|
||||
if not self.endpoint:
|
||||
self.endpoint = domain.doc_service_endpoint
|
||||
self.documents_batch = []
|
||||
self._sdf = None
|
||||
|
||||
def add(self, _id, version, fields, lang='en'):
|
||||
"""
|
||||
Add a document to be processed by the DocumentService
|
||||
|
||||
The document will not actually be added until :func:`commit` is called
|
||||
|
||||
:type _id: string
|
||||
:param _id: A unique ID used to refer to this document.
|
||||
|
||||
:type version: int
|
||||
:param version: Version of the document being indexed. If a file is
|
||||
being reindexed, the version should be higher than the existing one
|
||||
in CloudSearch.
|
||||
|
||||
:type fields: dict
|
||||
:param fields: A dictionary of key-value pairs to be uploaded .
|
||||
|
||||
:type lang: string
|
||||
:param lang: The language code the data is in. Only 'en' is currently
|
||||
supported
|
||||
"""
|
||||
|
||||
d = {'type': 'add', 'id': _id, 'version': version, 'lang': lang,
|
||||
'fields': fields}
|
||||
self.documents_batch.append(d)
|
||||
|
||||
def delete(self, _id, version):
|
||||
"""
|
||||
Schedule a document to be removed from the CloudSearch service
|
||||
|
||||
The document will not actually be scheduled for removal until :func:`commit` is called
|
||||
|
||||
:type _id: string
|
||||
:param _id: The unique ID of this document.
|
||||
|
||||
:type version: int
|
||||
:param version: Version of the document to remove. The delete will only
|
||||
occur if this version number is higher than the version currently
|
||||
in the index.
|
||||
"""
|
||||
|
||||
d = {'type': 'delete', 'id': _id, 'version': version}
|
||||
self.documents_batch.append(d)
|
||||
|
||||
def get_sdf(self):
|
||||
"""
|
||||
Generate the working set of documents in Search Data Format (SDF)
|
||||
|
||||
:rtype: string
|
||||
:returns: JSON-formatted string of the documents in SDF
|
||||
"""
|
||||
|
||||
return self._sdf if self._sdf else json.dumps(self.documents_batch)
|
||||
|
||||
def clear_sdf(self):
|
||||
"""
|
||||
Clear the working documents from this DocumentServiceConnection
|
||||
|
||||
This should be used after :func:`commit` if the connection will be reused
|
||||
for another set of documents.
|
||||
"""
|
||||
|
||||
self._sdf = None
|
||||
self.documents_batch = []
|
||||
|
||||
def add_sdf_from_s3(self, key_obj):
|
||||
"""
|
||||
Load an SDF from S3
|
||||
|
||||
Using this method will result in documents added through
|
||||
:func:`add` and :func:`delete` being ignored.
|
||||
|
||||
:type key_obj: :class:`boto.s3.key.Key`
|
||||
:param key_obj: An S3 key which contains an SDF
|
||||
"""
|
||||
#@todo:: (lucas) would be nice if this could just take an s3://uri..."
|
||||
|
||||
self._sdf = key_obj.get_contents_as_string()
|
||||
|
||||
def commit(self):
|
||||
"""
|
||||
Actually send an SDF to CloudSearch for processing
|
||||
|
||||
If an SDF file has been explicitly loaded it will be used. Otherwise,
|
||||
documents added through :func:`add` and :func:`delete` will be used.
|
||||
|
||||
:rtype: :class:`CommitResponse`
|
||||
:returns: A summary of documents added and deleted
|
||||
"""
|
||||
|
||||
sdf = self.get_sdf()
|
||||
|
||||
if ': null' in sdf:
|
||||
boto.log.error('null value in sdf detected. This will probably raise '
|
||||
'500 error.')
|
||||
index = sdf.index(': null')
|
||||
boto.log.error(sdf[index - 100:index + 100])
|
||||
|
||||
url = "http://%s/2011-02-01/documents/batch" % (self.endpoint)
|
||||
|
||||
# Keep-alive is automatic in a post-1.0 requests world.
|
||||
session = requests.Session()
|
||||
adapter = requests.adapters.HTTPAdapter(
|
||||
pool_connections=20,
|
||||
pool_maxsize=50
|
||||
)
|
||||
# Now kludge in the right number of retries.
|
||||
# Once we're requiring ``requests>=1.2.1``, this can become an
|
||||
# initialization parameter above.
|
||||
adapter.max_retries = 5
|
||||
session.mount('http://', adapter)
|
||||
session.mount('https://', adapter)
|
||||
r = session.post(url, data=sdf, headers={'Content-Type': 'application/json'})
|
||||
|
||||
return CommitResponse(r, self, sdf)
|
||||
|
||||
|
||||
class CommitResponse(object):
|
||||
"""Wrapper for response to Cloudsearch document batch commit.
|
||||
|
||||
:type response: :class:`requests.models.Response`
|
||||
:param response: Response from Cloudsearch /documents/batch API
|
||||
|
||||
:type doc_service: :class:`boto.cloudsearch.document.DocumentServiceConnection`
|
||||
:param doc_service: Object containing the documents posted and methods to
|
||||
retry
|
||||
|
||||
:raises: :class:`boto.exception.BotoServerError`
|
||||
:raises: :class:`boto.cloudsearch.document.SearchServiceException`
|
||||
:raises: :class:`boto.cloudsearch.document.EncodingError`
|
||||
:raises: :class:`boto.cloudsearch.document.ContentTooLongError`
|
||||
"""
|
||||
def __init__(self, response, doc_service, sdf):
|
||||
self.response = response
|
||||
self.doc_service = doc_service
|
||||
self.sdf = sdf
|
||||
|
||||
try:
|
||||
self.content = json.loads(response.content)
|
||||
except:
|
||||
boto.log.error('Error indexing documents.\nResponse Content:\n{0}\n\n'
|
||||
'SDF:\n{1}'.format(response.content, self.sdf))
|
||||
raise boto.exception.BotoServerError(self.response.status_code, '',
|
||||
body=response.content)
|
||||
|
||||
self.status = self.content['status']
|
||||
if self.status == 'error':
|
||||
self.errors = [e.get('message') for e in self.content.get('errors',
|
||||
[])]
|
||||
for e in self.errors:
|
||||
if "Illegal Unicode character" in e:
|
||||
raise EncodingError("Illegal Unicode character in document")
|
||||
elif e == "The Content-Length is too long":
|
||||
raise ContentTooLongError("Content was too long")
|
||||
else:
|
||||
self.errors = []
|
||||
|
||||
self.adds = self.content['adds']
|
||||
self.deletes = self.content['deletes']
|
||||
self._check_num_ops('add', self.adds)
|
||||
self._check_num_ops('delete', self.deletes)
|
||||
|
||||
def _check_num_ops(self, type_, response_num):
|
||||
"""Raise exception if number of ops in response doesn't match commit
|
||||
|
||||
:type type_: str
|
||||
:param type_: Type of commit operation: 'add' or 'delete'
|
||||
|
||||
:type response_num: int
|
||||
:param response_num: Number of adds or deletes in the response.
|
||||
|
||||
:raises: :class:`boto.cloudsearch.document.CommitMismatchError`
|
||||
"""
|
||||
commit_num = len([d for d in self.doc_service.documents_batch
|
||||
if d['type'] == type_])
|
||||
|
||||
if response_num != commit_num:
|
||||
raise CommitMismatchError(
|
||||
'Incorrect number of {0}s returned. Commit: {1} Response: {2}'\
|
||||
.format(type_, commit_num, response_num))
|
|
@ -1,394 +0,0 @@
|
|||
# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
import boto
|
||||
from boto.compat import json
|
||||
from .optionstatus import OptionStatus
|
||||
from .optionstatus import IndexFieldStatus
|
||||
from .optionstatus import ServicePoliciesStatus
|
||||
from .optionstatus import RankExpressionStatus
|
||||
from .document import DocumentServiceConnection
|
||||
from .search import SearchConnection
|
||||
|
||||
def handle_bool(value):
|
||||
if value in [True, 'true', 'True', 'TRUE', 1]:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Domain(object):
|
||||
"""
|
||||
A Cloudsearch domain.
|
||||
|
||||
:ivar name: The name of the domain.
|
||||
|
||||
:ivar id: The internally generated unique identifier for the domain.
|
||||
|
||||
:ivar created: A boolean which is True if the domain is
|
||||
created. It can take several minutes to initialize a domain
|
||||
when CreateDomain is called. Newly created search domains are
|
||||
returned with a False value for Created until domain creation
|
||||
is complete
|
||||
|
||||
:ivar deleted: A boolean which is True if the search domain has
|
||||
been deleted. The system must clean up resources dedicated to
|
||||
the search domain when delete is called. Newly deleted
|
||||
search domains are returned from list_domains with a True
|
||||
value for deleted for several minutes until resource cleanup
|
||||
is complete.
|
||||
|
||||
:ivar processing: True if processing is being done to activate the
|
||||
current domain configuration.
|
||||
|
||||
:ivar num_searchable_docs: The number of documents that have been
|
||||
submittted to the domain and indexed.
|
||||
|
||||
:ivar requires_index_document: True if index_documents needs to be
|
||||
called to activate the current domain configuration.
|
||||
|
||||
:ivar search_instance_count: The number of search instances that are
|
||||
available to process search requests.
|
||||
|
||||
:ivar search_instance_type: The instance type that is being used to
|
||||
process search requests.
|
||||
|
||||
:ivar search_partition_count: The number of partitions across which
|
||||
the search index is spread.
|
||||
"""
|
||||
|
||||
def __init__(self, layer1, data):
|
||||
self.layer1 = layer1
|
||||
self.update_from_data(data)
|
||||
|
||||
def update_from_data(self, data):
|
||||
self.created = data['created']
|
||||
self.deleted = data['deleted']
|
||||
self.processing = data['processing']
|
||||
self.requires_index_documents = data['requires_index_documents']
|
||||
self.domain_id = data['domain_id']
|
||||
self.domain_name = data['domain_name']
|
||||
self.num_searchable_docs = data['num_searchable_docs']
|
||||
self.search_instance_count = data['search_instance_count']
|
||||
self.search_instance_type = data.get('search_instance_type', None)
|
||||
self.search_partition_count = data['search_partition_count']
|
||||
self._doc_service = data['doc_service']
|
||||
self._search_service = data['search_service']
|
||||
|
||||
@property
|
||||
def doc_service_arn(self):
|
||||
return self._doc_service['arn']
|
||||
|
||||
@property
|
||||
def doc_service_endpoint(self):
|
||||
return self._doc_service['endpoint']
|
||||
|
||||
@property
|
||||
def search_service_arn(self):
|
||||
return self._search_service['arn']
|
||||
|
||||
@property
|
||||
def search_service_endpoint(self):
|
||||
return self._search_service['endpoint']
|
||||
|
||||
@property
|
||||
def created(self):
|
||||
return self._created
|
||||
|
||||
@created.setter
|
||||
def created(self, value):
|
||||
self._created = handle_bool(value)
|
||||
|
||||
@property
|
||||
def deleted(self):
|
||||
return self._deleted
|
||||
|
||||
@deleted.setter
|
||||
def deleted(self, value):
|
||||
self._deleted = handle_bool(value)
|
||||
|
||||
@property
|
||||
def processing(self):
|
||||
return self._processing
|
||||
|
||||
@processing.setter
|
||||
def processing(self, value):
|
||||
self._processing = handle_bool(value)
|
||||
|
||||
@property
|
||||
def requires_index_documents(self):
|
||||
return self._requires_index_documents
|
||||
|
||||
@requires_index_documents.setter
|
||||
def requires_index_documents(self, value):
|
||||
self._requires_index_documents = handle_bool(value)
|
||||
|
||||
@property
|
||||
def search_partition_count(self):
|
||||
return self._search_partition_count
|
||||
|
||||
@search_partition_count.setter
|
||||
def search_partition_count(self, value):
|
||||
self._search_partition_count = int(value)
|
||||
|
||||
@property
|
||||
def search_instance_count(self):
|
||||
return self._search_instance_count
|
||||
|
||||
@search_instance_count.setter
|
||||
def search_instance_count(self, value):
|
||||
self._search_instance_count = int(value)
|
||||
|
||||
@property
|
||||
def num_searchable_docs(self):
|
||||
return self._num_searchable_docs
|
||||
|
||||
@num_searchable_docs.setter
|
||||
def num_searchable_docs(self, value):
|
||||
self._num_searchable_docs = int(value)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.domain_name
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.domain_id
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
Delete this domain and all index data associated with it.
|
||||
"""
|
||||
return self.layer1.delete_domain(self.name)
|
||||
|
||||
def get_stemming(self):
|
||||
"""
|
||||
Return a :class:`boto.cloudsearch.option.OptionStatus` object
|
||||
representing the currently defined stemming options for
|
||||
the domain.
|
||||
"""
|
||||
return OptionStatus(self, None,
|
||||
self.layer1.describe_stemming_options,
|
||||
self.layer1.update_stemming_options)
|
||||
|
||||
def get_stopwords(self):
|
||||
"""
|
||||
Return a :class:`boto.cloudsearch.option.OptionStatus` object
|
||||
representing the currently defined stopword options for
|
||||
the domain.
|
||||
"""
|
||||
return OptionStatus(self, None,
|
||||
self.layer1.describe_stopword_options,
|
||||
self.layer1.update_stopword_options)
|
||||
|
||||
def get_synonyms(self):
|
||||
"""
|
||||
Return a :class:`boto.cloudsearch.option.OptionStatus` object
|
||||
representing the currently defined synonym options for
|
||||
the domain.
|
||||
"""
|
||||
return OptionStatus(self, None,
|
||||
self.layer1.describe_synonym_options,
|
||||
self.layer1.update_synonym_options)
|
||||
|
||||
def get_access_policies(self):
|
||||
"""
|
||||
Return a :class:`boto.cloudsearch.option.OptionStatus` object
|
||||
representing the currently defined access policies for
|
||||
the domain.
|
||||
"""
|
||||
return ServicePoliciesStatus(self, None,
|
||||
self.layer1.describe_service_access_policies,
|
||||
self.layer1.update_service_access_policies)
|
||||
|
||||
def index_documents(self):
|
||||
"""
|
||||
Tells the search domain to start indexing its documents using
|
||||
the latest text processing options and IndexFields. This
|
||||
operation must be invoked to make options whose OptionStatus
|
||||
has OptioState of RequiresIndexDocuments visible in search
|
||||
results.
|
||||
"""
|
||||
self.layer1.index_documents(self.name)
|
||||
|
||||
def get_index_fields(self, field_names=None):
|
||||
"""
|
||||
Return a list of index fields defined for this domain.
|
||||
"""
|
||||
data = self.layer1.describe_index_fields(self.name, field_names)
|
||||
return [IndexFieldStatus(self, d) for d in data]
|
||||
|
||||
def create_index_field(self, field_name, field_type,
|
||||
default='', facet=False, result=False, searchable=False,
|
||||
source_attributes=[]):
|
||||
"""
|
||||
Defines an ``IndexField``, either replacing an existing
|
||||
definition or creating a new one.
|
||||
|
||||
:type field_name: string
|
||||
:param field_name: The name of a field in the search index.
|
||||
|
||||
:type field_type: string
|
||||
:param field_type: The type of field. Valid values are
|
||||
uint | literal | text
|
||||
|
||||
:type default: string or int
|
||||
:param default: The default value for the field. If the
|
||||
field is of type ``uint`` this should be an integer value.
|
||||
Otherwise, it's a string.
|
||||
|
||||
:type facet: bool
|
||||
:param facet: A boolean to indicate whether facets
|
||||
are enabled for this field or not. Does not apply to
|
||||
fields of type ``uint``.
|
||||
|
||||
:type results: bool
|
||||
:param results: A boolean to indicate whether values
|
||||
of this field can be returned in search results or
|
||||
used in ranking. Does not apply to fields of type ``uint``.
|
||||
|
||||
:type searchable: bool
|
||||
:param searchable: A boolean to indicate whether search
|
||||
is enabled for this field or not. Applies only to fields
|
||||
of type ``literal``.
|
||||
|
||||
:type source_attributes: list of dicts
|
||||
:param source_attributes: An optional list of dicts that
|
||||
provide information about attributes for this index field.
|
||||
A maximum of 20 source attributes can be configured for
|
||||
each index field.
|
||||
|
||||
Each item in the list is a dict with the following keys:
|
||||
|
||||
* data_copy - The value is a dict with the following keys:
|
||||
* default - Optional default value if the source attribute
|
||||
is not specified in a document.
|
||||
* name - The name of the document source field to add
|
||||
to this ``IndexField``.
|
||||
* data_function - Identifies the transformation to apply
|
||||
when copying data from a source attribute.
|
||||
* data_map - The value is a dict with the following keys:
|
||||
* cases - A dict that translates source field values
|
||||
to custom values.
|
||||
* default - An optional default value to use if the
|
||||
source attribute is not specified in a document.
|
||||
* name - the name of the document source field to add
|
||||
to this ``IndexField``
|
||||
* data_trim_title - Trims common title words from a source
|
||||
document attribute when populating an ``IndexField``.
|
||||
This can be used to create an ``IndexField`` you can
|
||||
use for sorting. The value is a dict with the following
|
||||
fields:
|
||||
* default - An optional default value.
|
||||
* language - an IETF RFC 4646 language code.
|
||||
* separator - The separator that follows the text to trim.
|
||||
* name - The name of the document source field to add.
|
||||
|
||||
:raises: BaseException, InternalException, LimitExceededException,
|
||||
InvalidTypeException, ResourceNotFoundException
|
||||
"""
|
||||
data = self.layer1.define_index_field(self.name, field_name,
|
||||
field_type, default=default,
|
||||
facet=facet, result=result,
|
||||
searchable=searchable,
|
||||
source_attributes=source_attributes)
|
||||
return IndexFieldStatus(self, data,
|
||||
self.layer1.describe_index_fields)
|
||||
|
||||
def get_rank_expressions(self, rank_names=None):
|
||||
"""
|
||||
Return a list of rank expressions defined for this domain.
|
||||
"""
|
||||
fn = self.layer1.describe_rank_expressions
|
||||
data = fn(self.name, rank_names)
|
||||
return [RankExpressionStatus(self, d, fn) for d in data]
|
||||
|
||||
def create_rank_expression(self, name, expression):
|
||||
"""
|
||||
Create a new rank expression.
|
||||
|
||||
:type rank_name: string
|
||||
:param rank_name: The name of an expression computed for ranking
|
||||
while processing a search request.
|
||||
|
||||
:type rank_expression: string
|
||||
:param rank_expression: The expression to evaluate for ranking
|
||||
or thresholding while processing a search request. The
|
||||
RankExpression syntax is based on JavaScript expressions
|
||||
and supports:
|
||||
|
||||
* Integer, floating point, hex and octal literals
|
||||
* Shortcut evaluation of logical operators such that an
|
||||
expression a || b evaluates to the value a if a is
|
||||
true without evaluting b at all
|
||||
* JavaScript order of precedence for operators
|
||||
* Arithmetic operators: + - * / %
|
||||
* Boolean operators (including the ternary operator)
|
||||
* Bitwise operators
|
||||
* Comparison operators
|
||||
* Common mathematic functions: abs ceil erf exp floor
|
||||
lgamma ln log2 log10 max min sqrt pow
|
||||
* Trigonometric library functions: acosh acos asinh asin
|
||||
atanh atan cosh cos sinh sin tanh tan
|
||||
* Random generation of a number between 0 and 1: rand
|
||||
* Current time in epoch: time
|
||||
* The min max functions that operate on a variable argument list
|
||||
|
||||
Intermediate results are calculated as double precision
|
||||
floating point values. The final return value of a
|
||||
RankExpression is automatically converted from floating
|
||||
point to a 32-bit unsigned integer by rounding to the
|
||||
nearest integer, with a natural floor of 0 and a ceiling
|
||||
of max(uint32_t), 4294967295. Mathematical errors such as
|
||||
dividing by 0 will fail during evaluation and return a
|
||||
value of 0.
|
||||
|
||||
The source data for a RankExpression can be the name of an
|
||||
IndexField of type uint, another RankExpression or the
|
||||
reserved name text_relevance. The text_relevance source is
|
||||
defined to return an integer from 0 to 1000 (inclusive) to
|
||||
indicate how relevant a document is to the search request,
|
||||
taking into account repetition of search terms in the
|
||||
document and proximity of search terms to each other in
|
||||
each matching IndexField in the document.
|
||||
|
||||
For more information about using rank expressions to
|
||||
customize ranking, see the Amazon CloudSearch Developer
|
||||
Guide.
|
||||
|
||||
:raises: BaseException, InternalException, LimitExceededException,
|
||||
InvalidTypeException, ResourceNotFoundException
|
||||
"""
|
||||
data = self.layer1.define_rank_expression(self.name, name, expression)
|
||||
return RankExpressionStatus(self, data,
|
||||
self.layer1.describe_rank_expressions)
|
||||
|
||||
def get_document_service(self):
|
||||
return DocumentServiceConnection(domain=self)
|
||||
|
||||
def get_search_service(self):
|
||||
return SearchConnection(domain=self)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Domain: %s>' % self.domain_name
|
||||
|
|
@ -1,746 +0,0 @@
|
|||
# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
import boto
|
||||
import boto.jsonresponse
|
||||
from boto.connection import AWSQueryConnection
|
||||
from boto.regioninfo import RegionInfo
|
||||
|
||||
#boto.set_stream_logger('cloudsearch')
|
||||
|
||||
|
||||
def do_bool(val):
|
||||
return 'true' if val in [True, 1, '1', 'true'] else 'false'
|
||||
|
||||
|
||||
class Layer1(AWSQueryConnection):
|
||||
|
||||
APIVersion = '2011-02-01'
|
||||
DefaultRegionName = boto.config.get('Boto', 'cs_region_name', 'us-east-1')
|
||||
DefaultRegionEndpoint = boto.config.get('Boto', 'cs_region_endpoint',
|
||||
'cloudsearch.us-east-1.amazonaws.com')
|
||||
|
||||
def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
|
||||
is_secure=True, host=None, port=None,
|
||||
proxy=None, proxy_port=None,
|
||||
proxy_user=None, proxy_pass=None, debug=0,
|
||||
https_connection_factory=None, region=None, path='/',
|
||||
api_version=None, security_token=None,
|
||||
validate_certs=True):
|
||||
if not region:
|
||||
region = RegionInfo(self, self.DefaultRegionName,
|
||||
self.DefaultRegionEndpoint)
|
||||
self.region = region
|
||||
AWSQueryConnection.__init__(
|
||||
self,
|
||||
host=self.region.endpoint,
|
||||
aws_access_key_id=aws_access_key_id,
|
||||
aws_secret_access_key=aws_secret_access_key,
|
||||
is_secure=is_secure,
|
||||
port=port,
|
||||
proxy=proxy,
|
||||
proxy_port=proxy_port,
|
||||
proxy_user=proxy_user,
|
||||
proxy_pass=proxy_pass,
|
||||
debug=debug,
|
||||
https_connection_factory=https_connection_factory,
|
||||
path=path,
|
||||
security_token=security_token,
|
||||
validate_certs=validate_certs)
|
||||
|
||||
def _required_auth_capability(self):
|
||||
return ['hmac-v4']
|
||||
|
||||
def get_response(self, doc_path, action, params, path='/',
|
||||
parent=None, verb='GET', list_marker=None):
|
||||
if not parent:
|
||||
parent = self
|
||||
response = self.make_request(action, params, path, verb)
|
||||
body = response.read()
|
||||
boto.log.debug(body)
|
||||
if response.status == 200:
|
||||
e = boto.jsonresponse.Element(
|
||||
list_marker=list_marker if list_marker else 'Set',
|
||||
pythonize_name=True)
|
||||
h = boto.jsonresponse.XmlHandler(e, parent)
|
||||
h.parse(body)
|
||||
inner = e
|
||||
for p in doc_path:
|
||||
inner = inner.get(p)
|
||||
if not inner:
|
||||
return None if list_marker == None else []
|
||||
if isinstance(inner, list):
|
||||
return inner
|
||||
else:
|
||||
return dict(**inner)
|
||||
else:
|
||||
raise self.ResponseError(response.status, response.reason, body)
|
||||
|
||||
def create_domain(self, domain_name):
|
||||
"""
|
||||
Create a new search domain.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:raises: BaseException, InternalException, LimitExceededException
|
||||
"""
|
||||
doc_path = ('create_domain_response',
|
||||
'create_domain_result',
|
||||
'domain_status')
|
||||
params = {'DomainName': domain_name}
|
||||
return self.get_response(doc_path, 'CreateDomain',
|
||||
params, verb='POST')
|
||||
|
||||
def define_index_field(self, domain_name, field_name, field_type,
|
||||
default='', facet=False, result=False,
|
||||
searchable=False, source_attributes=None):
|
||||
"""
|
||||
Defines an ``IndexField``, either replacing an existing
|
||||
definition or creating a new one.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:type field_name: string
|
||||
:param field_name: The name of a field in the search index.
|
||||
|
||||
:type field_type: string
|
||||
:param field_type: The type of field. Valid values are
|
||||
uint | literal | text
|
||||
|
||||
:type default: string or int
|
||||
:param default: The default value for the field. If the
|
||||
field is of type ``uint`` this should be an integer value.
|
||||
Otherwise, it's a string.
|
||||
|
||||
:type facet: bool
|
||||
:param facet: A boolean to indicate whether facets
|
||||
are enabled for this field or not. Does not apply to
|
||||
fields of type ``uint``.
|
||||
|
||||
:type results: bool
|
||||
:param results: A boolean to indicate whether values
|
||||
of this field can be returned in search results or
|
||||
used in ranking. Does not apply to fields of type ``uint``.
|
||||
|
||||
:type searchable: bool
|
||||
:param searchable: A boolean to indicate whether search
|
||||
is enabled for this field or not. Applies only to fields
|
||||
of type ``literal``.
|
||||
|
||||
:type source_attributes: list of dicts
|
||||
:param source_attributes: An optional list of dicts that
|
||||
provide information about attributes for this index field.
|
||||
A maximum of 20 source attributes can be configured for
|
||||
each index field.
|
||||
|
||||
Each item in the list is a dict with the following keys:
|
||||
|
||||
* data_copy - The value is a dict with the following keys:
|
||||
* default - Optional default value if the source attribute
|
||||
is not specified in a document.
|
||||
* name - The name of the document source field to add
|
||||
to this ``IndexField``.
|
||||
* data_function - Identifies the transformation to apply
|
||||
when copying data from a source attribute.
|
||||
* data_map - The value is a dict with the following keys:
|
||||
* cases - A dict that translates source field values
|
||||
to custom values.
|
||||
* default - An optional default value to use if the
|
||||
source attribute is not specified in a document.
|
||||
* name - the name of the document source field to add
|
||||
to this ``IndexField``
|
||||
* data_trim_title - Trims common title words from a source
|
||||
document attribute when populating an ``IndexField``.
|
||||
This can be used to create an ``IndexField`` you can
|
||||
use for sorting. The value is a dict with the following
|
||||
fields:
|
||||
* default - An optional default value.
|
||||
* language - an IETF RFC 4646 language code.
|
||||
* separator - The separator that follows the text to trim.
|
||||
* name - The name of the document source field to add.
|
||||
|
||||
:raises: BaseException, InternalException, LimitExceededException,
|
||||
InvalidTypeException, ResourceNotFoundException
|
||||
"""
|
||||
doc_path = ('define_index_field_response',
|
||||
'define_index_field_result',
|
||||
'index_field')
|
||||
params = {'DomainName': domain_name,
|
||||
'IndexField.IndexFieldName': field_name,
|
||||
'IndexField.IndexFieldType': field_type}
|
||||
if field_type == 'literal':
|
||||
params['IndexField.LiteralOptions.DefaultValue'] = default
|
||||
params['IndexField.LiteralOptions.FacetEnabled'] = do_bool(facet)
|
||||
params['IndexField.LiteralOptions.ResultEnabled'] = do_bool(result)
|
||||
params['IndexField.LiteralOptions.SearchEnabled'] = do_bool(searchable)
|
||||
elif field_type == 'uint':
|
||||
params['IndexField.UIntOptions.DefaultValue'] = default
|
||||
elif field_type == 'text':
|
||||
params['IndexField.TextOptions.DefaultValue'] = default
|
||||
params['IndexField.TextOptions.FacetEnabled'] = do_bool(facet)
|
||||
params['IndexField.TextOptions.ResultEnabled'] = do_bool(result)
|
||||
|
||||
return self.get_response(doc_path, 'DefineIndexField',
|
||||
params, verb='POST')
|
||||
|
||||
def define_rank_expression(self, domain_name, rank_name, rank_expression):
|
||||
"""
|
||||
Defines a RankExpression, either replacing an existing
|
||||
definition or creating a new one.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:type rank_name: string
|
||||
:param rank_name: The name of an expression computed for ranking
|
||||
while processing a search request.
|
||||
|
||||
:type rank_expression: string
|
||||
:param rank_expression: The expression to evaluate for ranking
|
||||
or thresholding while processing a search request. The
|
||||
RankExpression syntax is based on JavaScript expressions
|
||||
and supports:
|
||||
|
||||
* Integer, floating point, hex and octal literals
|
||||
* Shortcut evaluation of logical operators such that an
|
||||
expression a || b evaluates to the value a if a is
|
||||
true without evaluting b at all
|
||||
* JavaScript order of precedence for operators
|
||||
* Arithmetic operators: + - * / %
|
||||
* Boolean operators (including the ternary operator)
|
||||
* Bitwise operators
|
||||
* Comparison operators
|
||||
* Common mathematic functions: abs ceil erf exp floor
|
||||
lgamma ln log2 log10 max min sqrt pow
|
||||
* Trigonometric library functions: acosh acos asinh asin
|
||||
atanh atan cosh cos sinh sin tanh tan
|
||||
* Random generation of a number between 0 and 1: rand
|
||||
* Current time in epoch: time
|
||||
* The min max functions that operate on a variable argument list
|
||||
|
||||
Intermediate results are calculated as double precision
|
||||
floating point values. The final return value of a
|
||||
RankExpression is automatically converted from floating
|
||||
point to a 32-bit unsigned integer by rounding to the
|
||||
nearest integer, with a natural floor of 0 and a ceiling
|
||||
of max(uint32_t), 4294967295. Mathematical errors such as
|
||||
dividing by 0 will fail during evaluation and return a
|
||||
value of 0.
|
||||
|
||||
The source data for a RankExpression can be the name of an
|
||||
IndexField of type uint, another RankExpression or the
|
||||
reserved name text_relevance. The text_relevance source is
|
||||
defined to return an integer from 0 to 1000 (inclusive) to
|
||||
indicate how relevant a document is to the search request,
|
||||
taking into account repetition of search terms in the
|
||||
document and proximity of search terms to each other in
|
||||
each matching IndexField in the document.
|
||||
|
||||
For more information about using rank expressions to
|
||||
customize ranking, see the Amazon CloudSearch Developer
|
||||
Guide.
|
||||
|
||||
:raises: BaseException, InternalException, LimitExceededException,
|
||||
InvalidTypeException, ResourceNotFoundException
|
||||
"""
|
||||
doc_path = ('define_rank_expression_response',
|
||||
'define_rank_expression_result',
|
||||
'rank_expression')
|
||||
params = {'DomainName': domain_name,
|
||||
'RankExpression.RankExpression': rank_expression,
|
||||
'RankExpression.RankName': rank_name}
|
||||
return self.get_response(doc_path, 'DefineRankExpression',
|
||||
params, verb='POST')
|
||||
|
||||
def delete_domain(self, domain_name):
|
||||
"""
|
||||
Delete a search domain.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:raises: BaseException, InternalException
|
||||
"""
|
||||
doc_path = ('delete_domain_response',
|
||||
'delete_domain_result',
|
||||
'domain_status')
|
||||
params = {'DomainName': domain_name}
|
||||
return self.get_response(doc_path, 'DeleteDomain',
|
||||
params, verb='POST')
|
||||
|
||||
def delete_index_field(self, domain_name, field_name):
|
||||
"""
|
||||
Deletes an existing ``IndexField`` from the search domain.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:type field_name: string
|
||||
:param field_name: A string that represents the name of
|
||||
an index field. Field names must begin with a letter and
|
||||
can contain the following characters: a-z (lowercase),
|
||||
0-9, and _ (underscore). Uppercase letters and hyphens are
|
||||
not allowed. The names "body", "docid", and
|
||||
"text_relevance" are reserved and cannot be specified as
|
||||
field or rank expression names.
|
||||
|
||||
:raises: BaseException, InternalException, ResourceNotFoundException
|
||||
"""
|
||||
doc_path = ('delete_index_field_response',
|
||||
'delete_index_field_result',
|
||||
'index_field')
|
||||
params = {'DomainName': domain_name,
|
||||
'IndexFieldName': field_name}
|
||||
return self.get_response(doc_path, 'DeleteIndexField',
|
||||
params, verb='POST')
|
||||
|
||||
def delete_rank_expression(self, domain_name, rank_name):
|
||||
"""
|
||||
Deletes an existing ``RankExpression`` from the search domain.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:type rank_name: string
|
||||
:param rank_name: Name of the ``RankExpression`` to delete.
|
||||
|
||||
:raises: BaseException, InternalException, ResourceNotFoundException
|
||||
"""
|
||||
doc_path = ('delete_rank_expression_response',
|
||||
'delete_rank_expression_result',
|
||||
'rank_expression')
|
||||
params = {'DomainName': domain_name, 'RankName': rank_name}
|
||||
return self.get_response(doc_path, 'DeleteRankExpression',
|
||||
params, verb='POST')
|
||||
|
||||
def describe_default_search_field(self, domain_name):
|
||||
"""
|
||||
Describes options defining the default search field used by
|
||||
indexing for the search domain.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:raises: BaseException, InternalException, ResourceNotFoundException
|
||||
"""
|
||||
doc_path = ('describe_default_search_field_response',
|
||||
'describe_default_search_field_result',
|
||||
'default_search_field')
|
||||
params = {'DomainName': domain_name}
|
||||
return self.get_response(doc_path, 'DescribeDefaultSearchField',
|
||||
params, verb='POST')
|
||||
|
||||
def describe_domains(self, domain_names=None):
|
||||
"""
|
||||
Describes the domains (optionally limited to one or more
|
||||
domains by name) owned by this account.
|
||||
|
||||
:type domain_names: list
|
||||
:param domain_names: Limits the response to the specified domains.
|
||||
|
||||
:raises: BaseException, InternalException
|
||||
"""
|
||||
doc_path = ('describe_domains_response',
|
||||
'describe_domains_result',
|
||||
'domain_status_list')
|
||||
params = {}
|
||||
if domain_names:
|
||||
for i, domain_name in enumerate(domain_names, 1):
|
||||
params['DomainNames.member.%d' % i] = domain_name
|
||||
return self.get_response(doc_path, 'DescribeDomains',
|
||||
params, verb='POST',
|
||||
list_marker='DomainStatusList')
|
||||
|
||||
def describe_index_fields(self, domain_name, field_names=None):
|
||||
"""
|
||||
Describes index fields in the search domain, optionally
|
||||
limited to a single ``IndexField``.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:type field_names: list
|
||||
:param field_names: Limits the response to the specified fields.
|
||||
|
||||
:raises: BaseException, InternalException, ResourceNotFoundException
|
||||
"""
|
||||
doc_path = ('describe_index_fields_response',
|
||||
'describe_index_fields_result',
|
||||
'index_fields')
|
||||
params = {'DomainName': domain_name}
|
||||
if field_names:
|
||||
for i, field_name in enumerate(field_names, 1):
|
||||
params['FieldNames.member.%d' % i] = field_name
|
||||
return self.get_response(doc_path, 'DescribeIndexFields',
|
||||
params, verb='POST',
|
||||
list_marker='IndexFields')
|
||||
|
||||
def describe_rank_expressions(self, domain_name, rank_names=None):
|
||||
"""
|
||||
Describes RankExpressions in the search domain, optionally
|
||||
limited to a single expression.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:type rank_names: list
|
||||
:param rank_names: Limit response to the specified rank names.
|
||||
|
||||
:raises: BaseException, InternalException, ResourceNotFoundException
|
||||
"""
|
||||
doc_path = ('describe_rank_expressions_response',
|
||||
'describe_rank_expressions_result',
|
||||
'rank_expressions')
|
||||
params = {'DomainName': domain_name}
|
||||
if rank_names:
|
||||
for i, rank_name in enumerate(rank_names, 1):
|
||||
params['RankNames.member.%d' % i] = rank_name
|
||||
return self.get_response(doc_path, 'DescribeRankExpressions',
|
||||
params, verb='POST',
|
||||
list_marker='RankExpressions')
|
||||
|
||||
def describe_service_access_policies(self, domain_name):
|
||||
"""
|
||||
Describes the resource-based policies controlling access to
|
||||
the services in this search domain.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:raises: BaseException, InternalException, ResourceNotFoundException
|
||||
"""
|
||||
doc_path = ('describe_service_access_policies_response',
|
||||
'describe_service_access_policies_result',
|
||||
'access_policies')
|
||||
params = {'DomainName': domain_name}
|
||||
return self.get_response(doc_path, 'DescribeServiceAccessPolicies',
|
||||
params, verb='POST')
|
||||
|
||||
def describe_stemming_options(self, domain_name):
|
||||
"""
|
||||
Describes stemming options used by indexing for the search domain.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:raises: BaseException, InternalException, ResourceNotFoundException
|
||||
"""
|
||||
doc_path = ('describe_stemming_options_response',
|
||||
'describe_stemming_options_result',
|
||||
'stems')
|
||||
params = {'DomainName': domain_name}
|
||||
return self.get_response(doc_path, 'DescribeStemmingOptions',
|
||||
params, verb='POST')
|
||||
|
||||
def describe_stopword_options(self, domain_name):
|
||||
"""
|
||||
Describes stopword options used by indexing for the search domain.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:raises: BaseException, InternalException, ResourceNotFoundException
|
||||
"""
|
||||
doc_path = ('describe_stopword_options_response',
|
||||
'describe_stopword_options_result',
|
||||
'stopwords')
|
||||
params = {'DomainName': domain_name}
|
||||
return self.get_response(doc_path, 'DescribeStopwordOptions',
|
||||
params, verb='POST')
|
||||
|
||||
def describe_synonym_options(self, domain_name):
|
||||
"""
|
||||
Describes synonym options used by indexing for the search domain.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:raises: BaseException, InternalException, ResourceNotFoundException
|
||||
"""
|
||||
doc_path = ('describe_synonym_options_response',
|
||||
'describe_synonym_options_result',
|
||||
'synonyms')
|
||||
params = {'DomainName': domain_name}
|
||||
return self.get_response(doc_path, 'DescribeSynonymOptions',
|
||||
params, verb='POST')
|
||||
|
||||
def index_documents(self, domain_name):
|
||||
"""
|
||||
Tells the search domain to start scanning its documents using
|
||||
the latest text processing options and ``IndexFields``. This
|
||||
operation must be invoked to make visible in searches any
|
||||
options whose <a>OptionStatus</a> has ``OptionState`` of
|
||||
``RequiresIndexDocuments``.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:raises: BaseException, InternalException, ResourceNotFoundException
|
||||
"""
|
||||
doc_path = ('index_documents_response',
|
||||
'index_documents_result',
|
||||
'field_names')
|
||||
params = {'DomainName': domain_name}
|
||||
return self.get_response(doc_path, 'IndexDocuments', params,
|
||||
verb='POST', list_marker='FieldNames')
|
||||
|
||||
def update_default_search_field(self, domain_name, default_search_field):
|
||||
"""
|
||||
Updates options defining the default search field used by
|
||||
indexing for the search domain.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:type default_search_field: string
|
||||
:param default_search_field: The IndexField to use for search
|
||||
requests issued with the q parameter. The default is an
|
||||
empty string, which automatically searches all text
|
||||
fields.
|
||||
|
||||
:raises: BaseException, InternalException, InvalidTypeException,
|
||||
ResourceNotFoundException
|
||||
"""
|
||||
doc_path = ('update_default_search_field_response',
|
||||
'update_default_search_field_result',
|
||||
'default_search_field')
|
||||
params = {'DomainName': domain_name,
|
||||
'DefaultSearchField': default_search_field}
|
||||
return self.get_response(doc_path, 'UpdateDefaultSearchField',
|
||||
params, verb='POST')
|
||||
|
||||
def update_service_access_policies(self, domain_name, access_policies):
|
||||
"""
|
||||
Updates the policies controlling access to the services in
|
||||
this search domain.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:type access_policies: string
|
||||
:param access_policies: An IAM access policy as described in
|
||||
The Access Policy Language in Using AWS Identity and
|
||||
Access Management. The maximum size of an access policy
|
||||
document is 100KB.
|
||||
|
||||
:raises: BaseException, InternalException, LimitExceededException,
|
||||
ResourceNotFoundException, InvalidTypeException
|
||||
"""
|
||||
doc_path = ('update_service_access_policies_response',
|
||||
'update_service_access_policies_result',
|
||||
'access_policies')
|
||||
params = {'AccessPolicies': access_policies,
|
||||
'DomainName': domain_name}
|
||||
return self.get_response(doc_path, 'UpdateServiceAccessPolicies',
|
||||
params, verb='POST')
|
||||
|
||||
def update_stemming_options(self, domain_name, stems):
|
||||
"""
|
||||
Updates stemming options used by indexing for the search domain.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:type stems: string
|
||||
:param stems: Maps terms to their stems. The JSON object
|
||||
has a single key called "stems" whose value is a
|
||||
dict mapping terms to their stems. The maximum size
|
||||
of a stemming document is 500KB.
|
||||
Example: {"stems":{"people": "person", "walking":"walk"}}
|
||||
|
||||
:raises: BaseException, InternalException, InvalidTypeException,
|
||||
LimitExceededException, ResourceNotFoundException
|
||||
"""
|
||||
doc_path = ('update_stemming_options_response',
|
||||
'update_stemming_options_result',
|
||||
'stems')
|
||||
params = {'DomainName': domain_name,
|
||||
'Stems': stems}
|
||||
return self.get_response(doc_path, 'UpdateStemmingOptions',
|
||||
params, verb='POST')
|
||||
|
||||
def update_stopword_options(self, domain_name, stopwords):
|
||||
"""
|
||||
Updates stopword options used by indexing for the search domain.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:type stopwords: string
|
||||
:param stopwords: Lists stopwords in a JSON object. The object has a
|
||||
single key called "stopwords" whose value is an array of strings.
|
||||
The maximum size of a stopwords document is 10KB. Example:
|
||||
{"stopwords": ["a", "an", "the", "of"]}
|
||||
|
||||
:raises: BaseException, InternalException, InvalidTypeException,
|
||||
LimitExceededException, ResourceNotFoundException
|
||||
"""
|
||||
doc_path = ('update_stopword_options_response',
|
||||
'update_stopword_options_result',
|
||||
'stopwords')
|
||||
params = {'DomainName': domain_name,
|
||||
'Stopwords': stopwords}
|
||||
return self.get_response(doc_path, 'UpdateStopwordOptions',
|
||||
params, verb='POST')
|
||||
|
||||
def update_synonym_options(self, domain_name, synonyms):
|
||||
"""
|
||||
Updates synonym options used by indexing for the search domain.
|
||||
|
||||
:type domain_name: string
|
||||
:param domain_name: A string that represents the name of a
|
||||
domain. Domain names must be unique across the domains
|
||||
owned by an account within an AWS region. Domain names
|
||||
must start with a letter or number and can contain the
|
||||
following characters: a-z (lowercase), 0-9, and -
|
||||
(hyphen). Uppercase letters and underscores are not
|
||||
allowed.
|
||||
|
||||
:type synonyms: string
|
||||
:param synonyms: Maps terms to their synonyms. The JSON object
|
||||
has a single key "synonyms" whose value is a dict mapping terms
|
||||
to their synonyms. Each synonym is a simple string or an
|
||||
array of strings. The maximum size of a stopwords document
|
||||
is 100KB. Example:
|
||||
{"synonyms": {"cat": ["feline", "kitten"], "puppy": "dog"}}
|
||||
|
||||
:raises: BaseException, InternalException, InvalidTypeException,
|
||||
LimitExceededException, ResourceNotFoundException
|
||||
"""
|
||||
doc_path = ('update_synonym_options_response',
|
||||
'update_synonym_options_result',
|
||||
'synonyms')
|
||||
params = {'DomainName': domain_name,
|
||||
'Synonyms': synonyms}
|
||||
return self.get_response(doc_path, 'UpdateSynonymOptions',
|
||||
params, verb='POST')
|
|
@ -1,75 +0,0 @@
|
|||
# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
from .layer1 import Layer1
|
||||
from .domain import Domain
|
||||
|
||||
|
||||
class Layer2(object):
|
||||
|
||||
def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
|
||||
is_secure=True, port=None, proxy=None, proxy_port=None,
|
||||
host=None, debug=0, session_token=None, region=None,
|
||||
validate_certs=True):
|
||||
self.layer1 = Layer1(
|
||||
aws_access_key_id=aws_access_key_id,
|
||||
aws_secret_access_key=aws_secret_access_key,
|
||||
is_secure=is_secure,
|
||||
port=port,
|
||||
proxy=proxy,
|
||||
proxy_port=proxy_port,
|
||||
host=host,
|
||||
debug=debug,
|
||||
security_token=session_token,
|
||||
region=region,
|
||||
validate_certs=validate_certs)
|
||||
|
||||
def list_domains(self, domain_names=None):
|
||||
"""
|
||||
Return a list of :class:`boto.cloudsearch.domain.Domain`
|
||||
objects for each domain defined in the current account.
|
||||
"""
|
||||
domain_data = self.layer1.describe_domains(domain_names)
|
||||
return [Domain(self.layer1, data) for data in domain_data]
|
||||
|
||||
def create_domain(self, domain_name):
|
||||
"""
|
||||
Create a new CloudSearch domain and return the corresponding
|
||||
:class:`boto.cloudsearch.domain.Domain` object.
|
||||
"""
|
||||
data = self.layer1.create_domain(domain_name)
|
||||
return Domain(self.layer1, data)
|
||||
|
||||
def lookup(self, domain_name):
|
||||
"""
|
||||
Lookup a single domain
|
||||
:param domain_name: The name of the domain to look up
|
||||
:type domain_name: str
|
||||
|
||||
:return: Domain object, or None if the domain isn't found
|
||||
:rtype: :class:`boto.cloudsearch.domain.Domain`
|
||||
"""
|
||||
domains = self.list_domains(domain_names=[domain_name])
|
||||
if len(domains) > 0:
|
||||
return domains[0]
|
|
@ -1,248 +0,0 @@
|
|||
# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
import time
|
||||
from boto.compat import json
|
||||
|
||||
|
||||
class OptionStatus(dict):
|
||||
"""
|
||||
Presents a combination of status field (defined below) which are
|
||||
accessed as attributes and option values which are stored in the
|
||||
native Python dictionary. In this class, the option values are
|
||||
merged from a JSON object that is stored as the Option part of
|
||||
the object.
|
||||
|
||||
:ivar domain_name: The name of the domain this option is associated with.
|
||||
:ivar create_date: A timestamp for when this option was created.
|
||||
:ivar state: The state of processing a change to an option.
|
||||
Possible values:
|
||||
|
||||
* RequiresIndexDocuments: the option's latest value will not
|
||||
be visible in searches until IndexDocuments has been called
|
||||
and indexing is complete.
|
||||
* Processing: the option's latest value is not yet visible in
|
||||
all searches but is in the process of being activated.
|
||||
* Active: the option's latest value is completely visible.
|
||||
|
||||
:ivar update_date: A timestamp for when this option was updated.
|
||||
:ivar update_version: A unique integer that indicates when this
|
||||
option was last updated.
|
||||
"""
|
||||
|
||||
def __init__(self, domain, data=None, refresh_fn=None, save_fn=None):
|
||||
self.domain = domain
|
||||
self.refresh_fn = refresh_fn
|
||||
self.save_fn = save_fn
|
||||
self.refresh(data)
|
||||
|
||||
def _update_status(self, status):
|
||||
self.creation_date = status['creation_date']
|
||||
self.status = status['state']
|
||||
self.update_date = status['update_date']
|
||||
self.update_version = int(status['update_version'])
|
||||
|
||||
def _update_options(self, options):
|
||||
if options:
|
||||
self.update(json.loads(options))
|
||||
|
||||
def refresh(self, data=None):
|
||||
"""
|
||||
Refresh the local state of the object. You can either pass
|
||||
new state data in as the parameter ``data`` or, if that parameter
|
||||
is omitted, the state data will be retrieved from CloudSearch.
|
||||
"""
|
||||
if not data:
|
||||
if self.refresh_fn:
|
||||
data = self.refresh_fn(self.domain.name)
|
||||
if data:
|
||||
self._update_status(data['status'])
|
||||
self._update_options(data['options'])
|
||||
|
||||
def to_json(self):
|
||||
"""
|
||||
Return the JSON representation of the options as a string.
|
||||
"""
|
||||
return json.dumps(self)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'CreationDate':
|
||||
self.created = value
|
||||
elif name == 'State':
|
||||
self.state = value
|
||||
elif name == 'UpdateDate':
|
||||
self.updated = value
|
||||
elif name == 'UpdateVersion':
|
||||
self.update_version = int(value)
|
||||
elif name == 'Options':
|
||||
self.update_from_json_doc(value)
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Write the current state of the local object back to the
|
||||
CloudSearch service.
|
||||
"""
|
||||
if self.save_fn:
|
||||
data = self.save_fn(self.domain.name, self.to_json())
|
||||
self.refresh(data)
|
||||
|
||||
def wait_for_state(self, state):
|
||||
"""
|
||||
Performs polling of CloudSearch to wait for the ``state``
|
||||
of this object to change to the provided state.
|
||||
"""
|
||||
while self.state != state:
|
||||
time.sleep(5)
|
||||
self.refresh()
|
||||
|
||||
|
||||
class IndexFieldStatus(OptionStatus):
|
||||
|
||||
def _update_options(self, options):
|
||||
self.update(options)
|
||||
|
||||
def save(self):
|
||||
pass
|
||||
|
||||
|
||||
class RankExpressionStatus(IndexFieldStatus):
|
||||
|
||||
pass
|
||||
|
||||
class ServicePoliciesStatus(OptionStatus):
|
||||
|
||||
def new_statement(self, arn, ip):
|
||||
"""
|
||||
Returns a new policy statement that will allow
|
||||
access to the service described by ``arn`` by the
|
||||
ip specified in ``ip``.
|
||||
|
||||
:type arn: string
|
||||
:param arn: The Amazon Resource Notation identifier for the
|
||||
service you wish to provide access to. This would be
|
||||
either the search service or the document service.
|
||||
|
||||
:type ip: string
|
||||
:param ip: An IP address or CIDR block you wish to grant access
|
||||
to.
|
||||
"""
|
||||
return {
|
||||
"Effect":"Allow",
|
||||
"Action":"*", # Docs say use GET, but denies unless *
|
||||
"Resource": arn,
|
||||
"Condition": {
|
||||
"IpAddress": {
|
||||
"aws:SourceIp": [ip]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def _allow_ip(self, arn, ip):
|
||||
if 'Statement' not in self:
|
||||
s = self.new_statement(arn, ip)
|
||||
self['Statement'] = [s]
|
||||
self.save()
|
||||
else:
|
||||
add_statement = True
|
||||
for statement in self['Statement']:
|
||||
if statement['Resource'] == arn:
|
||||
for condition_name in statement['Condition']:
|
||||
if condition_name == 'IpAddress':
|
||||
add_statement = False
|
||||
condition = statement['Condition'][condition_name]
|
||||
if ip not in condition['aws:SourceIp']:
|
||||
condition['aws:SourceIp'].append(ip)
|
||||
|
||||
if add_statement:
|
||||
s = self.new_statement(arn, ip)
|
||||
self['Statement'].append(s)
|
||||
self.save()
|
||||
|
||||
def allow_search_ip(self, ip):
|
||||
"""
|
||||
Add the provided ip address or CIDR block to the list of
|
||||
allowable address for the search service.
|
||||
|
||||
:type ip: string
|
||||
:param ip: An IP address or CIDR block you wish to grant access
|
||||
to.
|
||||
"""
|
||||
arn = self.domain.search_service_arn
|
||||
self._allow_ip(arn, ip)
|
||||
|
||||
def allow_doc_ip(self, ip):
|
||||
"""
|
||||
Add the provided ip address or CIDR block to the list of
|
||||
allowable address for the document service.
|
||||
|
||||
:type ip: string
|
||||
:param ip: An IP address or CIDR block you wish to grant access
|
||||
to.
|
||||
"""
|
||||
arn = self.domain.doc_service_arn
|
||||
self._allow_ip(arn, ip)
|
||||
|
||||
def _disallow_ip(self, arn, ip):
|
||||
if 'Statement' not in self:
|
||||
return
|
||||
need_update = False
|
||||
for statement in self['Statement']:
|
||||
if statement['Resource'] == arn:
|
||||
for condition_name in statement['Condition']:
|
||||
if condition_name == 'IpAddress':
|
||||
condition = statement['Condition'][condition_name]
|
||||
if ip in condition['aws:SourceIp']:
|
||||
condition['aws:SourceIp'].remove(ip)
|
||||
need_update = True
|
||||
if need_update:
|
||||
self.save()
|
||||
|
||||
def disallow_search_ip(self, ip):
|
||||
"""
|
||||
Remove the provided ip address or CIDR block from the list of
|
||||
allowable address for the search service.
|
||||
|
||||
:type ip: string
|
||||
:param ip: An IP address or CIDR block you wish to grant access
|
||||
to.
|
||||
"""
|
||||
arn = self.domain.search_service_arn
|
||||
self._disallow_ip(arn, ip)
|
||||
|
||||
def disallow_doc_ip(self, ip):
|
||||
"""
|
||||
Remove the provided ip address or CIDR block from the list of
|
||||
allowable address for the document service.
|
||||
|
||||
:type ip: string
|
||||
:param ip: An IP address or CIDR block you wish to grant access
|
||||
to.
|
||||
"""
|
||||
arn = self.domain.doc_service_arn
|
||||
self._disallow_ip(arn, ip)
|
|
@ -1,377 +0,0 @@
|
|||
# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
from math import ceil
|
||||
import time
|
||||
import boto
|
||||
from boto.compat import json
|
||||
import requests
|
||||
|
||||
|
||||
class SearchServiceException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class CommitMismatchError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SearchResults(object):
|
||||
def __init__(self, **attrs):
|
||||
self.rid = attrs['info']['rid']
|
||||
# self.doc_coverage_pct = attrs['info']['doc-coverage-pct']
|
||||
self.cpu_time_ms = attrs['info']['cpu-time-ms']
|
||||
self.time_ms = attrs['info']['time-ms']
|
||||
self.hits = attrs['hits']['found']
|
||||
self.docs = attrs['hits']['hit']
|
||||
self.start = attrs['hits']['start']
|
||||
self.rank = attrs['rank']
|
||||
self.match_expression = attrs['match-expr']
|
||||
self.query = attrs['query']
|
||||
self.search_service = attrs['search_service']
|
||||
|
||||
self.facets = {}
|
||||
if 'facets' in attrs:
|
||||
for (facet, values) in attrs['facets'].iteritems():
|
||||
if 'constraints' in values:
|
||||
self.facets[facet] = dict((k, v) for (k, v) in map(lambda x: (x['value'], x['count']), values['constraints']))
|
||||
|
||||
self.num_pages_needed = ceil(self.hits / self.query.real_size)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.docs)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.docs)
|
||||
|
||||
def next_page(self):
|
||||
"""Call Cloudsearch to get the next page of search results
|
||||
|
||||
:rtype: :class:`boto.cloudsearch.search.SearchResults`
|
||||
:return: the following page of search results
|
||||
"""
|
||||
if self.query.page <= self.num_pages_needed:
|
||||
self.query.start += self.query.real_size
|
||||
self.query.page += 1
|
||||
return self.search_service(self.query)
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
|
||||
class Query(object):
|
||||
|
||||
RESULTS_PER_PAGE = 500
|
||||
|
||||
def __init__(self, q=None, bq=None, rank=None,
|
||||
return_fields=None, size=10,
|
||||
start=0, facet=None, facet_constraints=None,
|
||||
facet_sort=None, facet_top_n=None, t=None):
|
||||
|
||||
self.q = q
|
||||
self.bq = bq
|
||||
self.rank = rank or []
|
||||
self.return_fields = return_fields or []
|
||||
self.start = start
|
||||
self.facet = facet or []
|
||||
self.facet_constraints = facet_constraints or {}
|
||||
self.facet_sort = facet_sort or {}
|
||||
self.facet_top_n = facet_top_n or {}
|
||||
self.t = t or {}
|
||||
self.page = 0
|
||||
self.update_size(size)
|
||||
|
||||
def update_size(self, new_size):
|
||||
self.size = new_size
|
||||
self.real_size = Query.RESULTS_PER_PAGE if (self.size >
|
||||
Query.RESULTS_PER_PAGE or self.size == 0) else self.size
|
||||
|
||||
def to_params(self):
|
||||
"""Transform search parameters from instance properties to a dictionary
|
||||
|
||||
:rtype: dict
|
||||
:return: search parameters
|
||||
"""
|
||||
params = {'start': self.start, 'size': self.real_size}
|
||||
|
||||
if self.q:
|
||||
params['q'] = self.q
|
||||
|
||||
if self.bq:
|
||||
params['bq'] = self.bq
|
||||
|
||||
if self.rank:
|
||||
params['rank'] = ','.join(self.rank)
|
||||
|
||||
if self.return_fields:
|
||||
params['return-fields'] = ','.join(self.return_fields)
|
||||
|
||||
if self.facet:
|
||||
params['facet'] = ','.join(self.facet)
|
||||
|
||||
if self.facet_constraints:
|
||||
for k, v in self.facet_constraints.iteritems():
|
||||
params['facet-%s-constraints' % k] = v
|
||||
|
||||
if self.facet_sort:
|
||||
for k, v in self.facet_sort.iteritems():
|
||||
params['facet-%s-sort' % k] = v
|
||||
|
||||
if self.facet_top_n:
|
||||
for k, v in self.facet_top_n.iteritems():
|
||||
params['facet-%s-top-n' % k] = v
|
||||
|
||||
if self.t:
|
||||
for k, v in self.t.iteritems():
|
||||
params['t-%s' % k] = v
|
||||
return params
|
||||
|
||||
|
||||
class SearchConnection(object):
|
||||
|
||||
def __init__(self, domain=None, endpoint=None):
|
||||
self.domain = domain
|
||||
self.endpoint = endpoint
|
||||
if not endpoint:
|
||||
self.endpoint = domain.search_service_endpoint
|
||||
|
||||
def build_query(self, q=None, bq=None, rank=None, return_fields=None,
|
||||
size=10, start=0, facet=None, facet_constraints=None,
|
||||
facet_sort=None, facet_top_n=None, t=None):
|
||||
return Query(q=q, bq=bq, rank=rank, return_fields=return_fields,
|
||||
size=size, start=start, facet=facet,
|
||||
facet_constraints=facet_constraints,
|
||||
facet_sort=facet_sort, facet_top_n=facet_top_n, t=t)
|
||||
|
||||
def search(self, q=None, bq=None, rank=None, return_fields=None,
|
||||
size=10, start=0, facet=None, facet_constraints=None,
|
||||
facet_sort=None, facet_top_n=None, t=None):
|
||||
"""
|
||||
Send a query to CloudSearch
|
||||
|
||||
Each search query should use at least the q or bq argument to specify
|
||||
the search parameter. The other options are used to specify the
|
||||
criteria of the search.
|
||||
|
||||
:type q: string
|
||||
:param q: A string to search the default search fields for.
|
||||
|
||||
:type bq: string
|
||||
:param bq: A string to perform a Boolean search. This can be used to
|
||||
create advanced searches.
|
||||
|
||||
:type rank: List of strings
|
||||
:param rank: A list of fields or rank expressions used to order the
|
||||
search results. A field can be reversed by using the - operator.
|
||||
``['-year', 'author']``
|
||||
|
||||
:type return_fields: List of strings
|
||||
:param return_fields: A list of fields which should be returned by the
|
||||
search. If this field is not specified, only IDs will be returned.
|
||||
``['headline']``
|
||||
|
||||
:type size: int
|
||||
:param size: Number of search results to specify
|
||||
|
||||
:type start: int
|
||||
:param start: Offset of the first search result to return (can be used
|
||||
for paging)
|
||||
|
||||
:type facet: list
|
||||
:param facet: List of fields for which facets should be returned
|
||||
``['colour', 'size']``
|
||||
|
||||
:type facet_constraints: dict
|
||||
:param facet_constraints: Use to limit facets to specific values
|
||||
specified as comma-delimited strings in a Dictionary of facets
|
||||
``{'colour': "'blue','white','red'", 'size': "big"}``
|
||||
|
||||
:type facet_sort: dict
|
||||
:param facet_sort: Rules used to specify the order in which facet
|
||||
values should be returned. Allowed values are *alpha*, *count*,
|
||||
*max*, *sum*. Use *alpha* to sort alphabetical, and *count* to sort
|
||||
the facet by number of available result.
|
||||
``{'color': 'alpha', 'size': 'count'}``
|
||||
|
||||
:type facet_top_n: dict
|
||||
:param facet_top_n: Dictionary of facets and number of facets to
|
||||
return.
|
||||
``{'colour': 2}``
|
||||
|
||||
:type t: dict
|
||||
:param t: Specify ranges for specific fields
|
||||
``{'year': '2000..2005'}``
|
||||
|
||||
:rtype: :class:`boto.cloudsearch.search.SearchResults`
|
||||
:return: Returns the results of this search
|
||||
|
||||
The following examples all assume we have indexed a set of documents
|
||||
with fields: *author*, *date*, *headline*
|
||||
|
||||
A simple search will look for documents whose default text search
|
||||
fields will contain the search word exactly:
|
||||
|
||||
>>> search(q='Tim') # Return documents with the word Tim in them (but not Timothy)
|
||||
|
||||
A simple search with more keywords will return documents whose default
|
||||
text search fields contain the search strings together or separately.
|
||||
|
||||
>>> search(q='Tim apple') # Will match "tim" and "apple"
|
||||
|
||||
More complex searches require the boolean search operator.
|
||||
|
||||
Wildcard searches can be used to search for any words that start with
|
||||
the search string.
|
||||
|
||||
>>> search(bq="'Tim*'") # Return documents with words like Tim or Timothy)
|
||||
|
||||
Search terms can also be combined. Allowed operators are "and", "or",
|
||||
"not", "field", "optional", "token", "phrase", or "filter"
|
||||
|
||||
>>> search(bq="(and 'Tim' (field author 'John Smith'))")
|
||||
|
||||
Facets allow you to show classification information about the search
|
||||
results. For example, you can retrieve the authors who have written
|
||||
about Tim:
|
||||
|
||||
>>> search(q='Tim', facet=['Author'])
|
||||
|
||||
With facet_constraints, facet_top_n and facet_sort more complicated
|
||||
constraints can be specified such as returning the top author out of
|
||||
John Smith and Mark Smith who have a document with the word Tim in it.
|
||||
|
||||
>>> search(q='Tim',
|
||||
... facet=['Author'],
|
||||
... facet_constraints={'author': "'John Smith','Mark Smith'"},
|
||||
... facet=['author'],
|
||||
... facet_top_n={'author': 1},
|
||||
... facet_sort={'author': 'count'})
|
||||
"""
|
||||
|
||||
query = self.build_query(q=q, bq=bq, rank=rank,
|
||||
return_fields=return_fields,
|
||||
size=size, start=start, facet=facet,
|
||||
facet_constraints=facet_constraints,
|
||||
facet_sort=facet_sort,
|
||||
facet_top_n=facet_top_n, t=t)
|
||||
return self(query)
|
||||
|
||||
def __call__(self, query):
|
||||
"""Make a call to CloudSearch
|
||||
|
||||
:type query: :class:`boto.cloudsearch.search.Query`
|
||||
:param query: A group of search criteria
|
||||
|
||||
:rtype: :class:`boto.cloudsearch.search.SearchResults`
|
||||
:return: search results
|
||||
"""
|
||||
url = "http://%s/2011-02-01/search" % (self.endpoint)
|
||||
params = query.to_params()
|
||||
|
||||
r = requests.get(url, params=params)
|
||||
try:
|
||||
data = json.loads(r.content)
|
||||
except ValueError, e:
|
||||
if r.status_code == 403:
|
||||
msg = ''
|
||||
import re
|
||||
g = re.search('<html><body><h1>403 Forbidden</h1>([^<]+)<', r.content)
|
||||
try:
|
||||
msg = ': %s' % (g.groups()[0].strip())
|
||||
except AttributeError:
|
||||
pass
|
||||
raise SearchServiceException('Authentication error from Amazon%s' % msg)
|
||||
raise SearchServiceException("Got non-json response from Amazon")
|
||||
data['query'] = query
|
||||
data['search_service'] = self
|
||||
|
||||
if 'messages' in data and 'error' in data:
|
||||
for m in data['messages']:
|
||||
if m['severity'] == 'fatal':
|
||||
raise SearchServiceException("Error processing search %s "
|
||||
"=> %s" % (params, m['message']), query)
|
||||
elif 'error' in data:
|
||||
raise SearchServiceException("Unknown error processing search %s"
|
||||
% (params), query)
|
||||
|
||||
return SearchResults(**data)
|
||||
|
||||
def get_all_paged(self, query, per_page):
|
||||
"""Get a generator to iterate over all pages of search results
|
||||
|
||||
:type query: :class:`boto.cloudsearch.search.Query`
|
||||
:param query: A group of search criteria
|
||||
|
||||
:type per_page: int
|
||||
:param per_page: Number of docs in each :class:`boto.cloudsearch.search.SearchResults` object.
|
||||
|
||||
:rtype: generator
|
||||
:return: Generator containing :class:`boto.cloudsearch.search.SearchResults`
|
||||
"""
|
||||
query.update_size(per_page)
|
||||
page = 0
|
||||
num_pages_needed = 0
|
||||
while page <= num_pages_needed:
|
||||
results = self(query)
|
||||
num_pages_needed = results.num_pages_needed
|
||||
yield results
|
||||
query.start += query.real_size
|
||||
page += 1
|
||||
|
||||
def get_all_hits(self, query):
|
||||
"""Get a generator to iterate over all search results
|
||||
|
||||
Transparently handles the results paging from Cloudsearch
|
||||
search results so even if you have many thousands of results
|
||||
you can iterate over all results in a reasonably efficient
|
||||
manner.
|
||||
|
||||
:type query: :class:`boto.cloudsearch.search.Query`
|
||||
:param query: A group of search criteria
|
||||
|
||||
:rtype: generator
|
||||
:return: All docs matching query
|
||||
"""
|
||||
page = 0
|
||||
num_pages_needed = 0
|
||||
while page <= num_pages_needed:
|
||||
results = self(query)
|
||||
num_pages_needed = results.num_pages_needed
|
||||
for doc in results:
|
||||
yield doc
|
||||
query.start += query.real_size
|
||||
page += 1
|
||||
|
||||
def get_num_hits(self, query):
|
||||
"""Return the total number of hits for query
|
||||
|
||||
:type query: :class:`boto.cloudsearch.search.Query`
|
||||
:param query: a group of search criteria
|
||||
|
||||
:rtype: int
|
||||
:return: Total number of hits for query
|
||||
"""
|
||||
query.update_size(1)
|
||||
return self(query).hits
|
||||
|
||||
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
# Copyright (c) 202 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
class SourceAttribute(object):
|
||||
"""
|
||||
Provide information about attributes for an index field.
|
||||
A maximum of 20 source attributes can be configured for
|
||||
each index field.
|
||||
|
||||
:ivar default: Optional default value if the source attribute
|
||||
is not specified in a document.
|
||||
|
||||
:ivar name: The name of the document source field to add
|
||||
to this ``IndexField``.
|
||||
|
||||
:ivar data_function: Identifies the transformation to apply
|
||||
when copying data from a source attribute.
|
||||
|
||||
:ivar data_map: The value is a dict with the following keys:
|
||||
* cases - A dict that translates source field values
|
||||
to custom values.
|
||||
* default - An optional default value to use if the
|
||||
source attribute is not specified in a document.
|
||||
* name - the name of the document source field to add
|
||||
to this ``IndexField``
|
||||
:ivar data_trim_title: Trims common title words from a source
|
||||
document attribute when populating an ``IndexField``.
|
||||
This can be used to create an ``IndexField`` you can
|
||||
use for sorting. The value is a dict with the following
|
||||
fields:
|
||||
* default - An optional default value.
|
||||
* language - an IETF RFC 4646 language code.
|
||||
* separator - The separator that follows the text to trim.
|
||||
* name - The name of the document source field to add.
|
||||
"""
|
||||
|
||||
ValidDataFunctions = ('Copy', 'TrimTitle', 'Map')
|
||||
|
||||
def __init__(self):
|
||||
self.data_copy = {}
|
||||
self._data_function = self.ValidDataFunctions[0]
|
||||
self.data_map = {}
|
||||
self.data_trim_title = {}
|
||||
|
||||
@property
|
||||
def data_function(self):
|
||||
return self._data_function
|
||||
|
||||
@data_function.setter
|
||||
def data_function(self, value):
|
||||
if value not in self.ValidDataFunctions:
|
||||
valid = '|'.join(self.ValidDataFunctions)
|
||||
raise ValueError('data_function must be one of: %s' % valid)
|
||||
self._data_function = value
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
# This allows boto modules to say "from boto.compat import json". This is
|
||||
# preferred so that all modules don't have to repeat this idiom.
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
File diff suppressed because it is too large
Load Diff
|
@ -1,22 +0,0 @@
|
|||
# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
# Copyright (c) 2006,2007 Chris Moyer
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
"""
|
||||
This module was contributed by Chris Moyer. It provides a subclass of the
|
||||
SQS Message class that supports YAML as the body of the message.
|
||||
|
||||
This module requires the yaml module.
|
||||
"""
|
||||
from boto.sqs.message import Message
|
||||
import yaml
|
||||
|
||||
class YAMLMessage(Message):
|
||||
"""
|
||||
The YAMLMessage class provides a YAML compatible message. Encoding and
|
||||
decoding are handled automaticaly.
|
||||
|
||||
Access this message data like such:
|
||||
|
||||
m.data = [ 1, 2, 3]
|
||||
m.data[0] # Returns 1
|
||||
|
||||
This depends on the PyYAML package
|
||||
"""
|
||||
|
||||
def __init__(self, queue=None, body='', xml_attrs=None):
|
||||
self.data = None
|
||||
Message.__init__(self, queue, body)
|
||||
|
||||
def set_body(self, body):
|
||||
self.data = yaml.load(body)
|
||||
|
||||
def get_body(self):
|
||||
return yaml.dump(self.data)
|
|
@ -1,42 +0,0 @@
|
|||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
from boto.exception import JSONResponseError
|
||||
|
||||
|
||||
class PipelineDeletedException(JSONResponseError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidRequestException(JSONResponseError):
|
||||
pass
|
||||
|
||||
|
||||
class TaskNotFoundException(JSONResponseError):
|
||||
pass
|
||||
|
||||
|
||||
class PipelineNotFoundException(JSONResponseError):
|
||||
pass
|
||||
|
||||
|
||||
class InternalServiceError(JSONResponseError):
|
||||
pass
|
|
@ -1,641 +0,0 @@
|
|||
# Copyright (c) 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
import boto
|
||||
from boto.compat import json
|
||||
from boto.connection import AWSQueryConnection
|
||||
from boto.regioninfo import RegionInfo
|
||||
from boto.exception import JSONResponseError
|
||||
from boto.datapipeline import exceptions
|
||||
|
||||
|
||||
class DataPipelineConnection(AWSQueryConnection):
|
||||
"""
|
||||
This is the AWS Data Pipeline API Reference . This guide provides
|
||||
descriptions and samples of the AWS Data Pipeline API.
|
||||
|
||||
AWS Data Pipeline is a web service that configures and manages a
|
||||
data-driven workflow called a pipeline. AWS Data Pipeline handles
|
||||
the details of scheduling and ensuring that data dependencies are
|
||||
met so your application can focus on processing the data.
|
||||
|
||||
The AWS Data Pipeline API implements two main sets of
|
||||
functionality. The first set of actions configure the pipeline in
|
||||
the web service. You call these actions to create a pipeline and
|
||||
define data sources, schedules, dependencies, and the transforms
|
||||
to be performed on the data.
|
||||
|
||||
The second set of actions are used by a task runner application
|
||||
that calls the AWS Data Pipeline API to receive the next task
|
||||
ready for processing. The logic for performing the task, such as
|
||||
querying the data, running data analysis, or converting the data
|
||||
from one format to another, is contained within the task runner.
|
||||
The task runner performs the task assigned to it by the web
|
||||
service, reporting progress to the web service as it does so. When
|
||||
the task is done, the task runner reports the final success or
|
||||
failure of the task to the web service.
|
||||
|
||||
AWS Data Pipeline provides an open-source implementation of a task
|
||||
runner called AWS Data Pipeline Task Runner. AWS Data Pipeline
|
||||
Task Runner provides logic for common data management scenarios,
|
||||
such as performing database queries and running data analysis
|
||||
using Amazon Elastic MapReduce (Amazon EMR). You can use AWS Data
|
||||
Pipeline Task Runner as your task runner, or you can write your
|
||||
own task runner to provide custom data management.
|
||||
|
||||
The AWS Data Pipeline API uses the Signature Version 4 protocol
|
||||
for signing requests. For more information about how to sign a
|
||||
request with this protocol, see `Signature Version 4 Signing
|
||||
Process`_. In the code examples in this reference, the Signature
|
||||
Version 4 Request parameters are represented as AuthParams.
|
||||
"""
|
||||
APIVersion = "2012-10-29"
|
||||
DefaultRegionName = "us-east-1"
|
||||
DefaultRegionEndpoint = "datapipeline.us-east-1.amazonaws.com"
|
||||
ServiceName = "DataPipeline"
|
||||
TargetPrefix = "DataPipeline"
|
||||
ResponseError = JSONResponseError
|
||||
|
||||
_faults = {
|
||||
"PipelineDeletedException": exceptions.PipelineDeletedException,
|
||||
"InvalidRequestException": exceptions.InvalidRequestException,
|
||||
"TaskNotFoundException": exceptions.TaskNotFoundException,
|
||||
"PipelineNotFoundException": exceptions.PipelineNotFoundException,
|
||||
"InternalServiceError": exceptions.InternalServiceError,
|
||||
}
|
||||
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
region = kwargs.get('region')
|
||||
if not region:
|
||||
region = RegionInfo(self, self.DefaultRegionName,
|
||||
self.DefaultRegionEndpoint)
|
||||
kwargs['host'] = region.endpoint
|
||||
AWSQueryConnection.__init__(self, **kwargs)
|
||||
self.region = region
|
||||
|
||||
def _required_auth_capability(self):
|
||||
return ['hmac-v4']
|
||||
|
||||
def activate_pipeline(self, pipeline_id):
|
||||
"""
|
||||
Validates a pipeline and initiates processing. If the pipeline
|
||||
does not pass validation, activation fails.
|
||||
|
||||
Call this action to start processing pipeline tasks of a
|
||||
pipeline you've created using the CreatePipeline and
|
||||
PutPipelineDefinition actions. A pipeline cannot be modified
|
||||
after it has been successfully activated.
|
||||
|
||||
:type pipeline_id: string
|
||||
:param pipeline_id: The identifier of the pipeline to activate.
|
||||
|
||||
"""
|
||||
params = {'pipelineId': pipeline_id, }
|
||||
return self.make_request(action='ActivatePipeline',
|
||||
body=json.dumps(params))
|
||||
|
||||
def create_pipeline(self, name, unique_id, description=None):
|
||||
"""
|
||||
Creates a new empty pipeline. When this action succeeds, you
|
||||
can then use the PutPipelineDefinition action to populate the
|
||||
pipeline.
|
||||
|
||||
:type name: string
|
||||
:param name: The name of the new pipeline. You can use the same name
|
||||
for multiple pipelines associated with your AWS account, because
|
||||
AWS Data Pipeline assigns each new pipeline a unique pipeline
|
||||
identifier.
|
||||
|
||||
:type unique_id: string
|
||||
:param unique_id: A unique identifier that you specify. This identifier
|
||||
is not the same as the pipeline identifier assigned by AWS Data
|
||||
Pipeline. You are responsible for defining the format and ensuring
|
||||
the uniqueness of this identifier. You use this parameter to ensure
|
||||
idempotency during repeated calls to CreatePipeline. For example,
|
||||
if the first call to CreatePipeline does not return a clear
|
||||
success, you can pass in the same unique identifier and pipeline
|
||||
name combination on a subsequent call to CreatePipeline.
|
||||
CreatePipeline ensures that if a pipeline already exists with the
|
||||
same name and unique identifier, a new pipeline will not be
|
||||
created. Instead, you'll receive the pipeline identifier from the
|
||||
previous attempt. The uniqueness of the name and unique identifier
|
||||
combination is scoped to the AWS account or IAM user credentials.
|
||||
|
||||
:type description: string
|
||||
:param description: The description of the new pipeline.
|
||||
|
||||
"""
|
||||
params = {'name': name, 'uniqueId': unique_id, }
|
||||
if description is not None:
|
||||
params['description'] = description
|
||||
return self.make_request(action='CreatePipeline',
|
||||
body=json.dumps(params))
|
||||
|
||||
def delete_pipeline(self, pipeline_id):
|
||||
"""
|
||||
Permanently deletes a pipeline, its pipeline definition and
|
||||
its run history. You cannot query or restore a deleted
|
||||
pipeline. AWS Data Pipeline will attempt to cancel instances
|
||||
associated with the pipeline that are currently being
|
||||
processed by task runners. Deleting a pipeline cannot be
|
||||
undone.
|
||||
|
||||
To temporarily pause a pipeline instead of deleting it, call
|
||||
SetStatus with the status set to Pause on individual
|
||||
components. Components that are paused by SetStatus can be
|
||||
resumed.
|
||||
|
||||
:type pipeline_id: string
|
||||
:param pipeline_id: The identifier of the pipeline to be deleted.
|
||||
|
||||
"""
|
||||
params = {'pipelineId': pipeline_id, }
|
||||
return self.make_request(action='DeletePipeline',
|
||||
body=json.dumps(params))
|
||||
|
||||
def describe_objects(self, object_ids, pipeline_id, marker=None,
|
||||
evaluate_expressions=None):
|
||||
"""
|
||||
Returns the object definitions for a set of objects associated
|
||||
with the pipeline. Object definitions are composed of a set of
|
||||
fields that define the properties of the object.
|
||||
|
||||
:type pipeline_id: string
|
||||
:param pipeline_id: Identifier of the pipeline that contains the object
|
||||
definitions.
|
||||
|
||||
:type object_ids: list
|
||||
:param object_ids: Identifiers of the pipeline objects that contain the
|
||||
definitions to be described. You can pass as many as 25 identifiers
|
||||
in a single call to DescribeObjects.
|
||||
|
||||
:type evaluate_expressions: boolean
|
||||
:param evaluate_expressions: Indicates whether any expressions in the
|
||||
object should be evaluated when the object descriptions are
|
||||
returned.
|
||||
|
||||
:type marker: string
|
||||
:param marker: The starting point for the results to be returned. The
|
||||
first time you call DescribeObjects, this value should be empty. As
|
||||
long as the action returns `HasMoreResults` as `True`, you can call
|
||||
DescribeObjects again and pass the marker value from the response
|
||||
to retrieve the next set of results.
|
||||
|
||||
"""
|
||||
params = {
|
||||
'pipelineId': pipeline_id,
|
||||
'objectIds': object_ids,
|
||||
}
|
||||
if evaluate_expressions is not None:
|
||||
params['evaluateExpressions'] = evaluate_expressions
|
||||
if marker is not None:
|
||||
params['marker'] = marker
|
||||
return self.make_request(action='DescribeObjects',
|
||||
body=json.dumps(params))
|
||||
|
||||
def describe_pipelines(self, pipeline_ids):
|
||||
"""
|
||||
Retrieve metadata about one or more pipelines. The information
|
||||
retrieved includes the name of the pipeline, the pipeline
|
||||
identifier, its current state, and the user account that owns
|
||||
the pipeline. Using account credentials, you can retrieve
|
||||
metadata about pipelines that you or your IAM users have
|
||||
created. If you are using an IAM user account, you can
|
||||
retrieve metadata about only those pipelines you have read
|
||||
permission for.
|
||||
|
||||
To retrieve the full pipeline definition instead of metadata
|
||||
about the pipeline, call the GetPipelineDefinition action.
|
||||
|
||||
:type pipeline_ids: list
|
||||
:param pipeline_ids: Identifiers of the pipelines to describe. You can
|
||||
pass as many as 25 identifiers in a single call to
|
||||
DescribePipelines. You can obtain pipeline identifiers by calling
|
||||
ListPipelines.
|
||||
|
||||
"""
|
||||
params = {'pipelineIds': pipeline_ids, }
|
||||
return self.make_request(action='DescribePipelines',
|
||||
body=json.dumps(params))
|
||||
|
||||
def evaluate_expression(self, pipeline_id, expression, object_id):
|
||||
"""
|
||||
Evaluates a string in the context of a specified object. A
|
||||
task runner can use this action to evaluate SQL queries stored
|
||||
in Amazon S3.
|
||||
|
||||
:type pipeline_id: string
|
||||
:param pipeline_id: The identifier of the pipeline.
|
||||
|
||||
:type object_id: string
|
||||
:param object_id: The identifier of the object.
|
||||
|
||||
:type expression: string
|
||||
:param expression: The expression to evaluate.
|
||||
|
||||
"""
|
||||
params = {
|
||||
'pipelineId': pipeline_id,
|
||||
'objectId': object_id,
|
||||
'expression': expression,
|
||||
}
|
||||
return self.make_request(action='EvaluateExpression',
|
||||
body=json.dumps(params))
|
||||
|
||||
def get_pipeline_definition(self, pipeline_id, version=None):
|
||||
"""
|
||||
Returns the definition of the specified pipeline. You can call
|
||||
GetPipelineDefinition to retrieve the pipeline definition you
|
||||
provided using PutPipelineDefinition.
|
||||
|
||||
:type pipeline_id: string
|
||||
:param pipeline_id: The identifier of the pipeline.
|
||||
|
||||
:type version: string
|
||||
:param version: The version of the pipeline definition to retrieve.
|
||||
This parameter accepts the values `latest` (default) and `active`.
|
||||
Where `latest` indicates the last definition saved to the pipeline
|
||||
and `active` indicates the last definition of the pipeline that was
|
||||
activated.
|
||||
|
||||
"""
|
||||
params = {'pipelineId': pipeline_id, }
|
||||
if version is not None:
|
||||
params['version'] = version
|
||||
return self.make_request(action='GetPipelineDefinition',
|
||||
body=json.dumps(params))
|
||||
|
||||
def list_pipelines(self, marker=None):
|
||||
"""
|
||||
Returns a list of pipeline identifiers for all active
|
||||
pipelines. Identifiers are returned only for pipelines you
|
||||
have permission to access.
|
||||
|
||||
:type marker: string
|
||||
:param marker: The starting point for the results to be returned. The
|
||||
first time you call ListPipelines, this value should be empty. As
|
||||
long as the action returns `HasMoreResults` as `True`, you can call
|
||||
ListPipelines again and pass the marker value from the response to
|
||||
retrieve the next set of results.
|
||||
|
||||
"""
|
||||
params = {}
|
||||
if marker is not None:
|
||||
params['marker'] = marker
|
||||
return self.make_request(action='ListPipelines',
|
||||
body=json.dumps(params))
|
||||
|
||||
def poll_for_task(self, worker_group, hostname=None,
|
||||
instance_identity=None):
|
||||
"""
|
||||
Task runners call this action to receive a task to perform
|
||||
from AWS Data Pipeline. The task runner specifies which tasks
|
||||
it can perform by setting a value for the workerGroup
|
||||
parameter of the PollForTask call. The task returned by
|
||||
PollForTask may come from any of the pipelines that match the
|
||||
workerGroup value passed in by the task runner and that was
|
||||
launched using the IAM user credentials specified by the task
|
||||
runner.
|
||||
|
||||
If tasks are ready in the work queue, PollForTask returns a
|
||||
response immediately. If no tasks are available in the queue,
|
||||
PollForTask uses long-polling and holds on to a poll
|
||||
connection for up to a 90 seconds during which time the first
|
||||
newly scheduled task is handed to the task runner. To
|
||||
accomodate this, set the socket timeout in your task runner to
|
||||
90 seconds. The task runner should not call PollForTask again
|
||||
on the same `workerGroup` until it receives a response, and
|
||||
this may take up to 90 seconds.
|
||||
|
||||
:type worker_group: string
|
||||
:param worker_group: Indicates the type of task the task runner is
|
||||
configured to accept and process. The worker group is set as a
|
||||
field on objects in the pipeline when they are created. You can
|
||||
only specify a single value for `workerGroup` in the call to
|
||||
PollForTask. There are no wildcard values permitted in
|
||||
`workerGroup`, the string must be an exact, case-sensitive, match.
|
||||
|
||||
:type hostname: string
|
||||
:param hostname: The public DNS name of the calling task runner.
|
||||
|
||||
:type instance_identity: dict
|
||||
:param instance_identity: Identity information for the Amazon EC2
|
||||
instance that is hosting the task runner. You can get this value by
|
||||
calling the URI, `http://169.254.169.254/latest/meta-data/instance-
|
||||
id`, from the EC2 instance. For more information, go to `Instance
|
||||
Metadata`_ in the Amazon Elastic Compute Cloud User Guide. Passing
|
||||
in this value proves that your task runner is running on an EC2
|
||||
instance, and ensures the proper AWS Data Pipeline service charges
|
||||
are applied to your pipeline.
|
||||
|
||||
"""
|
||||
params = {'workerGroup': worker_group, }
|
||||
if hostname is not None:
|
||||
params['hostname'] = hostname
|
||||
if instance_identity is not None:
|
||||
params['instanceIdentity'] = instance_identity
|
||||
return self.make_request(action='PollForTask',
|
||||
body=json.dumps(params))
|
||||
|
||||
def put_pipeline_definition(self, pipeline_objects, pipeline_id):
|
||||
"""
|
||||
Adds tasks, schedules, and preconditions that control the
|
||||
behavior of the pipeline. You can use PutPipelineDefinition to
|
||||
populate a new pipeline or to update an existing pipeline that
|
||||
has not yet been activated.
|
||||
|
||||
PutPipelineDefinition also validates the configuration as it
|
||||
adds it to the pipeline. Changes to the pipeline are saved
|
||||
unless one of the following three validation errors exists in
|
||||
the pipeline.
|
||||
|
||||
#. An object is missing a name or identifier field.
|
||||
#. A string or reference field is empty.
|
||||
#. The number of objects in the pipeline exceeds the maximum
|
||||
allowed objects.
|
||||
|
||||
|
||||
|
||||
Pipeline object definitions are passed to the
|
||||
PutPipelineDefinition action and returned by the
|
||||
GetPipelineDefinition action.
|
||||
|
||||
:type pipeline_id: string
|
||||
:param pipeline_id: The identifier of the pipeline to be configured.
|
||||
|
||||
:type pipeline_objects: list
|
||||
:param pipeline_objects: The objects that define the pipeline. These
|
||||
will overwrite the existing pipeline definition.
|
||||
|
||||
"""
|
||||
params = {
|
||||
'pipelineId': pipeline_id,
|
||||
'pipelineObjects': pipeline_objects,
|
||||
}
|
||||
return self.make_request(action='PutPipelineDefinition',
|
||||
body=json.dumps(params))
|
||||
|
||||
def query_objects(self, pipeline_id, sphere, marker=None, query=None,
|
||||
limit=None):
|
||||
"""
|
||||
Queries a pipeline for the names of objects that match a
|
||||
specified set of conditions.
|
||||
|
||||
The objects returned by QueryObjects are paginated and then
|
||||
filtered by the value you set for query. This means the action
|
||||
may return an empty result set with a value set for marker. If
|
||||
`HasMoreResults` is set to `True`, you should continue to call
|
||||
QueryObjects, passing in the returned value for marker, until
|
||||
`HasMoreResults` returns `False`.
|
||||
|
||||
:type pipeline_id: string
|
||||
:param pipeline_id: Identifier of the pipeline to be queried for object
|
||||
names.
|
||||
|
||||
:type query: dict
|
||||
:param query: Query that defines the objects to be returned. The Query
|
||||
object can contain a maximum of ten selectors. The conditions in
|
||||
the query are limited to top-level String fields in the object.
|
||||
These filters can be applied to components, instances, and
|
||||
attempts.
|
||||
|
||||
:type sphere: string
|
||||
:param sphere: Specifies whether the query applies to components or
|
||||
instances. Allowable values: `COMPONENT`, `INSTANCE`, `ATTEMPT`.
|
||||
|
||||
:type marker: string
|
||||
:param marker: The starting point for the results to be returned. The
|
||||
first time you call QueryObjects, this value should be empty. As
|
||||
long as the action returns `HasMoreResults` as `True`, you can call
|
||||
QueryObjects again and pass the marker value from the response to
|
||||
retrieve the next set of results.
|
||||
|
||||
:type limit: integer
|
||||
:param limit: Specifies the maximum number of object names that
|
||||
QueryObjects will return in a single call. The default value is
|
||||
100.
|
||||
|
||||
"""
|
||||
params = {'pipelineId': pipeline_id, 'sphere': sphere, }
|
||||
if query is not None:
|
||||
params['query'] = query
|
||||
if marker is not None:
|
||||
params['marker'] = marker
|
||||
if limit is not None:
|
||||
params['limit'] = limit
|
||||
return self.make_request(action='QueryObjects',
|
||||
body=json.dumps(params))
|
||||
|
||||
def report_task_progress(self, task_id):
|
||||
"""
|
||||
Updates the AWS Data Pipeline service on the progress of the
|
||||
calling task runner. When the task runner is assigned a task,
|
||||
it should call ReportTaskProgress to acknowledge that it has
|
||||
the task within 2 minutes. If the web service does not recieve
|
||||
this acknowledgement within the 2 minute window, it will
|
||||
assign the task in a subsequent PollForTask call. After this
|
||||
initial acknowledgement, the task runner only needs to report
|
||||
progress every 15 minutes to maintain its ownership of the
|
||||
task. You can change this reporting time from 15 minutes by
|
||||
specifying a `reportProgressTimeout` field in your pipeline.
|
||||
If a task runner does not report its status after 5 minutes,
|
||||
AWS Data Pipeline will assume that the task runner is unable
|
||||
to process the task and will reassign the task in a subsequent
|
||||
response to PollForTask. task runners should call
|
||||
ReportTaskProgress every 60 seconds.
|
||||
|
||||
:type task_id: string
|
||||
:param task_id: Identifier of the task assigned to the task runner.
|
||||
This value is provided in the TaskObject that the service returns
|
||||
with the response for the PollForTask action.
|
||||
|
||||
"""
|
||||
params = {'taskId': task_id, }
|
||||
return self.make_request(action='ReportTaskProgress',
|
||||
body=json.dumps(params))
|
||||
|
||||
def report_task_runner_heartbeat(self, taskrunner_id, worker_group=None,
|
||||
hostname=None):
|
||||
"""
|
||||
Task runners call ReportTaskRunnerHeartbeat every 15 minutes
|
||||
to indicate that they are operational. In the case of AWS Data
|
||||
Pipeline Task Runner launched on a resource managed by AWS
|
||||
Data Pipeline, the web service can use this call to detect
|
||||
when the task runner application has failed and restart a new
|
||||
instance.
|
||||
|
||||
:type taskrunner_id: string
|
||||
:param taskrunner_id: The identifier of the task runner. This value
|
||||
should be unique across your AWS account. In the case of AWS Data
|
||||
Pipeline Task Runner launched on a resource managed by AWS Data
|
||||
Pipeline, the web service provides a unique identifier when it
|
||||
launches the application. If you have written a custom task runner,
|
||||
you should assign a unique identifier for the task runner.
|
||||
|
||||
:type worker_group: string
|
||||
:param worker_group: Indicates the type of task the task runner is
|
||||
configured to accept and process. The worker group is set as a
|
||||
field on objects in the pipeline when they are created. You can
|
||||
only specify a single value for `workerGroup` in the call to
|
||||
ReportTaskRunnerHeartbeat. There are no wildcard values permitted
|
||||
in `workerGroup`, the string must be an exact, case-sensitive,
|
||||
match.
|
||||
|
||||
:type hostname: string
|
||||
:param hostname: The public DNS name of the calling task runner.
|
||||
|
||||
"""
|
||||
params = {'taskrunnerId': taskrunner_id, }
|
||||
if worker_group is not None:
|
||||
params['workerGroup'] = worker_group
|
||||
if hostname is not None:
|
||||
params['hostname'] = hostname
|
||||
return self.make_request(action='ReportTaskRunnerHeartbeat',
|
||||
body=json.dumps(params))
|
||||
|
||||
def set_status(self, object_ids, status, pipeline_id):
|
||||
"""
|
||||
Requests that the status of an array of physical or logical
|
||||
pipeline objects be updated in the pipeline. This update may
|
||||
not occur immediately, but is eventually consistent. The
|
||||
status that can be set depends on the type of object.
|
||||
|
||||
:type pipeline_id: string
|
||||
:param pipeline_id: Identifies the pipeline that contains the objects.
|
||||
|
||||
:type object_ids: list
|
||||
:param object_ids: Identifies an array of objects. The corresponding
|
||||
objects can be either physical or components, but not a mix of both
|
||||
types.
|
||||
|
||||
:type status: string
|
||||
:param status: Specifies the status to be set on all the objects in
|
||||
`objectIds`. For components, this can be either `PAUSE` or
|
||||
`RESUME`. For instances, this can be either `CANCEL`, `RERUN`, or
|
||||
`MARK_FINISHED`.
|
||||
|
||||
"""
|
||||
params = {
|
||||
'pipelineId': pipeline_id,
|
||||
'objectIds': object_ids,
|
||||
'status': status,
|
||||
}
|
||||
return self.make_request(action='SetStatus',
|
||||
body=json.dumps(params))
|
||||
|
||||
def set_task_status(self, task_id, task_status, error_id=None,
|
||||
error_message=None, error_stack_trace=None):
|
||||
"""
|
||||
Notifies AWS Data Pipeline that a task is completed and
|
||||
provides information about the final status. The task runner
|
||||
calls this action regardless of whether the task was
|
||||
sucessful. The task runner does not need to call SetTaskStatus
|
||||
for tasks that are canceled by the web service during a call
|
||||
to ReportTaskProgress.
|
||||
|
||||
:type task_id: string
|
||||
:param task_id: Identifies the task assigned to the task runner. This
|
||||
value is set in the TaskObject that is returned by the PollForTask
|
||||
action.
|
||||
|
||||
:type task_status: string
|
||||
:param task_status: If `FINISHED`, the task successfully completed. If
|
||||
`FAILED` the task ended unsuccessfully. The `FALSE` value is used
|
||||
by preconditions.
|
||||
|
||||
:type error_id: string
|
||||
:param error_id: If an error occurred during the task, this value
|
||||
specifies an id value that represents the error. This value is set
|
||||
on the physical attempt object. It is used to display error
|
||||
information to the user. It should not start with string "Service_"
|
||||
which is reserved by the system.
|
||||
|
||||
:type error_message: string
|
||||
:param error_message: If an error occurred during the task, this value
|
||||
specifies a text description of the error. This value is set on the
|
||||
physical attempt object. It is used to display error information to
|
||||
the user. The web service does not parse this value.
|
||||
|
||||
:type error_stack_trace: string
|
||||
:param error_stack_trace: If an error occurred during the task, this
|
||||
value specifies the stack trace associated with the error. This
|
||||
value is set on the physical attempt object. It is used to display
|
||||
error information to the user. The web service does not parse this
|
||||
value.
|
||||
|
||||
"""
|
||||
params = {'taskId': task_id, 'taskStatus': task_status, }
|
||||
if error_id is not None:
|
||||
params['errorId'] = error_id
|
||||
if error_message is not None:
|
||||
params['errorMessage'] = error_message
|
||||
if error_stack_trace is not None:
|
||||
params['errorStackTrace'] = error_stack_trace
|
||||
return self.make_request(action='SetTaskStatus',
|
||||
body=json.dumps(params))
|
||||
|
||||
def validate_pipeline_definition(self, pipeline_objects, pipeline_id):
|
||||
"""
|
||||
Tests the pipeline definition with a set of validation checks
|
||||
to ensure that it is well formed and can run without error.
|
||||
|
||||
:type pipeline_id: string
|
||||
:param pipeline_id: Identifies the pipeline whose definition is to be
|
||||
validated.
|
||||
|
||||
:type pipeline_objects: list
|
||||
:param pipeline_objects: A list of objects that define the pipeline
|
||||
changes to validate against the pipeline.
|
||||
|
||||
"""
|
||||
params = {
|
||||
'pipelineId': pipeline_id,
|
||||
'pipelineObjects': pipeline_objects,
|
||||
}
|
||||
return self.make_request(action='ValidatePipelineDefinition',
|
||||
body=json.dumps(params))
|
||||
|
||||
def make_request(self, action, body):
|
||||
headers = {
|
||||
'X-Amz-Target': '%s.%s' % (self.TargetPrefix, action),
|
||||
'Host': self.region.endpoint,
|
||||
'Content-Type': 'application/x-amz-json-1.1',
|
||||
'Content-Length': str(len(body)),
|
||||
}
|
||||
http_request = self.build_base_http_request(
|
||||
method='POST', path='/', auth_path='/', params={},
|
||||
headers=headers, data=body)
|
||||
response = self._mexe(http_request, sender=None,
|
||||
override_num_retries=10)
|
||||
response_body = response.read()
|
||||
boto.log.debug(response_body)
|
||||
if response.status == 200:
|
||||
if response_body:
|
||||
return json.loads(response_body)
|
||||
else:
|
||||
json_body = json.loads(response_body)
|
||||
fault_name = json_body.get('__type', None)
|
||||
exception_class = self._faults.get(fault_name, self.ResponseError)
|
||||
raise exception_class(response.status, response.reason,
|
||||
body=json_body)
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
# Copyright (c) 2011 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
from boto.regioninfo import RegionInfo
|
||||
|
||||
|
||||
def regions():
|
||||
"""
|
||||
Get all available regions for the Amazon DynamoDB service.
|
||||
|
||||
:rtype: list
|
||||
:return: A list of :class:`boto.regioninfo.RegionInfo`
|
||||
"""
|
||||
import boto.dynamodb.layer2
|
||||
return [RegionInfo(name='us-east-1',
|
||||
endpoint='dynamodb.us-east-1.amazonaws.com',
|
||||
connection_cls=boto.dynamodb.layer2.Layer2),
|
||||
RegionInfo(name='us-gov-west-1',
|
||||
endpoint='dynamodb.us-gov-west-1.amazonaws.com',
|
||||
connection_cls=boto.dynamodb.layer2.Layer2),
|
||||
RegionInfo(name='us-west-1',
|
||||
endpoint='dynamodb.us-west-1.amazonaws.com',
|
||||
connection_cls=boto.dynamodb.layer2.Layer2),
|
||||
RegionInfo(name='us-west-2',
|
||||
endpoint='dynamodb.us-west-2.amazonaws.com',
|
||||
connection_cls=boto.dynamodb.layer2.Layer2),
|
||||
RegionInfo(name='ap-northeast-1',
|
||||
endpoint='dynamodb.ap-northeast-1.amazonaws.com',
|
||||
connection_cls=boto.dynamodb.layer2.Layer2),
|
||||
RegionInfo(name='ap-southeast-1',
|
||||
endpoint='dynamodb.ap-southeast-1.amazonaws.com',
|
||||
connection_cls=boto.dynamodb.layer2.Layer2),
|
||||
RegionInfo(name='ap-southeast-2',
|
||||
endpoint='dynamodb.ap-southeast-2.amazonaws.com',
|
||||
connection_cls=boto.dynamodb.layer2.Layer2),
|
||||
RegionInfo(name='eu-west-1',
|
||||
endpoint='dynamodb.eu-west-1.amazonaws.com',
|
||||
connection_cls=boto.dynamodb.layer2.Layer2),
|
||||
RegionInfo(name='sa-east-1',
|
||||
endpoint='dynamodb.sa-east-1.amazonaws.com',
|
||||
connection_cls=boto.dynamodb.layer2.Layer2),
|
||||
]
|
||||
|
||||
|
||||
def connect_to_region(region_name, **kw_params):
|
||||
for region in regions():
|
||||
if region.name == region_name:
|
||||
return region.connect(**kw_params)
|
||||
return None
|
|
@ -1,262 +0,0 @@
|
|||
# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
|
||||
class Batch(object):
|
||||
"""
|
||||
Used to construct a BatchGet request.
|
||||
|
||||
:ivar table: The Table object from which the item is retrieved.
|
||||
|
||||
:ivar keys: A list of scalar or tuple values. Each element in the
|
||||
list represents one Item to retrieve. If the schema for the
|
||||
table has both a HashKey and a RangeKey, each element in the
|
||||
list should be a tuple consisting of (hash_key, range_key). If
|
||||
the schema for the table contains only a HashKey, each element
|
||||
in the list should be a scalar value of the appropriate type
|
||||
for the table schema. NOTE: The maximum number of items that
|
||||
can be retrieved for a single operation is 100. Also, the
|
||||
number of items retrieved is constrained by a 1 MB size limit.
|
||||
|
||||
:ivar attributes_to_get: A list of attribute names.
|
||||
If supplied, only the specified attribute names will
|
||||
be returned. Otherwise, all attributes will be returned.
|
||||
|
||||
:ivar consistent_read: Specify whether or not to use a
|
||||
consistent read. Defaults to False.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, table, keys, attributes_to_get=None,
|
||||
consistent_read=False):
|
||||
self.table = table
|
||||
self.keys = keys
|
||||
self.attributes_to_get = attributes_to_get
|
||||
self.consistent_read = consistent_read
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Convert the Batch object into the format required for Layer1.
|
||||
"""
|
||||
batch_dict = {}
|
||||
key_list = []
|
||||
for key in self.keys:
|
||||
if isinstance(key, tuple):
|
||||
hash_key, range_key = key
|
||||
else:
|
||||
hash_key = key
|
||||
range_key = None
|
||||
k = self.table.layer2.build_key_from_values(self.table.schema,
|
||||
hash_key, range_key)
|
||||
key_list.append(k)
|
||||
batch_dict['Keys'] = key_list
|
||||
if self.attributes_to_get:
|
||||
batch_dict['AttributesToGet'] = self.attributes_to_get
|
||||
if self.consistent_read:
|
||||
batch_dict['ConsistentRead'] = True
|
||||
else:
|
||||
batch_dict['ConsistentRead'] = False
|
||||
return batch_dict
|
||||
|
||||
|
||||
class BatchWrite(object):
|
||||
"""
|
||||
Used to construct a BatchWrite request. Each BatchWrite object
|
||||
represents a collection of PutItem and DeleteItem requests for
|
||||
a single Table.
|
||||
|
||||
:ivar table: The Table object from which the item is retrieved.
|
||||
|
||||
:ivar puts: A list of :class:`boto.dynamodb.item.Item` objects
|
||||
that you want to write to DynamoDB.
|
||||
|
||||
:ivar deletes: A list of scalar or tuple values. Each element in the
|
||||
list represents one Item to delete. If the schema for the
|
||||
table has both a HashKey and a RangeKey, each element in the
|
||||
list should be a tuple consisting of (hash_key, range_key). If
|
||||
the schema for the table contains only a HashKey, each element
|
||||
in the list should be a scalar value of the appropriate type
|
||||
for the table schema.
|
||||
"""
|
||||
|
||||
def __init__(self, table, puts=None, deletes=None):
|
||||
self.table = table
|
||||
self.puts = puts or []
|
||||
self.deletes = deletes or []
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Convert the Batch object into the format required for Layer1.
|
||||
"""
|
||||
op_list = []
|
||||
for item in self.puts:
|
||||
d = {'Item': self.table.layer2.dynamize_item(item)}
|
||||
d = {'PutRequest': d}
|
||||
op_list.append(d)
|
||||
for key in self.deletes:
|
||||
if isinstance(key, tuple):
|
||||
hash_key, range_key = key
|
||||
else:
|
||||
hash_key = key
|
||||
range_key = None
|
||||
k = self.table.layer2.build_key_from_values(self.table.schema,
|
||||
hash_key, range_key)
|
||||
d = {'Key': k}
|
||||
op_list.append({'DeleteRequest': d})
|
||||
return (self.table.name, op_list)
|
||||
|
||||
|
||||
class BatchList(list):
|
||||
"""
|
||||
A subclass of a list object that contains a collection of
|
||||
:class:`boto.dynamodb.batch.Batch` objects.
|
||||
"""
|
||||
|
||||
def __init__(self, layer2):
|
||||
list.__init__(self)
|
||||
self.unprocessed = None
|
||||
self.layer2 = layer2
|
||||
|
||||
def add_batch(self, table, keys, attributes_to_get=None,
|
||||
consistent_read=False):
|
||||
"""
|
||||
Add a Batch to this BatchList.
|
||||
|
||||
:type table: :class:`boto.dynamodb.table.Table`
|
||||
:param table: The Table object in which the items are contained.
|
||||
|
||||
:type keys: list
|
||||
:param keys: A list of scalar or tuple values. Each element in the
|
||||
list represents one Item to retrieve. If the schema for the
|
||||
table has both a HashKey and a RangeKey, each element in the
|
||||
list should be a tuple consisting of (hash_key, range_key). If
|
||||
the schema for the table contains only a HashKey, each element
|
||||
in the list should be a scalar value of the appropriate type
|
||||
for the table schema. NOTE: The maximum number of items that
|
||||
can be retrieved for a single operation is 100. Also, the
|
||||
number of items retrieved is constrained by a 1 MB size limit.
|
||||
|
||||
:type attributes_to_get: list
|
||||
:param attributes_to_get: A list of attribute names.
|
||||
If supplied, only the specified attribute names will
|
||||
be returned. Otherwise, all attributes will be returned.
|
||||
"""
|
||||
self.append(Batch(table, keys, attributes_to_get, consistent_read))
|
||||
|
||||
def resubmit(self):
|
||||
"""
|
||||
Resubmit the batch to get the next result set. The request object is
|
||||
rebuild from scratch meaning that all batch added between ``submit``
|
||||
and ``resubmit`` will be lost.
|
||||
|
||||
Note: This method is experimental and subject to changes in future releases
|
||||
"""
|
||||
del self[:]
|
||||
|
||||
if not self.unprocessed:
|
||||
return None
|
||||
|
||||
for table_name, table_req in self.unprocessed.iteritems():
|
||||
table_keys = table_req['Keys']
|
||||
table = self.layer2.get_table(table_name)
|
||||
|
||||
keys = []
|
||||
for key in table_keys:
|
||||
h = key['HashKeyElement']
|
||||
r = None
|
||||
if 'RangeKeyElement' in key:
|
||||
r = key['RangeKeyElement']
|
||||
keys.append((h, r))
|
||||
|
||||
attributes_to_get = None
|
||||
if 'AttributesToGet' in table_req:
|
||||
attributes_to_get = table_req['AttributesToGet']
|
||||
|
||||
self.add_batch(table, keys, attributes_to_get=attributes_to_get)
|
||||
|
||||
return self.submit()
|
||||
|
||||
|
||||
def submit(self):
|
||||
res = self.layer2.batch_get_item(self)
|
||||
if 'UnprocessedKeys' in res:
|
||||
self.unprocessed = res['UnprocessedKeys']
|
||||
return res
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Convert a BatchList object into format required for Layer1.
|
||||
"""
|
||||
d = {}
|
||||
for batch in self:
|
||||
b = batch.to_dict()
|
||||
if b['Keys']:
|
||||
d[batch.table.name] = b
|
||||
return d
|
||||
|
||||
|
||||
class BatchWriteList(list):
|
||||
"""
|
||||
A subclass of a list object that contains a collection of
|
||||
:class:`boto.dynamodb.batch.BatchWrite` objects.
|
||||
"""
|
||||
|
||||
def __init__(self, layer2):
|
||||
list.__init__(self)
|
||||
self.layer2 = layer2
|
||||
|
||||
def add_batch(self, table, puts=None, deletes=None):
|
||||
"""
|
||||
Add a BatchWrite to this BatchWriteList.
|
||||
|
||||
:type table: :class:`boto.dynamodb.table.Table`
|
||||
:param table: The Table object in which the items are contained.
|
||||
|
||||
:type puts: list of :class:`boto.dynamodb.item.Item` objects
|
||||
:param puts: A list of items that you want to write to DynamoDB.
|
||||
|
||||
:type deletes: A list
|
||||
:param deletes: A list of scalar or tuple values. Each element
|
||||
in the list represents one Item to delete. If the schema
|
||||
for the table has both a HashKey and a RangeKey, each
|
||||
element in the list should be a tuple consisting of
|
||||
(hash_key, range_key). If the schema for the table
|
||||
contains only a HashKey, each element in the list should
|
||||
be a scalar value of the appropriate type for the table
|
||||
schema.
|
||||
"""
|
||||
self.append(BatchWrite(table, puts, deletes))
|
||||
|
||||
def submit(self):
|
||||
return self.layer2.batch_write_item(self)
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Convert a BatchWriteList object into format required for Layer1.
|
||||
"""
|
||||
d = {}
|
||||
for batch in self:
|
||||
table_name, batch_dict = batch.to_dict()
|
||||
d[table_name] = batch_dict
|
||||
return d
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
from boto.dynamodb.types import dynamize_value
|
||||
|
||||
|
||||
class Condition(object):
|
||||
"""
|
||||
Base class for conditions. Doesn't do a darn thing but allows
|
||||
is to test if something is a Condition instance or not.
|
||||
"""
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Condition):
|
||||
return self.to_dict() == other.to_dict()
|
||||
|
||||
class ConditionNoArgs(Condition):
|
||||
"""
|
||||
Abstract class for Conditions that require no arguments, such
|
||||
as NULL or NOT_NULL.
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return '%s' % self.__class__.__name__
|
||||
|
||||
def to_dict(self):
|
||||
return {'ComparisonOperator': self.__class__.__name__}
|
||||
|
||||
|
||||
class ConditionOneArg(Condition):
|
||||
"""
|
||||
Abstract class for Conditions that require a single argument
|
||||
such as EQ or NE.
|
||||
"""
|
||||
|
||||
def __init__(self, v1):
|
||||
self.v1 = v1
|
||||
|
||||
def __repr__(self):
|
||||
return '%s:%s' % (self.__class__.__name__, self.v1)
|
||||
|
||||
def to_dict(self):
|
||||
return {'AttributeValueList': [dynamize_value(self.v1)],
|
||||
'ComparisonOperator': self.__class__.__name__}
|
||||
|
||||
|
||||
class ConditionTwoArgs(Condition):
|
||||
"""
|
||||
Abstract class for Conditions that require two arguments.
|
||||
The only example of this currently is BETWEEN.
|
||||
"""
|
||||
|
||||
def __init__(self, v1, v2):
|
||||
self.v1 = v1
|
||||
self.v2 = v2
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s, %s)' % (self.__class__.__name__, self.v1, self.v2)
|
||||
|
||||
def to_dict(self):
|
||||
values = (self.v1, self.v2)
|
||||
return {'AttributeValueList': [dynamize_value(v) for v in values],
|
||||
'ComparisonOperator': self.__class__.__name__}
|
||||
|
||||
|
||||
class ConditionSeveralArgs(Condition):
|
||||
"""
|
||||
Abstract class for conditions that require several argument (ex: IN).
|
||||
"""
|
||||
|
||||
def __init__(self, values):
|
||||
self.values = values
|
||||
|
||||
def __repr__(self):
|
||||
return '{0}({1})'.format(self.__class__.__name__,
|
||||
', '.join(self.values))
|
||||
|
||||
def to_dict(self):
|
||||
return {'AttributeValueList': [dynamize_value(v) for v in self.values],
|
||||
'ComparisonOperator': self.__class__.__name__}
|
||||
|
||||
|
||||
class EQ(ConditionOneArg):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NE(ConditionOneArg):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class LE(ConditionOneArg):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class LT(ConditionOneArg):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class GE(ConditionOneArg):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class GT(ConditionOneArg):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NULL(ConditionNoArgs):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NOT_NULL(ConditionNoArgs):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class CONTAINS(ConditionOneArg):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NOT_CONTAINS(ConditionOneArg):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BEGINS_WITH(ConditionOneArg):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class IN(ConditionSeveralArgs):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BEGINS_WITH(ConditionOneArg):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BETWEEN(ConditionTwoArgs):
|
||||
|
||||
pass
|
|
@ -1,64 +0,0 @@
|
|||
"""
|
||||
Exceptions that are specific to the dynamodb module.
|
||||
"""
|
||||
from boto.exception import BotoServerError, BotoClientError
|
||||
from boto.exception import DynamoDBResponseError
|
||||
|
||||
|
||||
class DynamoDBExpiredTokenError(BotoServerError):
|
||||
"""
|
||||
Raised when a DynamoDB security token expires. This is generally boto's
|
||||
(or the user's) notice to renew their DynamoDB security tokens.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class DynamoDBKeyNotFoundError(BotoClientError):
|
||||
"""
|
||||
Raised when attempting to retrieve or interact with an item whose key
|
||||
can't be found.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class DynamoDBItemError(BotoClientError):
|
||||
"""
|
||||
Raised when invalid parameters are passed when creating a
|
||||
new Item in DynamoDB.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class DynamoDBNumberError(BotoClientError):
|
||||
"""
|
||||
Raised in the event of incompatible numeric type casting.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class DynamoDBConditionalCheckFailedError(DynamoDBResponseError):
|
||||
"""
|
||||
Raised when a ConditionalCheckFailedException response is received.
|
||||
This happens when a conditional check, expressed via the expected_value
|
||||
paramenter, fails.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class DynamoDBValidationError(DynamoDBResponseError):
|
||||
"""
|
||||
Raised when a ValidationException response is received. This happens
|
||||
when one or more required parameter values are missing, or if the item
|
||||
has exceeded the 64Kb size limit.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class DynamoDBThroughputExceededError(DynamoDBResponseError):
|
||||
"""
|
||||
Raised when the provisioned throughput has been exceeded.
|
||||
Normally, when provisioned throughput is exceeded the operation
|
||||
is retried. If the retries are exhausted then this exception
|
||||
will be raised.
|
||||
"""
|
||||
pass
|
|
@ -1,202 +0,0 @@
|
|||
# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
from boto.dynamodb.exceptions import DynamoDBItemError
|
||||
|
||||
|
||||
class Item(dict):
|
||||
"""
|
||||
An item in Amazon DynamoDB.
|
||||
|
||||
:ivar hash_key: The HashKey of this item.
|
||||
:ivar range_key: The RangeKey of this item or None if no RangeKey
|
||||
is defined.
|
||||
:ivar hash_key_name: The name of the HashKey associated with this item.
|
||||
:ivar range_key_name: The name of the RangeKey associated with this item.
|
||||
:ivar table: The Table this item belongs to.
|
||||
"""
|
||||
|
||||
def __init__(self, table, hash_key=None, range_key=None, attrs=None):
|
||||
self.table = table
|
||||
self._updates = None
|
||||
self._hash_key_name = self.table.schema.hash_key_name
|
||||
self._range_key_name = self.table.schema.range_key_name
|
||||
if attrs == None:
|
||||
attrs = {}
|
||||
if hash_key == None:
|
||||
hash_key = attrs.get(self._hash_key_name, None)
|
||||
self[self._hash_key_name] = hash_key
|
||||
if self._range_key_name:
|
||||
if range_key == None:
|
||||
range_key = attrs.get(self._range_key_name, None)
|
||||
self[self._range_key_name] = range_key
|
||||
self._updates = {}
|
||||
for key, value in attrs.items():
|
||||
if key != self._hash_key_name and key != self._range_key_name:
|
||||
self[key] = value
|
||||
self.consumed_units = 0
|
||||
|
||||
@property
|
||||
def hash_key(self):
|
||||
return self[self._hash_key_name]
|
||||
|
||||
@property
|
||||
def range_key(self):
|
||||
return self.get(self._range_key_name)
|
||||
|
||||
@property
|
||||
def hash_key_name(self):
|
||||
return self._hash_key_name
|
||||
|
||||
@property
|
||||
def range_key_name(self):
|
||||
return self._range_key_name
|
||||
|
||||
def add_attribute(self, attr_name, attr_value):
|
||||
"""
|
||||
Queue the addition of an attribute to an item in DynamoDB.
|
||||
This will eventually result in an UpdateItem request being issued
|
||||
with an update action of ADD when the save method is called.
|
||||
|
||||
:type attr_name: str
|
||||
:param attr_name: Name of the attribute you want to alter.
|
||||
|
||||
:type attr_value: int|long|float|set
|
||||
:param attr_value: Value which is to be added to the attribute.
|
||||
"""
|
||||
self._updates[attr_name] = ("ADD", attr_value)
|
||||
|
||||
def delete_attribute(self, attr_name, attr_value=None):
|
||||
"""
|
||||
Queue the deletion of an attribute from an item in DynamoDB.
|
||||
This call will result in a UpdateItem request being issued
|
||||
with update action of DELETE when the save method is called.
|
||||
|
||||
:type attr_name: str
|
||||
:param attr_name: Name of the attribute you want to alter.
|
||||
|
||||
:type attr_value: set
|
||||
:param attr_value: A set of values to be removed from the attribute.
|
||||
This parameter is optional. If None, the whole attribute is
|
||||
removed from the item.
|
||||
"""
|
||||
self._updates[attr_name] = ("DELETE", attr_value)
|
||||
|
||||
def put_attribute(self, attr_name, attr_value):
|
||||
"""
|
||||
Queue the putting of an attribute to an item in DynamoDB.
|
||||
This call will result in an UpdateItem request being issued
|
||||
with the update action of PUT when the save method is called.
|
||||
|
||||
:type attr_name: str
|
||||
:param attr_name: Name of the attribute you want to alter.
|
||||
|
||||
:type attr_value: int|long|float|str|set
|
||||
:param attr_value: New value of the attribute.
|
||||
"""
|
||||
self._updates[attr_name] = ("PUT", attr_value)
|
||||
|
||||
def save(self, expected_value=None, return_values=None):
|
||||
"""
|
||||
Commits pending updates to Amazon DynamoDB.
|
||||
|
||||
:type expected_value: dict
|
||||
:param expected_value: A dictionary of name/value pairs that
|
||||
you expect. This dictionary should have name/value pairs
|
||||
where the name is the name of the attribute and the value is
|
||||
either the value you are expecting or False if you expect
|
||||
the attribute not to exist.
|
||||
|
||||
:type return_values: str
|
||||
:param return_values: Controls the return of attribute name/value pairs
|
||||
before they were updated. Possible values are: None, 'ALL_OLD',
|
||||
'UPDATED_OLD', 'ALL_NEW' or 'UPDATED_NEW'. If 'ALL_OLD' is
|
||||
specified and the item is overwritten, the content of the old item
|
||||
is returned. If 'ALL_NEW' is specified, then all the attributes of
|
||||
the new version of the item are returned. If 'UPDATED_NEW' is
|
||||
specified, the new versions of only the updated attributes are
|
||||
returned.
|
||||
"""
|
||||
return self.table.layer2.update_item(self, expected_value,
|
||||
return_values)
|
||||
|
||||
def delete(self, expected_value=None, return_values=None):
|
||||
"""
|
||||
Delete the item from DynamoDB.
|
||||
|
||||
:type expected_value: dict
|
||||
:param expected_value: A dictionary of name/value pairs that
|
||||
you expect. This dictionary should have name/value pairs
|
||||
where the name is the name of the attribute and the value
|
||||
is either the value you are expecting or False if you expect
|
||||
the attribute not to exist.
|
||||
|
||||
:type return_values: str
|
||||
:param return_values: Controls the return of attribute
|
||||
name-value pairs before then were changed. Possible
|
||||
values are: None or 'ALL_OLD'. If 'ALL_OLD' is
|
||||
specified and the item is overwritten, the content
|
||||
of the old item is returned.
|
||||
"""
|
||||
return self.table.layer2.delete_item(self, expected_value,
|
||||
return_values)
|
||||
|
||||
def put(self, expected_value=None, return_values=None):
|
||||
"""
|
||||
Store a new item or completely replace an existing item
|
||||
in Amazon DynamoDB.
|
||||
|
||||
:type expected_value: dict
|
||||
:param expected_value: A dictionary of name/value pairs that
|
||||
you expect. This dictionary should have name/value pairs
|
||||
where the name is the name of the attribute and the value
|
||||
is either the value you are expecting or False if you expect
|
||||
the attribute not to exist.
|
||||
|
||||
:type return_values: str
|
||||
:param return_values: Controls the return of attribute
|
||||
name-value pairs before then were changed. Possible
|
||||
values are: None or 'ALL_OLD'. If 'ALL_OLD' is
|
||||
specified and the item is overwritten, the content
|
||||
of the old item is returned.
|
||||
"""
|
||||
return self.table.layer2.put_item(self, expected_value, return_values)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Overrwrite the setter to instead update the _updates
|
||||
method so this can act like a normal dict"""
|
||||
if self._updates is not None:
|
||||
self.put_attribute(key, value)
|
||||
dict.__setitem__(self, key, value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
"""Remove this key from the items"""
|
||||
if self._updates is not None:
|
||||
self.delete_attribute(key)
|
||||
dict.__delitem__(self, key)
|
||||
|
||||
# Allow this item to still be pickled
|
||||
def __getstate__(self):
|
||||
return self.__dict__
|
||||
def __setstate__(self, d):
|
||||
self.__dict__.update(d)
|
|
@ -1,575 +0,0 @@
|
|||
# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
import time
|
||||
from binascii import crc32
|
||||
|
||||
import boto
|
||||
from boto.connection import AWSAuthConnection
|
||||
from boto.exception import DynamoDBResponseError
|
||||
from boto.provider import Provider
|
||||
from boto.dynamodb import exceptions as dynamodb_exceptions
|
||||
from boto.compat import json
|
||||
|
||||
|
||||
class Layer1(AWSAuthConnection):
|
||||
"""
|
||||
This is the lowest-level interface to DynamoDB. Methods at this
|
||||
layer map directly to API requests and parameters to the methods
|
||||
are either simple, scalar values or they are the Python equivalent
|
||||
of the JSON input as defined in the DynamoDB Developer's Guide.
|
||||
All responses are direct decoding of the JSON response bodies to
|
||||
Python data structures via the json or simplejson modules.
|
||||
|
||||
:ivar throughput_exceeded_events: An integer variable that
|
||||
keeps a running total of the number of ThroughputExceeded
|
||||
responses this connection has received from Amazon DynamoDB.
|
||||
"""
|
||||
|
||||
DefaultRegionName = 'us-east-1'
|
||||
"""The default region name for DynamoDB API."""
|
||||
|
||||
ServiceName = 'DynamoDB'
|
||||
"""The name of the Service"""
|
||||
|
||||
Version = '20111205'
|
||||
"""DynamoDB API version."""
|
||||
|
||||
ThruputError = "ProvisionedThroughputExceededException"
|
||||
"""The error response returned when provisioned throughput is exceeded"""
|
||||
|
||||
SessionExpiredError = 'com.amazon.coral.service#ExpiredTokenException'
|
||||
"""The error response returned when session token has expired"""
|
||||
|
||||
ConditionalCheckFailedError = 'ConditionalCheckFailedException'
|
||||
"""The error response returned when a conditional check fails"""
|
||||
|
||||
ValidationError = 'ValidationException'
|
||||
"""The error response returned when an item is invalid in some way"""
|
||||
|
||||
ResponseError = DynamoDBResponseError
|
||||
|
||||
NumberRetries = 10
|
||||
"""The number of times an error is retried."""
|
||||
|
||||
def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
|
||||
is_secure=True, port=None, proxy=None, proxy_port=None,
|
||||
debug=0, security_token=None, region=None,
|
||||
validate_certs=True, validate_checksums=True):
|
||||
if not region:
|
||||
region_name = boto.config.get('DynamoDB', 'region',
|
||||
self.DefaultRegionName)
|
||||
for reg in boto.dynamodb.regions():
|
||||
if reg.name == region_name:
|
||||
region = reg
|
||||
break
|
||||
|
||||
self.region = region
|
||||
AWSAuthConnection.__init__(self, self.region.endpoint,
|
||||
aws_access_key_id,
|
||||
aws_secret_access_key,
|
||||
is_secure, port, proxy, proxy_port,
|
||||
debug=debug, security_token=security_token,
|
||||
validate_certs=validate_certs)
|
||||
self.throughput_exceeded_events = 0
|
||||
self._validate_checksums = boto.config.getbool(
|
||||
'DynamoDB', 'validate_checksums', validate_checksums)
|
||||
|
||||
def _get_session_token(self):
|
||||
self.provider = Provider(self._provider_type)
|
||||
self._auth_handler.update_provider(self.provider)
|
||||
|
||||
def _required_auth_capability(self):
|
||||
return ['hmac-v4']
|
||||
|
||||
def make_request(self, action, body='', object_hook=None):
|
||||
"""
|
||||
:raises: ``DynamoDBExpiredTokenError`` if the security token expires.
|
||||
"""
|
||||
headers = {'X-Amz-Target': '%s_%s.%s' % (self.ServiceName,
|
||||
self.Version, action),
|
||||
'Host': self.region.endpoint,
|
||||
'Content-Type': 'application/x-amz-json-1.0',
|
||||
'Content-Length': str(len(body))}
|
||||
http_request = self.build_base_http_request('POST', '/', '/',
|
||||
{}, headers, body, None)
|
||||
start = time.time()
|
||||
response = self._mexe(http_request, sender=None,
|
||||
override_num_retries=self.NumberRetries,
|
||||
retry_handler=self._retry_handler)
|
||||
elapsed = (time.time() - start) * 1000
|
||||
request_id = response.getheader('x-amzn-RequestId')
|
||||
boto.log.debug('RequestId: %s' % request_id)
|
||||
boto.perflog.debug('%s: id=%s time=%sms',
|
||||
headers['X-Amz-Target'], request_id, int(elapsed))
|
||||
response_body = response.read()
|
||||
boto.log.debug(response_body)
|
||||
return json.loads(response_body, object_hook=object_hook)
|
||||
|
||||
def _retry_handler(self, response, i, next_sleep):
|
||||
status = None
|
||||
if response.status == 400:
|
||||
response_body = response.read()
|
||||
boto.log.debug(response_body)
|
||||
data = json.loads(response_body)
|
||||
if self.ThruputError in data.get('__type'):
|
||||
self.throughput_exceeded_events += 1
|
||||
msg = "%s, retry attempt %s" % (self.ThruputError, i)
|
||||
next_sleep = self._exponential_time(i)
|
||||
i += 1
|
||||
status = (msg, i, next_sleep)
|
||||
if i == self.NumberRetries:
|
||||
# If this was our last retry attempt, raise
|
||||
# a specific error saying that the throughput
|
||||
# was exceeded.
|
||||
raise dynamodb_exceptions.DynamoDBThroughputExceededError(
|
||||
response.status, response.reason, data)
|
||||
elif self.SessionExpiredError in data.get('__type'):
|
||||
msg = 'Renewing Session Token'
|
||||
self._get_session_token()
|
||||
status = (msg, i + self.num_retries - 1, 0)
|
||||
elif self.ConditionalCheckFailedError in data.get('__type'):
|
||||
raise dynamodb_exceptions.DynamoDBConditionalCheckFailedError(
|
||||
response.status, response.reason, data)
|
||||
elif self.ValidationError in data.get('__type'):
|
||||
raise dynamodb_exceptions.DynamoDBValidationError(
|
||||
response.status, response.reason, data)
|
||||
else:
|
||||
raise self.ResponseError(response.status, response.reason,
|
||||
data)
|
||||
expected_crc32 = response.getheader('x-amz-crc32')
|
||||
if self._validate_checksums and expected_crc32 is not None:
|
||||
boto.log.debug('Validating crc32 checksum for body: %s',
|
||||
response.read())
|
||||
actual_crc32 = crc32(response.read()) & 0xffffffff
|
||||
expected_crc32 = int(expected_crc32)
|
||||
if actual_crc32 != expected_crc32:
|
||||
msg = ("The calculated checksum %s did not match the expected "
|
||||
"checksum %s" % (actual_crc32, expected_crc32))
|
||||
status = (msg, i + 1, self._exponential_time(i))
|
||||
return status
|
||||
|
||||
def _exponential_time(self, i):
|
||||
if i == 0:
|
||||
next_sleep = 0
|
||||
else:
|
||||
next_sleep = 0.05 * (2 ** i)
|
||||
return next_sleep
|
||||
|
||||
def list_tables(self, limit=None, start_table=None):
|
||||
"""
|
||||
Returns a dictionary of results. The dictionary contains
|
||||
a **TableNames** key whose value is a list of the table names.
|
||||
The dictionary could also contain a **LastEvaluatedTableName**
|
||||
key whose value would be the last table name returned if
|
||||
the complete list of table names was not returned. This
|
||||
value would then be passed as the ``start_table`` parameter on
|
||||
a subsequent call to this method.
|
||||
|
||||
:type limit: int
|
||||
:param limit: The maximum number of tables to return.
|
||||
|
||||
:type start_table: str
|
||||
:param start_table: The name of the table that starts the
|
||||
list. If you ran a previous list_tables and not
|
||||
all results were returned, the response dict would
|
||||
include a LastEvaluatedTableName attribute. Use
|
||||
that value here to continue the listing.
|
||||
"""
|
||||
data = {}
|
||||
if limit:
|
||||
data['Limit'] = limit
|
||||
if start_table:
|
||||
data['ExclusiveStartTableName'] = start_table
|
||||
json_input = json.dumps(data)
|
||||
return self.make_request('ListTables', json_input)
|
||||
|
||||
def describe_table(self, table_name):
|
||||
"""
|
||||
Returns information about the table including current
|
||||
state of the table, primary key schema and when the
|
||||
table was created.
|
||||
|
||||
:type table_name: str
|
||||
:param table_name: The name of the table to describe.
|
||||
"""
|
||||
data = {'TableName': table_name}
|
||||
json_input = json.dumps(data)
|
||||
return self.make_request('DescribeTable', json_input)
|
||||
|
||||
def create_table(self, table_name, schema, provisioned_throughput):
|
||||
"""
|
||||
Add a new table to your account. The table name must be unique
|
||||
among those associated with the account issuing the request.
|
||||
This request triggers an asynchronous workflow to begin creating
|
||||
the table. When the workflow is complete, the state of the
|
||||
table will be ACTIVE.
|
||||
|
||||
:type table_name: str
|
||||
:param table_name: The name of the table to create.
|
||||
|
||||
:type schema: dict
|
||||
:param schema: A Python version of the KeySchema data structure
|
||||
as defined by DynamoDB
|
||||
|
||||
:type provisioned_throughput: dict
|
||||
:param provisioned_throughput: A Python version of the
|
||||
ProvisionedThroughput data structure defined by
|
||||
DynamoDB.
|
||||
"""
|
||||
data = {'TableName': table_name,
|
||||
'KeySchema': schema,
|
||||
'ProvisionedThroughput': provisioned_throughput}
|
||||
json_input = json.dumps(data)
|
||||
response_dict = self.make_request('CreateTable', json_input)
|
||||
return response_dict
|
||||
|
||||
def update_table(self, table_name, provisioned_throughput):
|
||||
"""
|
||||
Updates the provisioned throughput for a given table.
|
||||
|
||||
:type table_name: str
|
||||
:param table_name: The name of the table to update.
|
||||
|
||||
:type provisioned_throughput: dict
|
||||
:param provisioned_throughput: A Python version of the
|
||||
ProvisionedThroughput data structure defined by
|
||||
DynamoDB.
|
||||
"""
|
||||
data = {'TableName': table_name,
|
||||
'ProvisionedThroughput': provisioned_throughput}
|
||||
json_input = json.dumps(data)
|
||||
return self.make_request('UpdateTable', json_input)
|
||||
|
||||
def delete_table(self, table_name):
|
||||
"""
|
||||
Deletes the table and all of it's data. After this request
|
||||
the table will be in the DELETING state until DynamoDB
|
||||
completes the delete operation.
|
||||
|
||||
:type table_name: str
|
||||
:param table_name: The name of the table to delete.
|
||||
"""
|
||||
data = {'TableName': table_name}
|
||||
json_input = json.dumps(data)
|
||||
return self.make_request('DeleteTable', json_input)
|
||||
|
||||
def get_item(self, table_name, key, attributes_to_get=None,
|
||||
consistent_read=False, object_hook=None):
|
||||
"""
|
||||
Return a set of attributes for an item that matches
|
||||
the supplied key.
|
||||
|
||||
:type table_name: str
|
||||
:param table_name: The name of the table containing the item.
|
||||
|
||||
:type key: dict
|
||||
:param key: A Python version of the Key data structure
|
||||
defined by DynamoDB.
|
||||
|
||||
:type attributes_to_get: list
|
||||
:param attributes_to_get: A list of attribute names.
|
||||
If supplied, only the specified attribute names will
|
||||
be returned. Otherwise, all attributes will be returned.
|
||||
|
||||
:type consistent_read: bool
|
||||
:param consistent_read: If True, a consistent read
|
||||
request is issued. Otherwise, an eventually consistent
|
||||
request is issued.
|
||||
"""
|
||||
data = {'TableName': table_name,
|
||||
'Key': key}
|
||||
if attributes_to_get:
|
||||
data['AttributesToGet'] = attributes_to_get
|
||||
if consistent_read:
|
||||
data['ConsistentRead'] = True
|
||||
json_input = json.dumps(data)
|
||||
response = self.make_request('GetItem', json_input,
|
||||
object_hook=object_hook)
|
||||
if 'Item' not in response:
|
||||
raise dynamodb_exceptions.DynamoDBKeyNotFoundError(
|
||||
"Key does not exist."
|
||||
)
|
||||
return response
|
||||
|
||||
def batch_get_item(self, request_items, object_hook=None):
|
||||
"""
|
||||
Return a set of attributes for a multiple items in
|
||||
multiple tables using their primary keys.
|
||||
|
||||
:type request_items: dict
|
||||
:param request_items: A Python version of the RequestItems
|
||||
data structure defined by DynamoDB.
|
||||
"""
|
||||
# If the list is empty, return empty response
|
||||
if not request_items:
|
||||
return {}
|
||||
data = {'RequestItems': request_items}
|
||||
json_input = json.dumps(data)
|
||||
return self.make_request('BatchGetItem', json_input,
|
||||
object_hook=object_hook)
|
||||
|
||||
def batch_write_item(self, request_items, object_hook=None):
|
||||
"""
|
||||
This operation enables you to put or delete several items
|
||||
across multiple tables in a single API call.
|
||||
|
||||
:type request_items: dict
|
||||
:param request_items: A Python version of the RequestItems
|
||||
data structure defined by DynamoDB.
|
||||
"""
|
||||
data = {'RequestItems': request_items}
|
||||
json_input = json.dumps(data)
|
||||
return self.make_request('BatchWriteItem', json_input,
|
||||
object_hook=object_hook)
|
||||
|
||||
def put_item(self, table_name, item,
|
||||
expected=None, return_values=None,
|
||||
object_hook=None):
|
||||
"""
|
||||
Create a new item or replace an old item with a new
|
||||
item (including all attributes). If an item already
|
||||
exists in the specified table with the same primary
|
||||
key, the new item will completely replace the old item.
|
||||
You can perform a conditional put by specifying an
|
||||
expected rule.
|
||||
|
||||
:type table_name: str
|
||||
:param table_name: The name of the table in which to put the item.
|
||||
|
||||
:type item: dict
|
||||
:param item: A Python version of the Item data structure
|
||||
defined by DynamoDB.
|
||||
|
||||
:type expected: dict
|
||||
:param expected: A Python version of the Expected
|
||||
data structure defined by DynamoDB.
|
||||
|
||||
:type return_values: str
|
||||
:param return_values: Controls the return of attribute
|
||||
name-value pairs before then were changed. Possible
|
||||
values are: None or 'ALL_OLD'. If 'ALL_OLD' is
|
||||
specified and the item is overwritten, the content
|
||||
of the old item is returned.
|
||||
"""
|
||||
data = {'TableName': table_name,
|
||||
'Item': item}
|
||||
if expected:
|
||||
data['Expected'] = expected
|
||||
if return_values:
|
||||
data['ReturnValues'] = return_values
|
||||
json_input = json.dumps(data)
|
||||
return self.make_request('PutItem', json_input,
|
||||
object_hook=object_hook)
|
||||
|
||||
def update_item(self, table_name, key, attribute_updates,
|
||||
expected=None, return_values=None,
|
||||
object_hook=None):
|
||||
"""
|
||||
Edits an existing item's attributes. You can perform a conditional
|
||||
update (insert a new attribute name-value pair if it doesn't exist,
|
||||
or replace an existing name-value pair if it has certain expected
|
||||
attribute values).
|
||||
|
||||
:type table_name: str
|
||||
:param table_name: The name of the table.
|
||||
|
||||
:type key: dict
|
||||
:param key: A Python version of the Key data structure
|
||||
defined by DynamoDB which identifies the item to be updated.
|
||||
|
||||
:type attribute_updates: dict
|
||||
:param attribute_updates: A Python version of the AttributeUpdates
|
||||
data structure defined by DynamoDB.
|
||||
|
||||
:type expected: dict
|
||||
:param expected: A Python version of the Expected
|
||||
data structure defined by DynamoDB.
|
||||
|
||||
:type return_values: str
|
||||
:param return_values: Controls the return of attribute
|
||||
name-value pairs before then were changed. Possible
|
||||
values are: None or 'ALL_OLD'. If 'ALL_OLD' is
|
||||
specified and the item is overwritten, the content
|
||||
of the old item is returned.
|
||||
"""
|
||||
data = {'TableName': table_name,
|
||||
'Key': key,
|
||||
'AttributeUpdates': attribute_updates}
|
||||
if expected:
|
||||
data['Expected'] = expected
|
||||
if return_values:
|
||||
data['ReturnValues'] = return_values
|
||||
json_input = json.dumps(data)
|
||||
return self.make_request('UpdateItem', json_input,
|
||||
object_hook=object_hook)
|
||||
|
||||
def delete_item(self, table_name, key,
|
||||
expected=None, return_values=None,
|
||||
object_hook=None):
|
||||
"""
|
||||
Delete an item and all of it's attributes by primary key.
|
||||
You can perform a conditional delete by specifying an
|
||||
expected rule.
|
||||
|
||||
:type table_name: str
|
||||
:param table_name: The name of the table containing the item.
|
||||
|
||||
:type key: dict
|
||||
:param key: A Python version of the Key data structure
|
||||
defined by DynamoDB.
|
||||
|
||||
:type expected: dict
|
||||
:param expected: A Python version of the Expected
|
||||
data structure defined by DynamoDB.
|
||||
|
||||
:type return_values: str
|
||||
:param return_values: Controls the return of attribute
|
||||
name-value pairs before then were changed. Possible
|
||||
values are: None or 'ALL_OLD'. If 'ALL_OLD' is
|
||||
specified and the item is overwritten, the content
|
||||
of the old item is returned.
|
||||
"""
|
||||
data = {'TableName': table_name,
|
||||
'Key': key}
|
||||
if expected:
|
||||
data['Expected'] = expected
|
||||
if return_values:
|
||||
data['ReturnValues'] = return_values
|
||||
json_input = json.dumps(data)
|
||||
return self.make_request('DeleteItem', json_input,
|
||||
object_hook=object_hook)
|
||||
|
||||
def query(self, table_name, hash_key_value, range_key_conditions=None,
|
||||
attributes_to_get=None, limit=None, consistent_read=False,
|
||||
scan_index_forward=True, exclusive_start_key=None,
|
||||
object_hook=None, count=False):
|
||||
"""
|
||||
Perform a query of DynamoDB. This version is currently punting
|
||||
and expecting you to provide a full and correct JSON body
|
||||
which is passed as is to DynamoDB.
|
||||
|
||||
:type table_name: str
|
||||
:param table_name: The name of the table to query.
|
||||
|
||||
:type hash_key_value: dict
|
||||
:param key: A DynamoDB-style HashKeyValue.
|
||||
|
||||
:type range_key_conditions: dict
|
||||
:param range_key_conditions: A Python version of the
|
||||
RangeKeyConditions data structure.
|
||||
|
||||
:type attributes_to_get: list
|
||||
:param attributes_to_get: A list of attribute names.
|
||||
If supplied, only the specified attribute names will
|
||||
be returned. Otherwise, all attributes will be returned.
|
||||
|
||||
:type limit: int
|
||||
:param limit: The maximum number of items to return.
|
||||
|
||||
:type count: bool
|
||||
:param count: If True, Amazon DynamoDB returns a total
|
||||
number of items for the Query operation, even if the
|
||||
operation has no matching items for the assigned filter.
|
||||
|
||||
:type consistent_read: bool
|
||||
:param consistent_read: If True, a consistent read
|
||||
request is issued. Otherwise, an eventually consistent
|
||||
request is issued.
|
||||
|
||||
:type scan_index_forward: bool
|
||||
:param scan_index_forward: Specified forward or backward
|
||||
traversal of the index. Default is forward (True).
|
||||
|
||||
:type exclusive_start_key: list or tuple
|
||||
:param exclusive_start_key: Primary key of the item from
|
||||
which to continue an earlier query. This would be
|
||||
provided as the LastEvaluatedKey in that query.
|
||||
"""
|
||||
data = {'TableName': table_name,
|
||||
'HashKeyValue': hash_key_value}
|
||||
if range_key_conditions:
|
||||
data['RangeKeyCondition'] = range_key_conditions
|
||||
if attributes_to_get:
|
||||
data['AttributesToGet'] = attributes_to_get
|
||||
if limit:
|
||||
data['Limit'] = limit
|
||||
if count:
|
||||
data['Count'] = True
|
||||
if consistent_read:
|
||||
data['ConsistentRead'] = True
|
||||
if scan_index_forward:
|
||||
data['ScanIndexForward'] = True
|
||||
else:
|
||||
data['ScanIndexForward'] = False
|
||||
if exclusive_start_key:
|
||||
data['ExclusiveStartKey'] = exclusive_start_key
|
||||
json_input = json.dumps(data)
|
||||
return self.make_request('Query', json_input,
|
||||
object_hook=object_hook)
|
||||
|
||||
def scan(self, table_name, scan_filter=None,
|
||||
attributes_to_get=None, limit=None,
|
||||
exclusive_start_key=None, object_hook=None, count=False):
|
||||
"""
|
||||
Perform a scan of DynamoDB. This version is currently punting
|
||||
and expecting you to provide a full and correct JSON body
|
||||
which is passed as is to DynamoDB.
|
||||
|
||||
:type table_name: str
|
||||
:param table_name: The name of the table to scan.
|
||||
|
||||
:type scan_filter: dict
|
||||
:param scan_filter: A Python version of the
|
||||
ScanFilter data structure.
|
||||
|
||||
:type attributes_to_get: list
|
||||
:param attributes_to_get: A list of attribute names.
|
||||
If supplied, only the specified attribute names will
|
||||
be returned. Otherwise, all attributes will be returned.
|
||||
|
||||
:type limit: int
|
||||
:param limit: The maximum number of items to evaluate.
|
||||
|
||||
:type count: bool
|
||||
:param count: If True, Amazon DynamoDB returns a total
|
||||
number of items for the Scan operation, even if the
|
||||
operation has no matching items for the assigned filter.
|
||||
|
||||
:type exclusive_start_key: list or tuple
|
||||
:param exclusive_start_key: Primary key of the item from
|
||||
which to continue an earlier query. This would be
|
||||
provided as the LastEvaluatedKey in that query.
|
||||
"""
|
||||
data = {'TableName': table_name}
|
||||
if scan_filter:
|
||||
data['ScanFilter'] = scan_filter
|
||||
if attributes_to_get:
|
||||
data['AttributesToGet'] = attributes_to_get
|
||||
if limit:
|
||||
data['Limit'] = limit
|
||||
if count:
|
||||
data['Count'] = True
|
||||
if exclusive_start_key:
|
||||
data['ExclusiveStartKey'] = exclusive_start_key
|
||||
json_input = json.dumps(data)
|
||||
return self.make_request('Scan', json_input, object_hook=object_hook)
|
|
@ -1,804 +0,0 @@
|
|||
# Copyright (c) 2011 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
from boto.dynamodb.layer1 import Layer1
|
||||
from boto.dynamodb.table import Table
|
||||
from boto.dynamodb.schema import Schema
|
||||
from boto.dynamodb.item import Item
|
||||
from boto.dynamodb.batch import BatchList, BatchWriteList
|
||||
from boto.dynamodb.types import get_dynamodb_type, Dynamizer, \
|
||||
LossyFloatDynamizer
|
||||
|
||||
|
||||
class TableGenerator(object):
|
||||
"""
|
||||
This is an object that wraps up the table_generator function.
|
||||
The only real reason to have this is that we want to be able
|
||||
to accumulate and return the ConsumedCapacityUnits element that
|
||||
is part of each response.
|
||||
|
||||
:ivar last_evaluated_key: A sequence representing the key(s)
|
||||
of the item last evaluated, or None if no additional
|
||||
results are available.
|
||||
|
||||
:ivar remaining: The remaining quantity of results requested.
|
||||
|
||||
:ivar table: The table to which the call was made.
|
||||
"""
|
||||
|
||||
def __init__(self, table, callable, remaining, item_class, kwargs):
|
||||
self.table = table
|
||||
self.callable = callable
|
||||
self.remaining = -1 if remaining is None else remaining
|
||||
self.item_class = item_class
|
||||
self.kwargs = kwargs
|
||||
self._consumed_units = 0.0
|
||||
self.last_evaluated_key = None
|
||||
self._count = 0
|
||||
self._scanned_count = 0
|
||||
self._response = None
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
"""
|
||||
The total number of items retrieved thus far. This value changes with
|
||||
iteration and even when issuing a call with count=True, it is necessary
|
||||
to complete the iteration to assert an accurate count value.
|
||||
"""
|
||||
self.response
|
||||
return self._count
|
||||
|
||||
@property
|
||||
def scanned_count(self):
|
||||
"""
|
||||
As above, but representing the total number of items scanned by
|
||||
DynamoDB, without regard to any filters.
|
||||
"""
|
||||
self.response
|
||||
return self._scanned_count
|
||||
|
||||
@property
|
||||
def consumed_units(self):
|
||||
"""
|
||||
Returns a float representing the ConsumedCapacityUnits accumulated.
|
||||
"""
|
||||
self.response
|
||||
return self._consumed_units
|
||||
|
||||
@property
|
||||
def response(self):
|
||||
"""
|
||||
The current response to the call from DynamoDB.
|
||||
"""
|
||||
return self.next_response() if self._response is None else self._response
|
||||
|
||||
def next_response(self):
|
||||
"""
|
||||
Issue a call and return the result. You can invoke this method
|
||||
while iterating over the TableGenerator in order to skip to the
|
||||
next "page" of results.
|
||||
"""
|
||||
# preserve any existing limit in case the user alters self.remaining
|
||||
limit = self.kwargs.get('limit')
|
||||
if (self.remaining > 0 and (limit is None or limit > self.remaining)):
|
||||
self.kwargs['limit'] = self.remaining
|
||||
self._response = self.callable(**self.kwargs)
|
||||
self.kwargs['limit'] = limit
|
||||
self._consumed_units += self._response.get('ConsumedCapacityUnits', 0.0)
|
||||
self._count += self._response.get('Count', 0)
|
||||
self._scanned_count += self._response.get('ScannedCount', 0)
|
||||
# at the expense of a possibly gratuitous dynamize, ensure that
|
||||
# early generator termination won't result in bad LEK values
|
||||
if 'LastEvaluatedKey' in self._response:
|
||||
lek = self._response['LastEvaluatedKey']
|
||||
esk = self.table.layer2.dynamize_last_evaluated_key(lek)
|
||||
self.kwargs['exclusive_start_key'] = esk
|
||||
lektuple = (lek['HashKeyElement'],)
|
||||
if 'RangeKeyElement' in lek:
|
||||
lektuple += (lek['RangeKeyElement'],)
|
||||
self.last_evaluated_key = lektuple
|
||||
else:
|
||||
self.last_evaluated_key = None
|
||||
return self._response
|
||||
|
||||
def __iter__(self):
|
||||
while self.remaining != 0:
|
||||
response = self.response
|
||||
for item in response.get('Items', []):
|
||||
self.remaining -= 1
|
||||
yield self.item_class(self.table, attrs=item)
|
||||
if self.remaining == 0:
|
||||
break
|
||||
if response is not self._response:
|
||||
break
|
||||
else:
|
||||
if self.last_evaluated_key is not None:
|
||||
self.next_response()
|
||||
continue
|
||||
break
|
||||
if response is not self._response:
|
||||
continue
|
||||
break
|
||||
|
||||
|
||||
class Layer2(object):
|
||||
|
||||
def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
|
||||
is_secure=True, port=None, proxy=None, proxy_port=None,
|
||||
debug=0, security_token=None, region=None,
|
||||
validate_certs=True, dynamizer=LossyFloatDynamizer):
|
||||
self.layer1 = Layer1(aws_access_key_id, aws_secret_access_key,
|
||||
is_secure, port, proxy, proxy_port,
|
||||
debug, security_token, region,
|
||||
validate_certs=validate_certs)
|
||||
self.dynamizer = dynamizer()
|
||||
|
||||
def use_decimals(self):
|
||||
"""
|
||||
Use the ``decimal.Decimal`` type for encoding/decoding numeric types.
|
||||
|
||||
By default, ints/floats are used to represent numeric types
|
||||
('N', 'NS') received from DynamoDB. Using the ``Decimal``
|
||||
type is recommended to prevent loss of precision.
|
||||
|
||||
"""
|
||||
# Eventually this should be made the default dynamizer.
|
||||
self.dynamizer = Dynamizer()
|
||||
|
||||
def dynamize_attribute_updates(self, pending_updates):
|
||||
"""
|
||||
Convert a set of pending item updates into the structure
|
||||
required by Layer1.
|
||||
"""
|
||||
d = {}
|
||||
for attr_name in pending_updates:
|
||||
action, value = pending_updates[attr_name]
|
||||
if value is None:
|
||||
# DELETE without an attribute value
|
||||
d[attr_name] = {"Action": action}
|
||||
else:
|
||||
d[attr_name] = {"Action": action,
|
||||
"Value": self.dynamizer.encode(value)}
|
||||
return d
|
||||
|
||||
def dynamize_item(self, item):
|
||||
d = {}
|
||||
for attr_name in item:
|
||||
d[attr_name] = self.dynamizer.encode(item[attr_name])
|
||||
return d
|
||||
|
||||
def dynamize_range_key_condition(self, range_key_condition):
|
||||
"""
|
||||
Convert a layer2 range_key_condition parameter into the
|
||||
structure required by Layer1.
|
||||
"""
|
||||
return range_key_condition.to_dict()
|
||||
|
||||
def dynamize_scan_filter(self, scan_filter):
|
||||
"""
|
||||
Convert a layer2 scan_filter parameter into the
|
||||
structure required by Layer1.
|
||||
"""
|
||||
d = None
|
||||
if scan_filter:
|
||||
d = {}
|
||||
for attr_name in scan_filter:
|
||||
condition = scan_filter[attr_name]
|
||||
d[attr_name] = condition.to_dict()
|
||||
return d
|
||||
|
||||
def dynamize_expected_value(self, expected_value):
|
||||
"""
|
||||
Convert an expected_value parameter into the data structure
|
||||
required for Layer1.
|
||||
"""
|
||||
d = None
|
||||
if expected_value:
|
||||
d = {}
|
||||
for attr_name in expected_value:
|
||||
attr_value = expected_value[attr_name]
|
||||
if attr_value is True:
|
||||
attr_value = {'Exists': True}
|
||||
elif attr_value is False:
|
||||
attr_value = {'Exists': False}
|
||||
else:
|
||||
val = self.dynamizer.encode(expected_value[attr_name])
|
||||
attr_value = {'Value': val}
|
||||
d[attr_name] = attr_value
|
||||
return d
|
||||
|
||||
def dynamize_last_evaluated_key(self, last_evaluated_key):
|
||||
"""
|
||||
Convert a last_evaluated_key parameter into the data structure
|
||||
required for Layer1.
|
||||
"""
|
||||
d = None
|
||||
if last_evaluated_key:
|
||||
hash_key = last_evaluated_key['HashKeyElement']
|
||||
d = {'HashKeyElement': self.dynamizer.encode(hash_key)}
|
||||
if 'RangeKeyElement' in last_evaluated_key:
|
||||
range_key = last_evaluated_key['RangeKeyElement']
|
||||
d['RangeKeyElement'] = self.dynamizer.encode(range_key)
|
||||
return d
|
||||
|
||||
def build_key_from_values(self, schema, hash_key, range_key=None):
|
||||
"""
|
||||
Build a Key structure to be used for accessing items
|
||||
in Amazon DynamoDB. This method takes the supplied hash_key
|
||||
and optional range_key and validates them against the
|
||||
schema. If there is a mismatch, a TypeError is raised.
|
||||
Otherwise, a Python dict version of a Amazon DynamoDB Key
|
||||
data structure is returned.
|
||||
|
||||
:type hash_key: int|float|str|unicode|Binary
|
||||
:param hash_key: The hash key of the item you are looking for.
|
||||
The type of the hash key should match the type defined in
|
||||
the schema.
|
||||
|
||||
:type range_key: int|float|str|unicode|Binary
|
||||
:param range_key: The range key of the item your are looking for.
|
||||
This should be supplied only if the schema requires a
|
||||
range key. The type of the range key should match the
|
||||
type defined in the schema.
|
||||
"""
|
||||
dynamodb_key = {}
|
||||
dynamodb_value = self.dynamizer.encode(hash_key)
|
||||
if dynamodb_value.keys()[0] != schema.hash_key_type:
|
||||
msg = 'Hashkey must be of type: %s' % schema.hash_key_type
|
||||
raise TypeError(msg)
|
||||
dynamodb_key['HashKeyElement'] = dynamodb_value
|
||||
if range_key is not None:
|
||||
dynamodb_value = self.dynamizer.encode(range_key)
|
||||
if dynamodb_value.keys()[0] != schema.range_key_type:
|
||||
msg = 'RangeKey must be of type: %s' % schema.range_key_type
|
||||
raise TypeError(msg)
|
||||
dynamodb_key['RangeKeyElement'] = dynamodb_value
|
||||
return dynamodb_key
|
||||
|
||||
def new_batch_list(self):
|
||||
"""
|
||||
Return a new, empty :class:`boto.dynamodb.batch.BatchList`
|
||||
object.
|
||||
"""
|
||||
return BatchList(self)
|
||||
|
||||
def new_batch_write_list(self):
|
||||
"""
|
||||
Return a new, empty :class:`boto.dynamodb.batch.BatchWriteList`
|
||||
object.
|
||||
"""
|
||||
return BatchWriteList(self)
|
||||
|
||||
def list_tables(self, limit=None):
|
||||
"""
|
||||
Return a list of the names of all tables associated with the
|
||||
current account and region.
|
||||
|
||||
:type limit: int
|
||||
:param limit: The maximum number of tables to return.
|
||||
"""
|
||||
tables = []
|
||||
start_table = None
|
||||
while not limit or len(tables) < limit:
|
||||
this_round_limit = None
|
||||
if limit:
|
||||
this_round_limit = limit - len(tables)
|
||||
this_round_limit = min(this_round_limit, 100)
|
||||
result = self.layer1.list_tables(limit=this_round_limit, start_table=start_table)
|
||||
tables.extend(result.get('TableNames', []))
|
||||
start_table = result.get('LastEvaluatedTableName', None)
|
||||
if not start_table:
|
||||
break
|
||||
return tables
|
||||
|
||||
def describe_table(self, name):
|
||||
"""
|
||||
Retrieve information about an existing table.
|
||||
|
||||
:type name: str
|
||||
:param name: The name of the desired table.
|
||||
|
||||
"""
|
||||
return self.layer1.describe_table(name)
|
||||
|
||||
def table_from_schema(self, name, schema):
|
||||
"""
|
||||
Create a Table object from a schema.
|
||||
|
||||
This method will create a Table object without
|
||||
making any API calls. If you know the name and schema
|
||||
of the table, you can use this method instead of
|
||||
``get_table``.
|
||||
|
||||
Example usage::
|
||||
|
||||
table = layer2.table_from_schema(
|
||||
'tablename',
|
||||
Schema.create(hash_key=('foo', 'N')))
|
||||
|
||||
:type name: str
|
||||
:param name: The name of the table.
|
||||
|
||||
:type schema: :class:`boto.dynamodb.schema.Schema`
|
||||
:param schema: The schema associated with the table.
|
||||
|
||||
:rtype: :class:`boto.dynamodb.table.Table`
|
||||
:return: A Table object representing the table.
|
||||
|
||||
"""
|
||||
return Table.create_from_schema(self, name, schema)
|
||||
|
||||
def get_table(self, name):
|
||||
"""
|
||||
Retrieve the Table object for an existing table.
|
||||
|
||||
:type name: str
|
||||
:param name: The name of the desired table.
|
||||
|
||||
:rtype: :class:`boto.dynamodb.table.Table`
|
||||
:return: A Table object representing the table.
|
||||
"""
|
||||
response = self.layer1.describe_table(name)
|
||||
return Table(self, response)
|
||||
|
||||
lookup = get_table
|
||||
|
||||
def create_table(self, name, schema, read_units, write_units):
|
||||
"""
|
||||
Create a new Amazon DynamoDB table.
|
||||
|
||||
:type name: str
|
||||
:param name: The name of the desired table.
|
||||
|
||||
:type schema: :class:`boto.dynamodb.schema.Schema`
|
||||
:param schema: The Schema object that defines the schema used
|
||||
by this table.
|
||||
|
||||
:type read_units: int
|
||||
:param read_units: The value for ReadCapacityUnits.
|
||||
|
||||
:type write_units: int
|
||||
:param write_units: The value for WriteCapacityUnits.
|
||||
|
||||
:rtype: :class:`boto.dynamodb.table.Table`
|
||||
:return: A Table object representing the new Amazon DynamoDB table.
|
||||
"""
|
||||
response = self.layer1.create_table(name, schema.dict,
|
||||
{'ReadCapacityUnits': read_units,
|
||||
'WriteCapacityUnits': write_units})
|
||||
return Table(self, response)
|
||||
|
||||
def update_throughput(self, table, read_units, write_units):
|
||||
"""
|
||||
Update the ProvisionedThroughput for the Amazon DynamoDB Table.
|
||||
|
||||
:type table: :class:`boto.dynamodb.table.Table`
|
||||
:param table: The Table object whose throughput is being updated.
|
||||
|
||||
:type read_units: int
|
||||
:param read_units: The new value for ReadCapacityUnits.
|
||||
|
||||
:type write_units: int
|
||||
:param write_units: The new value for WriteCapacityUnits.
|
||||
"""
|
||||
response = self.layer1.update_table(table.name,
|
||||
{'ReadCapacityUnits': read_units,
|
||||
'WriteCapacityUnits': write_units})
|
||||
table.update_from_response(response)
|
||||
|
||||
def delete_table(self, table):
|
||||
"""
|
||||
Delete this table and all items in it. After calling this
|
||||
the Table objects status attribute will be set to 'DELETING'.
|
||||
|
||||
:type table: :class:`boto.dynamodb.table.Table`
|
||||
:param table: The Table object that is being deleted.
|
||||
"""
|
||||
response = self.layer1.delete_table(table.name)
|
||||
table.update_from_response(response)
|
||||
|
||||
def create_schema(self, hash_key_name, hash_key_proto_value,
|
||||
range_key_name=None, range_key_proto_value=None):
|
||||
"""
|
||||
Create a Schema object used when creating a Table.
|
||||
|
||||
:type hash_key_name: str
|
||||
:param hash_key_name: The name of the HashKey for the schema.
|
||||
|
||||
:type hash_key_proto_value: int|long|float|str|unicode|Binary
|
||||
:param hash_key_proto_value: A sample or prototype of the type
|
||||
of value you want to use for the HashKey. Alternatively,
|
||||
you can also just pass in the Python type (e.g. int, float, etc.).
|
||||
|
||||
:type range_key_name: str
|
||||
:param range_key_name: The name of the RangeKey for the schema.
|
||||
This parameter is optional.
|
||||
|
||||
:type range_key_proto_value: int|long|float|str|unicode|Binary
|
||||
:param range_key_proto_value: A sample or prototype of the type
|
||||
of value you want to use for the RangeKey. Alternatively,
|
||||
you can also pass in the Python type (e.g. int, float, etc.)
|
||||
This parameter is optional.
|
||||
"""
|
||||
hash_key = (hash_key_name, get_dynamodb_type(hash_key_proto_value))
|
||||
if range_key_name and range_key_proto_value is not None:
|
||||
range_key = (range_key_name,
|
||||
get_dynamodb_type(range_key_proto_value))
|
||||
else:
|
||||
range_key = None
|
||||
return Schema.create(hash_key, range_key)
|
||||
|
||||
def get_item(self, table, hash_key, range_key=None,
|
||||
attributes_to_get=None, consistent_read=False,
|
||||
item_class=Item):
|
||||
"""
|
||||
Retrieve an existing item from the table.
|
||||
|
||||
:type table: :class:`boto.dynamodb.table.Table`
|
||||
:param table: The Table object from which the item is retrieved.
|
||||
|
||||
:type hash_key: int|long|float|str|unicode|Binary
|
||||
:param hash_key: The HashKey of the requested item. The
|
||||
type of the value must match the type defined in the
|
||||
schema for the table.
|
||||
|
||||
:type range_key: int|long|float|str|unicode|Binary
|
||||
:param range_key: The optional RangeKey of the requested item.
|
||||
The type of the value must match the type defined in the
|
||||
schema for the table.
|
||||
|
||||
:type attributes_to_get: list
|
||||
:param attributes_to_get: A list of attribute names.
|
||||
If supplied, only the specified attribute names will
|
||||
be returned. Otherwise, all attributes will be returned.
|
||||
|
||||
:type consistent_read: bool
|
||||
:param consistent_read: If True, a consistent read
|
||||
request is issued. Otherwise, an eventually consistent
|
||||
request is issued.
|
||||
|
||||
:type item_class: Class
|
||||
:param item_class: Allows you to override the class used
|
||||
to generate the items. This should be a subclass of
|
||||
:class:`boto.dynamodb.item.Item`
|
||||
"""
|
||||
key = self.build_key_from_values(table.schema, hash_key, range_key)
|
||||
response = self.layer1.get_item(table.name, key,
|
||||
attributes_to_get, consistent_read,
|
||||
object_hook=self.dynamizer.decode)
|
||||
item = item_class(table, hash_key, range_key, response['Item'])
|
||||
if 'ConsumedCapacityUnits' in response:
|
||||
item.consumed_units = response['ConsumedCapacityUnits']
|
||||
return item
|
||||
|
||||
def batch_get_item(self, batch_list):
|
||||
"""
|
||||
Return a set of attributes for a multiple items in
|
||||
multiple tables using their primary keys.
|
||||
|
||||
:type batch_list: :class:`boto.dynamodb.batch.BatchList`
|
||||
:param batch_list: A BatchList object which consists of a
|
||||
list of :class:`boto.dynamoddb.batch.Batch` objects.
|
||||
Each Batch object contains the information about one
|
||||
batch of objects that you wish to retrieve in this
|
||||
request.
|
||||
"""
|
||||
request_items = batch_list.to_dict()
|
||||
return self.layer1.batch_get_item(request_items,
|
||||
object_hook=self.dynamizer.decode)
|
||||
|
||||
def batch_write_item(self, batch_list):
|
||||
"""
|
||||
Performs multiple Puts and Deletes in one batch.
|
||||
|
||||
:type batch_list: :class:`boto.dynamodb.batch.BatchWriteList`
|
||||
:param batch_list: A BatchWriteList object which consists of a
|
||||
list of :class:`boto.dynamoddb.batch.BatchWrite` objects.
|
||||
Each Batch object contains the information about one
|
||||
batch of objects that you wish to put or delete.
|
||||
"""
|
||||
request_items = batch_list.to_dict()
|
||||
return self.layer1.batch_write_item(request_items,
|
||||
object_hook=self.dynamizer.decode)
|
||||
|
||||
def put_item(self, item, expected_value=None, return_values=None):
|
||||
"""
|
||||
Store a new item or completely replace an existing item
|
||||
in Amazon DynamoDB.
|
||||
|
||||
:type item: :class:`boto.dynamodb.item.Item`
|
||||
:param item: The Item to write to Amazon DynamoDB.
|
||||
|
||||
:type expected_value: dict
|
||||
:param expected_value: A dictionary of name/value pairs that you expect.
|
||||
This dictionary should have name/value pairs where the name
|
||||
is the name of the attribute and the value is either the value
|
||||
you are expecting or False if you expect the attribute not to
|
||||
exist.
|
||||
|
||||
:type return_values: str
|
||||
:param return_values: Controls the return of attribute
|
||||
name-value pairs before then were changed. Possible
|
||||
values are: None or 'ALL_OLD'. If 'ALL_OLD' is
|
||||
specified and the item is overwritten, the content
|
||||
of the old item is returned.
|
||||
"""
|
||||
expected_value = self.dynamize_expected_value(expected_value)
|
||||
response = self.layer1.put_item(item.table.name,
|
||||
self.dynamize_item(item),
|
||||
expected_value, return_values,
|
||||
object_hook=self.dynamizer.decode)
|
||||
if 'ConsumedCapacityUnits' in response:
|
||||
item.consumed_units = response['ConsumedCapacityUnits']
|
||||
return response
|
||||
|
||||
def update_item(self, item, expected_value=None, return_values=None):
|
||||
"""
|
||||
Commit pending item updates to Amazon DynamoDB.
|
||||
|
||||
:type item: :class:`boto.dynamodb.item.Item`
|
||||
:param item: The Item to update in Amazon DynamoDB. It is expected
|
||||
that you would have called the add_attribute, put_attribute
|
||||
and/or delete_attribute methods on this Item prior to calling
|
||||
this method. Those queued changes are what will be updated.
|
||||
|
||||
:type expected_value: dict
|
||||
:param expected_value: A dictionary of name/value pairs that you
|
||||
expect. This dictionary should have name/value pairs where the
|
||||
name is the name of the attribute and the value is either the
|
||||
value you are expecting or False if you expect the attribute
|
||||
not to exist.
|
||||
|
||||
:type return_values: str
|
||||
:param return_values: Controls the return of attribute name/value pairs
|
||||
before they were updated. Possible values are: None, 'ALL_OLD',
|
||||
'UPDATED_OLD', 'ALL_NEW' or 'UPDATED_NEW'. If 'ALL_OLD' is
|
||||
specified and the item is overwritten, the content of the old item
|
||||
is returned. If 'ALL_NEW' is specified, then all the attributes of
|
||||
the new version of the item are returned. If 'UPDATED_NEW' is
|
||||
specified, the new versions of only the updated attributes are
|
||||
returned.
|
||||
|
||||
"""
|
||||
expected_value = self.dynamize_expected_value(expected_value)
|
||||
key = self.build_key_from_values(item.table.schema,
|
||||
item.hash_key, item.range_key)
|
||||
attr_updates = self.dynamize_attribute_updates(item._updates)
|
||||
|
||||
response = self.layer1.update_item(item.table.name, key,
|
||||
attr_updates,
|
||||
expected_value, return_values,
|
||||
object_hook=self.dynamizer.decode)
|
||||
item._updates.clear()
|
||||
if 'ConsumedCapacityUnits' in response:
|
||||
item.consumed_units = response['ConsumedCapacityUnits']
|
||||
return response
|
||||
|
||||
def delete_item(self, item, expected_value=None, return_values=None):
|
||||
"""
|
||||
Delete the item from Amazon DynamoDB.
|
||||
|
||||
:type item: :class:`boto.dynamodb.item.Item`
|
||||
:param item: The Item to delete from Amazon DynamoDB.
|
||||
|
||||
:type expected_value: dict
|
||||
:param expected_value: A dictionary of name/value pairs that you expect.
|
||||
This dictionary should have name/value pairs where the name
|
||||
is the name of the attribute and the value is either the value
|
||||
you are expecting or False if you expect the attribute not to
|
||||
exist.
|
||||
|
||||
:type return_values: str
|
||||
:param return_values: Controls the return of attribute
|
||||
name-value pairs before then were changed. Possible
|
||||
values are: None or 'ALL_OLD'. If 'ALL_OLD' is
|
||||
specified and the item is overwritten, the content
|
||||
of the old item is returned.
|
||||
"""
|
||||
expected_value = self.dynamize_expected_value(expected_value)
|
||||
key = self.build_key_from_values(item.table.schema,
|
||||
item.hash_key, item.range_key)
|
||||
return self.layer1.delete_item(item.table.name, key,
|
||||
expected=expected_value,
|
||||
return_values=return_values,
|
||||
object_hook=self.dynamizer.decode)
|
||||
|
||||
def query(self, table, hash_key, range_key_condition=None,
|
||||
attributes_to_get=None, request_limit=None,
|
||||
max_results=None, consistent_read=False,
|
||||
scan_index_forward=True, exclusive_start_key=None,
|
||||
item_class=Item, count=False):
|
||||
"""
|
||||
Perform a query on the table.
|
||||
|
||||
:type table: :class:`boto.dynamodb.table.Table`
|
||||
:param table: The Table object that is being queried.
|
||||
|
||||
:type hash_key: int|long|float|str|unicode|Binary
|
||||
:param hash_key: The HashKey of the requested item. The
|
||||
type of the value must match the type defined in the
|
||||
schema for the table.
|
||||
|
||||
:type range_key_condition: :class:`boto.dynamodb.condition.Condition`
|
||||
:param range_key_condition: A Condition object.
|
||||
Condition object can be one of the following types:
|
||||
|
||||
EQ|LE|LT|GE|GT|BEGINS_WITH|BETWEEN
|
||||
|
||||
The only condition which expects or will accept two
|
||||
values is 'BETWEEN', otherwise a single value should
|
||||
be passed to the Condition constructor.
|
||||
|
||||
:type attributes_to_get: list
|
||||
:param attributes_to_get: A list of attribute names.
|
||||
If supplied, only the specified attribute names will
|
||||
be returned. Otherwise, all attributes will be returned.
|
||||
|
||||
:type request_limit: int
|
||||
:param request_limit: The maximum number of items to retrieve
|
||||
from Amazon DynamoDB on each request. You may want to set
|
||||
a specific request_limit based on the provisioned throughput
|
||||
of your table. The default behavior is to retrieve as many
|
||||
results as possible per request.
|
||||
|
||||
:type max_results: int
|
||||
:param max_results: The maximum number of results that will
|
||||
be retrieved from Amazon DynamoDB in total. For example,
|
||||
if you only wanted to see the first 100 results from the
|
||||
query, regardless of how many were actually available, you
|
||||
could set max_results to 100 and the generator returned
|
||||
from the query method will only yeild 100 results max.
|
||||
|
||||
:type consistent_read: bool
|
||||
:param consistent_read: If True, a consistent read
|
||||
request is issued. Otherwise, an eventually consistent
|
||||
request is issued.
|
||||
|
||||
:type scan_index_forward: bool
|
||||
:param scan_index_forward: Specified forward or backward
|
||||
traversal of the index. Default is forward (True).
|
||||
|
||||
:type count: bool
|
||||
:param count: If True, Amazon DynamoDB returns a total
|
||||
number of items for the Query operation, even if the
|
||||
operation has no matching items for the assigned filter.
|
||||
If count is True, the actual items are not returned and
|
||||
the count is accessible as the ``count`` attribute of
|
||||
the returned object.
|
||||
|
||||
:type exclusive_start_key: list or tuple
|
||||
:param exclusive_start_key: Primary key of the item from
|
||||
which to continue an earlier query. This would be
|
||||
provided as the LastEvaluatedKey in that query.
|
||||
|
||||
:type item_class: Class
|
||||
:param item_class: Allows you to override the class used
|
||||
to generate the items. This should be a subclass of
|
||||
:class:`boto.dynamodb.item.Item`
|
||||
|
||||
:rtype: :class:`boto.dynamodb.layer2.TableGenerator`
|
||||
"""
|
||||
if range_key_condition:
|
||||
rkc = self.dynamize_range_key_condition(range_key_condition)
|
||||
else:
|
||||
rkc = None
|
||||
if exclusive_start_key:
|
||||
esk = self.build_key_from_values(table.schema,
|
||||
*exclusive_start_key)
|
||||
else:
|
||||
esk = None
|
||||
kwargs = {'table_name': table.name,
|
||||
'hash_key_value': self.dynamizer.encode(hash_key),
|
||||
'range_key_conditions': rkc,
|
||||
'attributes_to_get': attributes_to_get,
|
||||
'limit': request_limit,
|
||||
'count': count,
|
||||
'consistent_read': consistent_read,
|
||||
'scan_index_forward': scan_index_forward,
|
||||
'exclusive_start_key': esk,
|
||||
'object_hook': self.dynamizer.decode}
|
||||
return TableGenerator(table, self.layer1.query,
|
||||
max_results, item_class, kwargs)
|
||||
|
||||
def scan(self, table, scan_filter=None,
|
||||
attributes_to_get=None, request_limit=None, max_results=None,
|
||||
exclusive_start_key=None, item_class=Item, count=False):
|
||||
"""
|
||||
Perform a scan of DynamoDB.
|
||||
|
||||
:type table: :class:`boto.dynamodb.table.Table`
|
||||
:param table: The Table object that is being scanned.
|
||||
|
||||
:type scan_filter: A dict
|
||||
:param scan_filter: A dictionary where the key is the
|
||||
attribute name and the value is a
|
||||
:class:`boto.dynamodb.condition.Condition` object.
|
||||
Valid Condition objects include:
|
||||
|
||||
* EQ - equal (1)
|
||||
* NE - not equal (1)
|
||||
* LE - less than or equal (1)
|
||||
* LT - less than (1)
|
||||
* GE - greater than or equal (1)
|
||||
* GT - greater than (1)
|
||||
* NOT_NULL - attribute exists (0, use None)
|
||||
* NULL - attribute does not exist (0, use None)
|
||||
* CONTAINS - substring or value in list (1)
|
||||
* NOT_CONTAINS - absence of substring or value in list (1)
|
||||
* BEGINS_WITH - substring prefix (1)
|
||||
* IN - exact match in list (N)
|
||||
* BETWEEN - >= first value, <= second value (2)
|
||||
|
||||
:type attributes_to_get: list
|
||||
:param attributes_to_get: A list of attribute names.
|
||||
If supplied, only the specified attribute names will
|
||||
be returned. Otherwise, all attributes will be returned.
|
||||
|
||||
:type request_limit: int
|
||||
:param request_limit: The maximum number of items to retrieve
|
||||
from Amazon DynamoDB on each request. You may want to set
|
||||
a specific request_limit based on the provisioned throughput
|
||||
of your table. The default behavior is to retrieve as many
|
||||
results as possible per request.
|
||||
|
||||
:type max_results: int
|
||||
:param max_results: The maximum number of results that will
|
||||
be retrieved from Amazon DynamoDB in total. For example,
|
||||
if you only wanted to see the first 100 results from the
|
||||
query, regardless of how many were actually available, you
|
||||
could set max_results to 100 and the generator returned
|
||||
from the query method will only yeild 100 results max.
|
||||
|
||||
:type count: bool
|
||||
:param count: If True, Amazon DynamoDB returns a total
|
||||
number of items for the Scan operation, even if the
|
||||
operation has no matching items for the assigned filter.
|
||||
If count is True, the actual items are not returned and
|
||||
the count is accessible as the ``count`` attribute of
|
||||
the returned object.
|
||||
|
||||
:type exclusive_start_key: list or tuple
|
||||
:param exclusive_start_key: Primary key of the item from
|
||||
which to continue an earlier query. This would be
|
||||
provided as the LastEvaluatedKey in that query.
|
||||
|
||||
:type item_class: Class
|
||||
:param item_class: Allows you to override the class used
|
||||
to generate the items. This should be a subclass of
|
||||
:class:`boto.dynamodb.item.Item`
|
||||
|
||||
:rtype: :class:`boto.dynamodb.layer2.TableGenerator`
|
||||
"""
|
||||
if exclusive_start_key:
|
||||
esk = self.build_key_from_values(table.schema,
|
||||
*exclusive_start_key)
|
||||
else:
|
||||
esk = None
|
||||
kwargs = {'table_name': table.name,
|
||||
'scan_filter': self.dynamize_scan_filter(scan_filter),
|
||||
'attributes_to_get': attributes_to_get,
|
||||
'limit': request_limit,
|
||||
'count': count,
|
||||
'exclusive_start_key': esk,
|
||||
'object_hook': self.dynamizer.decode}
|
||||
return TableGenerator(table, self.layer1.scan,
|
||||
max_results, item_class, kwargs)
|
|
@ -1,112 +0,0 @@
|
|||
# Copyright (c) 2011 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
|
||||
class Schema(object):
|
||||
"""
|
||||
Represents a DynamoDB schema.
|
||||
|
||||
:ivar hash_key_name: The name of the hash key of the schema.
|
||||
:ivar hash_key_type: The DynamoDB type specification for the
|
||||
hash key of the schema.
|
||||
:ivar range_key_name: The name of the range key of the schema
|
||||
or None if no range key is defined.
|
||||
:ivar range_key_type: The DynamoDB type specification for the
|
||||
range key of the schema or None if no range key is defined.
|
||||
:ivar dict: The underlying Python dictionary that needs to be
|
||||
passed to Layer1 methods.
|
||||
"""
|
||||
|
||||
def __init__(self, schema_dict):
|
||||
self._dict = schema_dict
|
||||
|
||||
def __repr__(self):
|
||||
if self.range_key_name:
|
||||
s = 'Schema(%s:%s)' % (self.hash_key_name, self.range_key_name)
|
||||
else:
|
||||
s = 'Schema(%s)' % self.hash_key_name
|
||||
return s
|
||||
|
||||
@classmethod
|
||||
def create(cls, hash_key, range_key=None):
|
||||
"""Convenience method to create a schema object.
|
||||
|
||||
Example usage::
|
||||
|
||||
schema = Schema.create(hash_key=('foo', 'N'))
|
||||
schema2 = Schema.create(hash_key=('foo', 'N'),
|
||||
range_key=('bar', 'S'))
|
||||
|
||||
:type hash_key: tuple
|
||||
:param hash_key: A tuple of (hash_key_name, hash_key_type)
|
||||
|
||||
:type range_key: tuple
|
||||
:param hash_key: A tuple of (range_key_name, range_key_type)
|
||||
|
||||
"""
|
||||
reconstructed = {
|
||||
'HashKeyElement': {
|
||||
'AttributeName': hash_key[0],
|
||||
'AttributeType': hash_key[1],
|
||||
}
|
||||
}
|
||||
if range_key is not None:
|
||||
reconstructed['RangeKeyElement'] = {
|
||||
'AttributeName': range_key[0],
|
||||
'AttributeType': range_key[1],
|
||||
}
|
||||
instance = cls(None)
|
||||
instance._dict = reconstructed
|
||||
return instance
|
||||
|
||||
@property
|
||||
def dict(self):
|
||||
return self._dict
|
||||
|
||||
@property
|
||||
def hash_key_name(self):
|
||||
return self._dict['HashKeyElement']['AttributeName']
|
||||
|
||||
@property
|
||||
def hash_key_type(self):
|
||||
return self._dict['HashKeyElement']['AttributeType']
|
||||
|
||||
@property
|
||||
def range_key_name(self):
|
||||
name = None
|
||||
if 'RangeKeyElement' in self._dict:
|
||||
name = self._dict['RangeKeyElement']['AttributeName']
|
||||
return name
|
||||
|
||||
@property
|
||||
def range_key_type(self):
|
||||
type = None
|
||||
if 'RangeKeyElement' in self._dict:
|
||||
type = self._dict['RangeKeyElement']['AttributeType']
|
||||
return type
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.hash_key_name == other.hash_key_name and
|
||||
self.hash_key_type == other.hash_key_type and
|
||||
self.range_key_name == other.range_key_name and
|
||||
self.range_key_type == other.range_key_type)
|
|
@ -1,546 +0,0 @@
|
|||
# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
from boto.dynamodb.batch import BatchList
|
||||
from boto.dynamodb.schema import Schema
|
||||
from boto.dynamodb.item import Item
|
||||
from boto.dynamodb import exceptions as dynamodb_exceptions
|
||||
import time
|
||||
|
||||
|
||||
class TableBatchGenerator(object):
|
||||
"""
|
||||
A low-level generator used to page through results from
|
||||
batch_get_item operations.
|
||||
|
||||
:ivar consumed_units: An integer that holds the number of
|
||||
ConsumedCapacityUnits accumulated thus far for this
|
||||
generator.
|
||||
"""
|
||||
|
||||
def __init__(self, table, keys, attributes_to_get=None,
|
||||
consistent_read=False):
|
||||
self.table = table
|
||||
self.keys = keys
|
||||
self.consumed_units = 0
|
||||
self.attributes_to_get = attributes_to_get
|
||||
self.consistent_read = consistent_read
|
||||
|
||||
def _queue_unprocessed(self, res):
|
||||
if not u'UnprocessedKeys' in res:
|
||||
return
|
||||
if not self.table.name in res[u'UnprocessedKeys']:
|
||||
return
|
||||
|
||||
keys = res[u'UnprocessedKeys'][self.table.name][u'Keys']
|
||||
|
||||
for key in keys:
|
||||
h = key[u'HashKeyElement']
|
||||
r = key[u'RangeKeyElement'] if u'RangeKeyElement' in key else None
|
||||
self.keys.append((h, r))
|
||||
|
||||
def __iter__(self):
|
||||
while self.keys:
|
||||
# Build the next batch
|
||||
batch = BatchList(self.table.layer2)
|
||||
batch.add_batch(self.table, self.keys[:100],
|
||||
self.attributes_to_get)
|
||||
res = batch.submit()
|
||||
|
||||
# parse the results
|
||||
if not self.table.name in res[u'Responses']:
|
||||
continue
|
||||
self.consumed_units += res[u'Responses'][self.table.name][u'ConsumedCapacityUnits']
|
||||
for elem in res[u'Responses'][self.table.name][u'Items']:
|
||||
yield elem
|
||||
|
||||
# re-queue un processed keys
|
||||
self.keys = self.keys[100:]
|
||||
self._queue_unprocessed(res)
|
||||
|
||||
|
||||
class Table(object):
|
||||
"""
|
||||
An Amazon DynamoDB table.
|
||||
|
||||
:ivar name: The name of the table.
|
||||
:ivar create_time: The date and time that the table was created.
|
||||
:ivar status: The current status of the table. One of:
|
||||
'ACTIVE', 'UPDATING', 'DELETING'.
|
||||
:ivar schema: A :class:`boto.dynamodb.schema.Schema` object representing
|
||||
the schema defined for the table.
|
||||
:ivar item_count: The number of items in the table. This value is
|
||||
set only when the Table object is created or refreshed and
|
||||
may not reflect the actual count.
|
||||
:ivar size_bytes: Total size of the specified table, in bytes.
|
||||
Amazon DynamoDB updates this value approximately every six hours.
|
||||
Recent changes might not be reflected in this value.
|
||||
:ivar read_units: The ReadCapacityUnits of the tables
|
||||
Provisioned Throughput.
|
||||
:ivar write_units: The WriteCapacityUnits of the tables
|
||||
Provisioned Throughput.
|
||||
:ivar schema: The Schema object associated with the table.
|
||||
"""
|
||||
|
||||
def __init__(self, layer2, response):
|
||||
"""
|
||||
|
||||
:type layer2: :class:`boto.dynamodb.layer2.Layer2`
|
||||
:param layer2: A `Layer2` api object.
|
||||
|
||||
:type response: dict
|
||||
:param response: The output of
|
||||
`boto.dynamodb.layer1.Layer1.describe_table`.
|
||||
|
||||
"""
|
||||
self.layer2 = layer2
|
||||
self._dict = {}
|
||||
self.update_from_response(response)
|
||||
|
||||
@classmethod
|
||||
def create_from_schema(cls, layer2, name, schema):
|
||||
"""Create a Table object.
|
||||
|
||||
If you know the name and schema of your table, you can
|
||||
create a ``Table`` object without having to make any
|
||||
API calls (normally an API call is made to retrieve
|
||||
the schema of a table).
|
||||
|
||||
Example usage::
|
||||
|
||||
table = Table.create_from_schema(
|
||||
boto.connect_dynamodb(),
|
||||
'tablename',
|
||||
Schema.create(hash_key=('keyname', 'N')))
|
||||
|
||||
:type layer2: :class:`boto.dynamodb.layer2.Layer2`
|
||||
:param layer2: A ``Layer2`` api object.
|
||||
|
||||
:type name: str
|
||||
:param name: The name of the table.
|
||||
|
||||
:type schema: :class:`boto.dynamodb.schema.Schema`
|
||||
:param schema: The schema associated with the table.
|
||||
|
||||
:rtype: :class:`boto.dynamodb.table.Table`
|
||||
:return: A Table object representing the table.
|
||||
|
||||
"""
|
||||
table = cls(layer2, {'Table': {'TableName': name}})
|
||||
table._schema = schema
|
||||
return table
|
||||
|
||||
def __repr__(self):
|
||||
return 'Table(%s)' % self.name
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._dict['TableName']
|
||||
|
||||
@property
|
||||
def create_time(self):
|
||||
return self._dict.get('CreationDateTime', None)
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self._dict.get('TableStatus', None)
|
||||
|
||||
@property
|
||||
def item_count(self):
|
||||
return self._dict.get('ItemCount', 0)
|
||||
|
||||
@property
|
||||
def size_bytes(self):
|
||||
return self._dict.get('TableSizeBytes', 0)
|
||||
|
||||
@property
|
||||
def schema(self):
|
||||
return self._schema
|
||||
|
||||
@property
|
||||
def read_units(self):
|
||||
try:
|
||||
return self._dict['ProvisionedThroughput']['ReadCapacityUnits']
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def write_units(self):
|
||||
try:
|
||||
return self._dict['ProvisionedThroughput']['WriteCapacityUnits']
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def update_from_response(self, response):
|
||||
"""
|
||||
Update the state of the Table object based on the response
|
||||
data received from Amazon DynamoDB.
|
||||
"""
|
||||
# 'Table' is from a describe_table call.
|
||||
if 'Table' in response:
|
||||
self._dict.update(response['Table'])
|
||||
# 'TableDescription' is from a create_table call.
|
||||
elif 'TableDescription' in response:
|
||||
self._dict.update(response['TableDescription'])
|
||||
if 'KeySchema' in self._dict:
|
||||
self._schema = Schema(self._dict['KeySchema'])
|
||||
|
||||
def refresh(self, wait_for_active=False, retry_seconds=5):
|
||||
"""
|
||||
Refresh all of the fields of the Table object by calling
|
||||
the underlying DescribeTable request.
|
||||
|
||||
:type wait_for_active: bool
|
||||
:param wait_for_active: If True, this command will not return
|
||||
until the table status, as returned from Amazon DynamoDB, is
|
||||
'ACTIVE'.
|
||||
|
||||
:type retry_seconds: int
|
||||
:param retry_seconds: If wait_for_active is True, this
|
||||
parameter controls the number of seconds of delay between
|
||||
calls to update_table in Amazon DynamoDB. Default is 5 seconds.
|
||||
"""
|
||||
done = False
|
||||
while not done:
|
||||
response = self.layer2.describe_table(self.name)
|
||||
self.update_from_response(response)
|
||||
if wait_for_active:
|
||||
if self.status == 'ACTIVE':
|
||||
done = True
|
||||
else:
|
||||
time.sleep(retry_seconds)
|
||||
else:
|
||||
done = True
|
||||
|
||||
def update_throughput(self, read_units, write_units):
|
||||
"""
|
||||
Update the ProvisionedThroughput for the Amazon DynamoDB Table.
|
||||
|
||||
:type read_units: int
|
||||
:param read_units: The new value for ReadCapacityUnits.
|
||||
|
||||
:type write_units: int
|
||||
:param write_units: The new value for WriteCapacityUnits.
|
||||
"""
|
||||
self.layer2.update_throughput(self, read_units, write_units)
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
Delete this table and all items in it. After calling this
|
||||
the Table objects status attribute will be set to 'DELETING'.
|
||||
"""
|
||||
self.layer2.delete_table(self)
|
||||
|
||||
def get_item(self, hash_key, range_key=None,
|
||||
attributes_to_get=None, consistent_read=False,
|
||||
item_class=Item):
|
||||
"""
|
||||
Retrieve an existing item from the table.
|
||||
|
||||
:type hash_key: int|long|float|str|unicode|Binary
|
||||
:param hash_key: The HashKey of the requested item. The
|
||||
type of the value must match the type defined in the
|
||||
schema for the table.
|
||||
|
||||
:type range_key: int|long|float|str|unicode|Binary
|
||||
:param range_key: The optional RangeKey of the requested item.
|
||||
The type of the value must match the type defined in the
|
||||
schema for the table.
|
||||
|
||||
:type attributes_to_get: list
|
||||
:param attributes_to_get: A list of attribute names.
|
||||
If supplied, only the specified attribute names will
|
||||
be returned. Otherwise, all attributes will be returned.
|
||||
|
||||
:type consistent_read: bool
|
||||
:param consistent_read: If True, a consistent read
|
||||
request is issued. Otherwise, an eventually consistent
|
||||
request is issued.
|
||||
|
||||
:type item_class: Class
|
||||
:param item_class: Allows you to override the class used
|
||||
to generate the items. This should be a subclass of
|
||||
:class:`boto.dynamodb.item.Item`
|
||||
"""
|
||||
return self.layer2.get_item(self, hash_key, range_key,
|
||||
attributes_to_get, consistent_read,
|
||||
item_class)
|
||||
lookup = get_item
|
||||
|
||||
def has_item(self, hash_key, range_key=None, consistent_read=False):
|
||||
"""
|
||||
Checks the table to see if the Item with the specified ``hash_key``
|
||||
exists. This may save a tiny bit of time/bandwidth over a
|
||||
straight :py:meth:`get_item` if you have no intention to touch
|
||||
the data that is returned, since this method specifically tells
|
||||
Amazon not to return anything but the Item's key.
|
||||
|
||||
:type hash_key: int|long|float|str|unicode|Binary
|
||||
:param hash_key: The HashKey of the requested item. The
|
||||
type of the value must match the type defined in the
|
||||
schema for the table.
|
||||
|
||||
:type range_key: int|long|float|str|unicode|Binary
|
||||
:param range_key: The optional RangeKey of the requested item.
|
||||
The type of the value must match the type defined in the
|
||||
schema for the table.
|
||||
|
||||
:type consistent_read: bool
|
||||
:param consistent_read: If True, a consistent read
|
||||
request is issued. Otherwise, an eventually consistent
|
||||
request is issued.
|
||||
|
||||
:rtype: bool
|
||||
:returns: ``True`` if the Item exists, ``False`` if not.
|
||||
"""
|
||||
try:
|
||||
# Attempt to get the key. If it can't be found, it'll raise
|
||||
# an exception.
|
||||
self.get_item(hash_key, range_key=range_key,
|
||||
# This minimizes the size of the response body.
|
||||
attributes_to_get=[hash_key],
|
||||
consistent_read=consistent_read)
|
||||
except dynamodb_exceptions.DynamoDBKeyNotFoundError:
|
||||
# Key doesn't exist.
|
||||
return False
|
||||
return True
|
||||
|
||||
def new_item(self, hash_key=None, range_key=None, attrs=None,
|
||||
item_class=Item):
|
||||
"""
|
||||
Return an new, unsaved Item which can later be PUT to
|
||||
Amazon DynamoDB.
|
||||
|
||||
This method has explicit (but optional) parameters for
|
||||
the hash_key and range_key values of the item. You can use
|
||||
these explicit parameters when calling the method, such as::
|
||||
|
||||
>>> my_item = my_table.new_item(hash_key='a', range_key=1,
|
||||
attrs={'key1': 'val1', 'key2': 'val2'})
|
||||
>>> my_item
|
||||
{u'bar': 1, u'foo': 'a', 'key1': 'val1', 'key2': 'val2'}
|
||||
|
||||
Or, if you prefer, you can simply put the hash_key and range_key
|
||||
in the attrs dictionary itself, like this::
|
||||
|
||||
>>> attrs = {'foo': 'a', 'bar': 1, 'key1': 'val1', 'key2': 'val2'}
|
||||
>>> my_item = my_table.new_item(attrs=attrs)
|
||||
>>> my_item
|
||||
{u'bar': 1, u'foo': 'a', 'key1': 'val1', 'key2': 'val2'}
|
||||
|
||||
The effect is the same.
|
||||
|
||||
.. note:
|
||||
The explicit parameters take priority over the values in
|
||||
the attrs dict. So, if you have a hash_key or range_key
|
||||
in the attrs dict and you also supply either or both using
|
||||
the explicit parameters, the values in the attrs will be
|
||||
ignored.
|
||||
|
||||
:type hash_key: int|long|float|str|unicode|Binary
|
||||
:param hash_key: The HashKey of the new item. The
|
||||
type of the value must match the type defined in the
|
||||
schema for the table.
|
||||
|
||||
:type range_key: int|long|float|str|unicode|Binary
|
||||
:param range_key: The optional RangeKey of the new item.
|
||||
The type of the value must match the type defined in the
|
||||
schema for the table.
|
||||
|
||||
:type attrs: dict
|
||||
:param attrs: A dictionary of key value pairs used to
|
||||
populate the new item.
|
||||
|
||||
:type item_class: Class
|
||||
:param item_class: Allows you to override the class used
|
||||
to generate the items. This should be a subclass of
|
||||
:class:`boto.dynamodb.item.Item`
|
||||
"""
|
||||
return item_class(self, hash_key, range_key, attrs)
|
||||
|
||||
def query(self, hash_key, *args, **kw):
|
||||
"""
|
||||
Perform a query on the table.
|
||||
|
||||
:type hash_key: int|long|float|str|unicode|Binary
|
||||
:param hash_key: The HashKey of the requested item. The
|
||||
type of the value must match the type defined in the
|
||||
schema for the table.
|
||||
|
||||
:type range_key_condition: :class:`boto.dynamodb.condition.Condition`
|
||||
:param range_key_condition: A Condition object.
|
||||
Condition object can be one of the following types:
|
||||
|
||||
EQ|LE|LT|GE|GT|BEGINS_WITH|BETWEEN
|
||||
|
||||
The only condition which expects or will accept two
|
||||
values is 'BETWEEN', otherwise a single value should
|
||||
be passed to the Condition constructor.
|
||||
|
||||
:type attributes_to_get: list
|
||||
:param attributes_to_get: A list of attribute names.
|
||||
If supplied, only the specified attribute names will
|
||||
be returned. Otherwise, all attributes will be returned.
|
||||
|
||||
:type request_limit: int
|
||||
:param request_limit: The maximum number of items to retrieve
|
||||
from Amazon DynamoDB on each request. You may want to set
|
||||
a specific request_limit based on the provisioned throughput
|
||||
of your table. The default behavior is to retrieve as many
|
||||
results as possible per request.
|
||||
|
||||
:type max_results: int
|
||||
:param max_results: The maximum number of results that will
|
||||
be retrieved from Amazon DynamoDB in total. For example,
|
||||
if you only wanted to see the first 100 results from the
|
||||
query, regardless of how many were actually available, you
|
||||
could set max_results to 100 and the generator returned
|
||||
from the query method will only yeild 100 results max.
|
||||
|
||||
:type consistent_read: bool
|
||||
:param consistent_read: If True, a consistent read
|
||||
request is issued. Otherwise, an eventually consistent
|
||||
request is issued.
|
||||
|
||||
:type scan_index_forward: bool
|
||||
:param scan_index_forward: Specified forward or backward
|
||||
traversal of the index. Default is forward (True).
|
||||
|
||||
:type exclusive_start_key: list or tuple
|
||||
:param exclusive_start_key: Primary key of the item from
|
||||
which to continue an earlier query. This would be
|
||||
provided as the LastEvaluatedKey in that query.
|
||||
|
||||
:type count: bool
|
||||
:param count: If True, Amazon DynamoDB returns a total
|
||||
number of items for the Query operation, even if the
|
||||
operation has no matching items for the assigned filter.
|
||||
If count is True, the actual items are not returned and
|
||||
the count is accessible as the ``count`` attribute of
|
||||
the returned object.
|
||||
|
||||
|
||||
:type item_class: Class
|
||||
:param item_class: Allows you to override the class used
|
||||
to generate the items. This should be a subclass of
|
||||
:class:`boto.dynamodb.item.Item`
|
||||
"""
|
||||
return self.layer2.query(self, hash_key, *args, **kw)
|
||||
|
||||
def scan(self, *args, **kw):
|
||||
"""
|
||||
Scan through this table, this is a very long
|
||||
and expensive operation, and should be avoided if
|
||||
at all possible.
|
||||
|
||||
:type scan_filter: A dict
|
||||
:param scan_filter: A dictionary where the key is the
|
||||
attribute name and the value is a
|
||||
:class:`boto.dynamodb.condition.Condition` object.
|
||||
Valid Condition objects include:
|
||||
|
||||
* EQ - equal (1)
|
||||
* NE - not equal (1)
|
||||
* LE - less than or equal (1)
|
||||
* LT - less than (1)
|
||||
* GE - greater than or equal (1)
|
||||
* GT - greater than (1)
|
||||
* NOT_NULL - attribute exists (0, use None)
|
||||
* NULL - attribute does not exist (0, use None)
|
||||
* CONTAINS - substring or value in list (1)
|
||||
* NOT_CONTAINS - absence of substring or value in list (1)
|
||||
* BEGINS_WITH - substring prefix (1)
|
||||
* IN - exact match in list (N)
|
||||
* BETWEEN - >= first value, <= second value (2)
|
||||
|
||||
:type attributes_to_get: list
|
||||
:param attributes_to_get: A list of attribute names.
|
||||
If supplied, only the specified attribute names will
|
||||
be returned. Otherwise, all attributes will be returned.
|
||||
|
||||
:type request_limit: int
|
||||
:param request_limit: The maximum number of items to retrieve
|
||||
from Amazon DynamoDB on each request. You may want to set
|
||||
a specific request_limit based on the provisioned throughput
|
||||
of your table. The default behavior is to retrieve as many
|
||||
results as possible per request.
|
||||
|
||||
:type max_results: int
|
||||
:param max_results: The maximum number of results that will
|
||||
be retrieved from Amazon DynamoDB in total. For example,
|
||||
if you only wanted to see the first 100 results from the
|
||||
query, regardless of how many were actually available, you
|
||||
could set max_results to 100 and the generator returned
|
||||
from the query method will only yeild 100 results max.
|
||||
|
||||
:type count: bool
|
||||
:param count: If True, Amazon DynamoDB returns a total
|
||||
number of items for the Scan operation, even if the
|
||||
operation has no matching items for the assigned filter.
|
||||
If count is True, the actual items are not returned and
|
||||
the count is accessible as the ``count`` attribute of
|
||||
the returned object.
|
||||
|
||||
:type exclusive_start_key: list or tuple
|
||||
:param exclusive_start_key: Primary key of the item from
|
||||
which to continue an earlier query. This would be
|
||||
provided as the LastEvaluatedKey in that query.
|
||||
|
||||
:type item_class: Class
|
||||
:param item_class: Allows you to override the class used
|
||||
to generate the items. This should be a subclass of
|
||||
:class:`boto.dynamodb.item.Item`
|
||||
|
||||
:return: A TableGenerator (generator) object which will iterate
|
||||
over all results
|
||||
:rtype: :class:`boto.dynamodb.layer2.TableGenerator`
|
||||
"""
|
||||
return self.layer2.scan(self, *args, **kw)
|
||||
|
||||
def batch_get_item(self, keys, attributes_to_get=None):
|
||||
"""
|
||||
Return a set of attributes for a multiple items from a single table
|
||||
using their primary keys. This abstraction removes the 100 Items per
|
||||
batch limitations as well as the "UnprocessedKeys" logic.
|
||||
|
||||
:type keys: list
|
||||
:param keys: A list of scalar or tuple values. Each element in the
|
||||
list represents one Item to retrieve. If the schema for the
|
||||
table has both a HashKey and a RangeKey, each element in the
|
||||
list should be a tuple consisting of (hash_key, range_key). If
|
||||
the schema for the table contains only a HashKey, each element
|
||||
in the list should be a scalar value of the appropriate type
|
||||
for the table schema. NOTE: The maximum number of items that
|
||||
can be retrieved for a single operation is 100. Also, the
|
||||
number of items retrieved is constrained by a 1 MB size limit.
|
||||
|
||||
:type attributes_to_get: list
|
||||
:param attributes_to_get: A list of attribute names.
|
||||
If supplied, only the specified attribute names will
|
||||
be returned. Otherwise, all attributes will be returned.
|
||||
|
||||
:return: A TableBatchGenerator (generator) object which will
|
||||
iterate over all results
|
||||
:rtype: :class:`boto.dynamodb.table.TableBatchGenerator`
|
||||
"""
|
||||
return TableBatchGenerator(self, keys, attributes_to_get)
|
|
@ -1,326 +0,0 @@
|
|||
# Copyright (c) 2011 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
"""
|
||||
Some utility functions to deal with mapping Amazon DynamoDB types to
|
||||
Python types and vice-versa.
|
||||
"""
|
||||
import base64
|
||||
from decimal import (Decimal, DecimalException, Context,
|
||||
Clamped, Overflow, Inexact, Underflow, Rounded)
|
||||
from exceptions import DynamoDBNumberError
|
||||
|
||||
|
||||
DYNAMODB_CONTEXT = Context(
|
||||
Emin=-128, Emax=126, rounding=None, prec=38,
|
||||
traps=[Clamped, Overflow, Inexact, Rounded, Underflow])
|
||||
|
||||
|
||||
# python2.6 cannot convert floats directly to
|
||||
# Decimals. This is taken from:
|
||||
# http://docs.python.org/release/2.6.7/library/decimal.html#decimal-faq
|
||||
def float_to_decimal(f):
|
||||
n, d = f.as_integer_ratio()
|
||||
numerator, denominator = Decimal(n), Decimal(d)
|
||||
ctx = DYNAMODB_CONTEXT
|
||||
result = ctx.divide(numerator, denominator)
|
||||
while ctx.flags[Inexact]:
|
||||
ctx.flags[Inexact] = False
|
||||
ctx.prec *= 2
|
||||
result = ctx.divide(numerator, denominator)
|
||||
return result
|
||||
|
||||
|
||||
def is_num(n):
|
||||
types = (int, long, float, bool, Decimal)
|
||||
return isinstance(n, types) or n in types
|
||||
|
||||
|
||||
def is_str(n):
|
||||
return isinstance(n, basestring) or (isinstance(n, type) and
|
||||
issubclass(n, basestring))
|
||||
|
||||
|
||||
def is_binary(n):
|
||||
return isinstance(n, Binary)
|
||||
|
||||
|
||||
def serialize_num(val):
|
||||
"""Cast a number to a string and perform
|
||||
validation to ensure no loss of precision.
|
||||
"""
|
||||
if isinstance(val, bool):
|
||||
return str(int(val))
|
||||
return str(val)
|
||||
|
||||
|
||||
def convert_num(s):
|
||||
if '.' in s:
|
||||
n = float(s)
|
||||
else:
|
||||
n = int(s)
|
||||
return n
|
||||
|
||||
|
||||
def convert_binary(n):
|
||||
return Binary(base64.b64decode(n))
|
||||
|
||||
|
||||
def get_dynamodb_type(val):
|
||||
"""
|
||||
Take a scalar Python value and return a string representing
|
||||
the corresponding Amazon DynamoDB type. If the value passed in is
|
||||
not a supported type, raise a TypeError.
|
||||
"""
|
||||
dynamodb_type = None
|
||||
if is_num(val):
|
||||
dynamodb_type = 'N'
|
||||
elif is_str(val):
|
||||
dynamodb_type = 'S'
|
||||
elif isinstance(val, (set, frozenset)):
|
||||
if False not in map(is_num, val):
|
||||
dynamodb_type = 'NS'
|
||||
elif False not in map(is_str, val):
|
||||
dynamodb_type = 'SS'
|
||||
elif False not in map(is_binary, val):
|
||||
dynamodb_type = 'BS'
|
||||
elif isinstance(val, Binary):
|
||||
dynamodb_type = 'B'
|
||||
if dynamodb_type is None:
|
||||
msg = 'Unsupported type "%s" for value "%s"' % (type(val), val)
|
||||
raise TypeError(msg)
|
||||
return dynamodb_type
|
||||
|
||||
|
||||
def dynamize_value(val):
|
||||
"""
|
||||
Take a scalar Python value and return a dict consisting
|
||||
of the Amazon DynamoDB type specification and the value that
|
||||
needs to be sent to Amazon DynamoDB. If the type of the value
|
||||
is not supported, raise a TypeError
|
||||
"""
|
||||
dynamodb_type = get_dynamodb_type(val)
|
||||
if dynamodb_type == 'N':
|
||||
val = {dynamodb_type: serialize_num(val)}
|
||||
elif dynamodb_type == 'S':
|
||||
val = {dynamodb_type: val}
|
||||
elif dynamodb_type == 'NS':
|
||||
val = {dynamodb_type: map(serialize_num, val)}
|
||||
elif dynamodb_type == 'SS':
|
||||
val = {dynamodb_type: [n for n in val]}
|
||||
elif dynamodb_type == 'B':
|
||||
val = {dynamodb_type: val.encode()}
|
||||
elif dynamodb_type == 'BS':
|
||||
val = {dynamodb_type: [n.encode() for n in val]}
|
||||
return val
|
||||
|
||||
|
||||
class Binary(object):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def encode(self):
|
||||
return base64.b64encode(self.value)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Binary):
|
||||
return self.value == other.value
|
||||
else:
|
||||
return self.value == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Binary(%s)' % self.value
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.value)
|
||||
|
||||
|
||||
def item_object_hook(dct):
|
||||
"""
|
||||
A custom object hook for use when decoding JSON item bodys.
|
||||
This hook will transform Amazon DynamoDB JSON responses to something
|
||||
that maps directly to native Python types.
|
||||
"""
|
||||
if len(dct.keys()) > 1:
|
||||
return dct
|
||||
if 'S' in dct:
|
||||
return dct['S']
|
||||
if 'N' in dct:
|
||||
return convert_num(dct['N'])
|
||||
if 'SS' in dct:
|
||||
return set(dct['SS'])
|
||||
if 'NS' in dct:
|
||||
return set(map(convert_num, dct['NS']))
|
||||
if 'B' in dct:
|
||||
return convert_binary(dct['B'])
|
||||
if 'BS' in dct:
|
||||
return set(map(convert_binary, dct['BS']))
|
||||
return dct
|
||||
|
||||
|
||||
class Dynamizer(object):
|
||||
"""Control serialization/deserialization of types.
|
||||
|
||||
This class controls the encoding of python types to the
|
||||
format that is expected by the DynamoDB API, as well as
|
||||
taking DynamoDB types and constructing the appropriate
|
||||
python types.
|
||||
|
||||
If you want to customize this process, you can subclass
|
||||
this class and override the encoding/decoding of
|
||||
specific types. For example::
|
||||
|
||||
'foo' (Python type)
|
||||
|
|
||||
v
|
||||
encode('foo')
|
||||
|
|
||||
v
|
||||
_encode_s('foo')
|
||||
|
|
||||
v
|
||||
{'S': 'foo'} (Encoding sent to/received from DynamoDB)
|
||||
|
|
||||
V
|
||||
decode({'S': 'foo'})
|
||||
|
|
||||
v
|
||||
_decode_s({'S': 'foo'})
|
||||
|
|
||||
v
|
||||
'foo' (Python type)
|
||||
|
||||
"""
|
||||
def _get_dynamodb_type(self, attr):
|
||||
return get_dynamodb_type(attr)
|
||||
|
||||
def encode(self, attr):
|
||||
"""
|
||||
Encodes a python type to the format expected
|
||||
by DynamoDB.
|
||||
|
||||
"""
|
||||
dynamodb_type = self._get_dynamodb_type(attr)
|
||||
try:
|
||||
encoder = getattr(self, '_encode_%s' % dynamodb_type.lower())
|
||||
except AttributeError:
|
||||
raise ValueError("Unable to encode dynamodb type: %s" %
|
||||
dynamodb_type)
|
||||
return {dynamodb_type: encoder(attr)}
|
||||
|
||||
def _encode_n(self, attr):
|
||||
try:
|
||||
if isinstance(attr, float) and not hasattr(Decimal, 'from_float'):
|
||||
# python2.6 does not support creating Decimals directly
|
||||
# from floats so we have to do this ourself.
|
||||
n = str(float_to_decimal(attr))
|
||||
else:
|
||||
n = str(DYNAMODB_CONTEXT.create_decimal(attr))
|
||||
if filter(lambda x: x in n, ('Infinity', 'NaN')):
|
||||
raise TypeError('Infinity and NaN not supported')
|
||||
return n
|
||||
except (TypeError, DecimalException), e:
|
||||
msg = '{0} numeric for `{1}`\n{2}'.format(
|
||||
e.__class__.__name__, attr, str(e) or '')
|
||||
raise DynamoDBNumberError(msg)
|
||||
|
||||
def _encode_s(self, attr):
|
||||
if isinstance(attr, unicode):
|
||||
attr = attr.encode('utf-8')
|
||||
elif not isinstance(attr, str):
|
||||
attr = str(attr)
|
||||
return attr
|
||||
|
||||
def _encode_ns(self, attr):
|
||||
return map(self._encode_n, attr)
|
||||
|
||||
def _encode_ss(self, attr):
|
||||
return [self._encode_s(n) for n in attr]
|
||||
|
||||
def _encode_b(self, attr):
|
||||
return attr.encode()
|
||||
|
||||
def _encode_bs(self, attr):
|
||||
return [self._encode_b(n) for n in attr]
|
||||
|
||||
def decode(self, attr):
|
||||
"""
|
||||
Takes the format returned by DynamoDB and constructs
|
||||
the appropriate python type.
|
||||
|
||||
"""
|
||||
if len(attr) > 1 or not attr:
|
||||
return attr
|
||||
dynamodb_type = attr.keys()[0]
|
||||
try:
|
||||
decoder = getattr(self, '_decode_%s' % dynamodb_type.lower())
|
||||
except AttributeError:
|
||||
return attr
|
||||
return decoder(attr[dynamodb_type])
|
||||
|
||||
def _decode_n(self, attr):
|
||||
return DYNAMODB_CONTEXT.create_decimal(attr)
|
||||
|
||||
def _decode_s(self, attr):
|
||||
return attr
|
||||
|
||||
def _decode_ns(self, attr):
|
||||
return set(map(self._decode_n, attr))
|
||||
|
||||
def _decode_ss(self, attr):
|
||||
return set(map(self._decode_s, attr))
|
||||
|
||||
def _decode_b(self, attr):
|
||||
return convert_binary(attr)
|
||||
|
||||
def _decode_bs(self, attr):
|
||||
return set(map(self._decode_b, attr))
|
||||
|
||||
|
||||
class LossyFloatDynamizer(Dynamizer):
|
||||
"""Use float/int instead of Decimal for numeric types.
|
||||
|
||||
This class is provided for backwards compatibility. Instead of
|
||||
using Decimals for the 'N', 'NS' types it uses ints/floats.
|
||||
|
||||
This class is deprecated and its usage is not encouraged,
|
||||
as doing so may result in loss of precision. Use the
|
||||
`Dynamizer` class instead.
|
||||
|
||||
"""
|
||||
def _encode_n(self, attr):
|
||||
return serialize_num(attr)
|
||||
|
||||
def _encode_ns(self, attr):
|
||||
return [str(i) for i in attr]
|
||||
|
||||
def _decode_n(self, attr):
|
||||
return convert_num(attr)
|
||||
|
||||
def _decode_ns(self, attr):
|
||||
return set(map(self._decode_n, attr))
|
|
@ -1,69 +0,0 @@
|
|||
# Copyright (c) 2011 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
from boto.regioninfo import RegionInfo
|
||||
|
||||
|
||||
def regions():
|
||||
"""
|
||||
Get all available regions for the Amazon DynamoDB service.
|
||||
|
||||
:rtype: list
|
||||
:return: A list of :class:`boto.regioninfo.RegionInfo`
|
||||
"""
|
||||
from boto.dynamodb2.layer1 import DynamoDBConnection
|
||||
return [RegionInfo(name='us-east-1',
|
||||
endpoint='dynamodb.us-east-1.amazonaws.com',
|
||||
connection_cls=DynamoDBConnection),
|
||||
RegionInfo(name='us-gov-west-1',
|
||||
endpoint='dynamodb.us-gov-west-1.amazonaws.com',
|
||||
connection_cls=DynamoDBConnection),
|
||||
RegionInfo(name='us-west-1',
|
||||
endpoint='dynamodb.us-west-1.amazonaws.com',
|
||||
connection_cls=DynamoDBConnection),
|
||||
RegionInfo(name='us-west-2',
|
||||
endpoint='dynamodb.us-west-2.amazonaws.com',
|
||||
connection_cls=DynamoDBConnection),
|
||||
RegionInfo(name='eu-west-1',
|
||||
endpoint='dynamodb.eu-west-1.amazonaws.com',
|
||||
connection_cls=DynamoDBConnection),
|
||||
RegionInfo(name='ap-northeast-1',
|
||||
endpoint='dynamodb.ap-northeast-1.amazonaws.com',
|
||||
connection_cls=DynamoDBConnection),
|
||||
RegionInfo(name='ap-southeast-1',
|
||||
endpoint='dynamodb.ap-southeast-1.amazonaws.com',
|
||||
connection_cls=DynamoDBConnection),
|
||||
RegionInfo(name='ap-southeast-2',
|
||||
endpoint='dynamodb.ap-southeast-2.amazonaws.com',
|
||||
connection_cls=DynamoDBConnection),
|
||||
RegionInfo(name='sa-east-1',
|
||||
endpoint='dynamodb.sa-east-1.amazonaws.com',
|
||||
connection_cls=DynamoDBConnection),
|
||||
]
|
||||
|
||||
|
||||
def connect_to_region(region_name, **kw_params):
|
||||
for region in regions():
|
||||
if region.name == region_name:
|
||||
return region.connect(**kw_params)
|
||||
return None
|
|
@ -1,74 +0,0 @@
|
|||
# Copyright (c) 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
from boto.exception import JSONResponseError
|
||||
|
||||
|
||||
class ProvisionedThroughputExceededException(JSONResponseError):
|
||||
pass
|
||||
|
||||
|
||||
class LimitExceededException(JSONResponseError):
|
||||
pass
|
||||
|
||||
|
||||
class ConditionalCheckFailedException(JSONResponseError):
|
||||
pass
|
||||
|
||||
|
||||
class ResourceInUseException(JSONResponseError):
|
||||
pass
|
||||
|
||||
|
||||
class ResourceNotFoundException(JSONResponseError):
|
||||
pass
|
||||
|
||||
|
||||
class InternalServerError(JSONResponseError):
|
||||
pass
|
||||
|
||||
|
||||
class ValidationException(JSONResponseError):
|
||||
pass
|
||||
|
||||
|
||||
class ItemCollectionSizeLimitExceededException(JSONResponseError):
|
||||
pass
|
||||
|
||||
|
||||
class DynamoDBError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownSchemaFieldError(DynamoDBError):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownIndexFieldError(DynamoDBError):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownFilterTypeError(DynamoDBError):
|
||||
pass
|
||||
|
||||
|
||||
class QueryError(DynamoDBError):
|
||||
pass
|
|
@ -1,212 +0,0 @@
|
|||
from boto.dynamodb2.types import STRING
|
||||
|
||||
|
||||
class BaseSchemaField(object):
|
||||
"""
|
||||
An abstract class for defining schema fields.
|
||||
|
||||
Contains most of the core functionality for the field. Subclasses must
|
||||
define an ``attr_type`` to pass to DynamoDB.
|
||||
"""
|
||||
attr_type = None
|
||||
|
||||
def __init__(self, name, data_type=STRING):
|
||||
"""
|
||||
Creates a Python schema field, to represent the data to pass to
|
||||
DynamoDB.
|
||||
|
||||
Requires a ``name`` parameter, which should be a string name of the
|
||||
field.
|
||||
|
||||
Optionally accepts a ``data_type`` parameter, which should be a
|
||||
constant from ``boto.dynamodb2.types``. (Default: ``STRING``)
|
||||
"""
|
||||
self.name = name
|
||||
self.data_type = data_type
|
||||
|
||||
def definition(self):
|
||||
"""
|
||||
Returns the attribute definition structure DynamoDB expects.
|
||||
|
||||
Example::
|
||||
|
||||
>>> field.definition()
|
||||
{
|
||||
'AttributeName': 'username',
|
||||
'AttributeType': 'S',
|
||||
}
|
||||
|
||||
"""
|
||||
return {
|
||||
'AttributeName': self.name,
|
||||
'AttributeType': self.data_type,
|
||||
}
|
||||
|
||||
def schema(self):
|
||||
"""
|
||||
Returns the schema structure DynamoDB expects.
|
||||
|
||||
Example::
|
||||
|
||||
>>> field.schema()
|
||||
{
|
||||
'AttributeName': 'username',
|
||||
'KeyType': 'HASH',
|
||||
}
|
||||
|
||||
"""
|
||||
return {
|
||||
'AttributeName': self.name,
|
||||
'KeyType': self.attr_type,
|
||||
}
|
||||
|
||||
|
||||
class HashKey(BaseSchemaField):
|
||||
"""
|
||||
An field representing a hash key.
|
||||
|
||||
Example::
|
||||
|
||||
>>> from boto.dynamodb2.types import NUMBER
|
||||
>>> HashKey('username')
|
||||
>>> HashKey('date_joined', data_type=NUMBER)
|
||||
|
||||
"""
|
||||
attr_type = 'HASH'
|
||||
|
||||
|
||||
class RangeKey(BaseSchemaField):
|
||||
"""
|
||||
An field representing a range key.
|
||||
|
||||
Example::
|
||||
|
||||
>>> from boto.dynamodb2.types import NUMBER
|
||||
>>> HashKey('username')
|
||||
>>> HashKey('date_joined', data_type=NUMBER)
|
||||
|
||||
"""
|
||||
attr_type = 'RANGE'
|
||||
|
||||
|
||||
class BaseIndexField(object):
|
||||
"""
|
||||
An abstract class for defining schema fields.
|
||||
|
||||
Contains most of the core functionality for the field. Subclasses must
|
||||
define an ``attr_type`` to pass to DynamoDB.
|
||||
"""
|
||||
def __init__(self, name, parts):
|
||||
self.name = name
|
||||
self.parts = parts
|
||||
|
||||
def definition(self):
|
||||
"""
|
||||
Returns the attribute definition structure DynamoDB expects.
|
||||
|
||||
Example::
|
||||
|
||||
>>> index.definition()
|
||||
{
|
||||
'AttributeName': 'username',
|
||||
'AttributeType': 'S',
|
||||
}
|
||||
|
||||
"""
|
||||
definition = []
|
||||
|
||||
for part in self.parts:
|
||||
definition.append({
|
||||
'AttributeName': part.name,
|
||||
'AttributeType': part.data_type,
|
||||
})
|
||||
|
||||
return definition
|
||||
|
||||
def schema(self):
|
||||
"""
|
||||
Returns the schema structure DynamoDB expects.
|
||||
|
||||
Example::
|
||||
|
||||
>>> index.schema()
|
||||
{
|
||||
'IndexName': 'LastNameIndex',
|
||||
'KeySchema': [
|
||||
{
|
||||
'AttributeName': 'username',
|
||||
'KeyType': 'HASH',
|
||||
},
|
||||
],
|
||||
'Projection': {
|
||||
'ProjectionType': 'KEYS_ONLY,
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
key_schema = []
|
||||
|
||||
for part in self.parts:
|
||||
key_schema.append(part.schema())
|
||||
|
||||
return {
|
||||
'IndexName': self.name,
|
||||
'KeySchema': key_schema,
|
||||
'Projection': {
|
||||
'ProjectionType': self.projection_type,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AllIndex(BaseIndexField):
|
||||
"""
|
||||
An index signifying all fields should be in the index.
|
||||
|
||||
Example::
|
||||
|
||||
>>> AllIndex('MostRecentlyJoined', parts=[
|
||||
... HashKey('username'),
|
||||
... RangeKey('date_joined')
|
||||
... ])
|
||||
|
||||
"""
|
||||
projection_type = 'ALL'
|
||||
|
||||
|
||||
class KeysOnlyIndex(BaseIndexField):
|
||||
"""
|
||||
An index signifying only key fields should be in the index.
|
||||
|
||||
Example::
|
||||
|
||||
>>> KeysOnlyIndex('MostRecentlyJoined', parts=[
|
||||
... HashKey('username'),
|
||||
... RangeKey('date_joined')
|
||||
... ])
|
||||
|
||||
"""
|
||||
projection_type = 'KEYS_ONLY'
|
||||
|
||||
|
||||
class IncludeIndex(BaseIndexField):
|
||||
"""
|
||||
An index signifying only certain fields should be in the index.
|
||||
|
||||
Example::
|
||||
|
||||
>>> IncludeIndex('GenderIndex', parts=[
|
||||
... HashKey('username'),
|
||||
... RangeKey('date_joined')
|
||||
... ], includes=['gender'])
|
||||
|
||||
"""
|
||||
projection_type = 'INCLUDE'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.includes_fields = kwargs.pop('includes', [])
|
||||
super(IncludeIndex, self).__init__(*args, **kwargs)
|
||||
|
||||
def schema(self):
|
||||
schema_data = super(IncludeIndex, self).schema()
|
||||
schema_data['Projection']['NonKeyAttributes'] = self.includes_fields
|
||||
return schema_data
|
|
@ -1,464 +0,0 @@
|
|||
from copy import deepcopy
|
||||
|
||||
from boto.dynamodb2.types import Dynamizer
|
||||
|
||||
|
||||
class NEWVALUE(object):
|
||||
# A marker for new data added.
|
||||
pass
|
||||
|
||||
|
||||
class Item(object):
|
||||
"""
|
||||
An object representing the item data within a DynamoDB table.
|
||||
|
||||
An item is largely schema-free, meaning it can contain any data. The only
|
||||
limitation is that it must have data for the fields in the ``Table``'s
|
||||
schema.
|
||||
|
||||
This object presents a dictionary-like interface for accessing/storing
|
||||
data. It also tries to intelligently track how data has changed throughout
|
||||
the life of the instance, to be as efficient as possible about updates.
|
||||
"""
|
||||
def __init__(self, table, data=None, loaded=False):
|
||||
"""
|
||||
Constructs an (unsaved) ``Item`` instance.
|
||||
|
||||
To persist the data in DynamoDB, you'll need to call the ``Item.save``
|
||||
(or ``Item.partial_save``) on the instance.
|
||||
|
||||
Requires a ``table`` parameter, which should be a ``Table`` instance.
|
||||
This is required, as DynamoDB's API is focus around all operations
|
||||
being table-level. It's also for persisting schema around many objects.
|
||||
|
||||
Optionally accepts a ``data`` parameter, which should be a dictionary
|
||||
of the fields & values of the item.
|
||||
|
||||
Optionally accepts a ``loaded`` parameter, which should be a boolean.
|
||||
``True`` if it was preexisting data loaded from DynamoDB, ``False`` if
|
||||
it's new data from the user. Default is ``False``.
|
||||
|
||||
Example::
|
||||
|
||||
>>> users = Table('users')
|
||||
>>> user = Item(users, data={
|
||||
... 'username': 'johndoe',
|
||||
... 'first_name': 'John',
|
||||
... 'date_joined': 1248o61592,
|
||||
... })
|
||||
|
||||
# Change existing data.
|
||||
>>> user['first_name'] = 'Johann'
|
||||
# Add more data.
|
||||
>>> user['last_name'] = 'Doe'
|
||||
# Delete data.
|
||||
>>> del user['date_joined']
|
||||
|
||||
# Iterate over all the data.
|
||||
>>> for field, val in user.items():
|
||||
... print "%s: %s" % (field, val)
|
||||
username: johndoe
|
||||
first_name: John
|
||||
date_joined: 1248o61592
|
||||
|
||||
"""
|
||||
self.table = table
|
||||
self._loaded = loaded
|
||||
self._orig_data = {}
|
||||
self._data = data
|
||||
self._dynamizer = Dynamizer()
|
||||
|
||||
if self._data is None:
|
||||
self._data = {}
|
||||
|
||||
if self._loaded:
|
||||
self._orig_data = deepcopy(self._data)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._data.get(key, None)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._data[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
if not key in self._data:
|
||||
return
|
||||
|
||||
del self._data[key]
|
||||
|
||||
def keys(self):
|
||||
return self._data.keys()
|
||||
|
||||
def values(self):
|
||||
return self._data.values()
|
||||
|
||||
def items(self):
|
||||
return self._data.items()
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self._data.get(key, default)
|
||||
|
||||
def __iter__(self):
|
||||
for key in self._data:
|
||||
yield self._data[key]
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._data
|
||||
|
||||
def _determine_alterations(self):
|
||||
"""
|
||||
Checks the ``-orig_data`` against the ``_data`` to determine what
|
||||
changes to the data are present.
|
||||
|
||||
Returns a dictionary containing the keys ``adds``, ``changes`` &
|
||||
``deletes``, containing the updated data.
|
||||
"""
|
||||
alterations = {
|
||||
'adds': {},
|
||||
'changes': {},
|
||||
'deletes': [],
|
||||
}
|
||||
|
||||
orig_keys = set(self._orig_data.keys())
|
||||
data_keys = set(self._data.keys())
|
||||
|
||||
# Run through keys we know are in both for changes.
|
||||
for key in orig_keys.intersection(data_keys):
|
||||
if self._data[key] != self._orig_data[key]:
|
||||
if self._is_storable(self._data[key]):
|
||||
alterations['changes'][key] = self._data[key]
|
||||
else:
|
||||
alterations['deletes'].append(key)
|
||||
|
||||
# Run through additions.
|
||||
for key in data_keys.difference(orig_keys):
|
||||
if self._is_storable(self._data[key]):
|
||||
alterations['adds'][key] = self._data[key]
|
||||
|
||||
# Run through deletions.
|
||||
for key in orig_keys.difference(data_keys):
|
||||
alterations['deletes'].append(key)
|
||||
|
||||
return alterations
|
||||
|
||||
def needs_save(self, data=None):
|
||||
"""
|
||||
Returns whether or not the data has changed on the ``Item``.
|
||||
|
||||
Optionally accepts a ``data`` argument, which accepts the output from
|
||||
``self._determine_alterations()`` if you've already called it. Typically
|
||||
unnecessary to do. Default is ``None``.
|
||||
|
||||
Example:
|
||||
|
||||
>>> user.needs_save()
|
||||
False
|
||||
>>> user['first_name'] = 'Johann'
|
||||
>>> user.needs_save()
|
||||
True
|
||||
|
||||
"""
|
||||
if data is None:
|
||||
data = self._determine_alterations()
|
||||
|
||||
needs_save = False
|
||||
|
||||
for kind in ['adds', 'changes', 'deletes']:
|
||||
if len(data[kind]):
|
||||
needs_save = True
|
||||
break
|
||||
|
||||
return needs_save
|
||||
|
||||
def mark_clean(self):
|
||||
"""
|
||||
Marks an ``Item`` instance as no longer needing to be saved.
|
||||
|
||||
Example:
|
||||
|
||||
>>> user.needs_save()
|
||||
False
|
||||
>>> user['first_name'] = 'Johann'
|
||||
>>> user.needs_save()
|
||||
True
|
||||
>>> user.mark_clean()
|
||||
>>> user.needs_save()
|
||||
False
|
||||
|
||||
"""
|
||||
self._orig_data = deepcopy(self._data)
|
||||
|
||||
def mark_dirty(self):
|
||||
"""
|
||||
DEPRECATED: Marks an ``Item`` instance as needing to be saved.
|
||||
|
||||
This method is no longer necessary, as the state tracking on ``Item``
|
||||
has been improved to automatically detect proper state.
|
||||
"""
|
||||
return
|
||||
|
||||
def load(self, data):
|
||||
"""
|
||||
This is only useful when being handed raw data from DynamoDB directly.
|
||||
If you have a Python datastructure already, use the ``__init__`` or
|
||||
manually set the data instead.
|
||||
|
||||
Largely internal, unless you know what you're doing or are trying to
|
||||
mix the low-level & high-level APIs.
|
||||
"""
|
||||
self._data = {}
|
||||
|
||||
for field_name, field_value in data.get('Item', {}).items():
|
||||
self[field_name] = self._dynamizer.decode(field_value)
|
||||
|
||||
self._loaded = True
|
||||
self._orig_data = deepcopy(self._data)
|
||||
|
||||
def get_keys(self):
|
||||
"""
|
||||
Returns a Python-style dict of the keys/values.
|
||||
|
||||
Largely internal.
|
||||
"""
|
||||
key_fields = self.table.get_key_fields()
|
||||
key_data = {}
|
||||
|
||||
for key in key_fields:
|
||||
key_data[key] = self[key]
|
||||
|
||||
return key_data
|
||||
|
||||
def get_raw_keys(self):
|
||||
"""
|
||||
Returns a DynamoDB-style dict of the keys/values.
|
||||
|
||||
Largely internal.
|
||||
"""
|
||||
raw_key_data = {}
|
||||
|
||||
for key, value in self.get_keys().items():
|
||||
raw_key_data[key] = self._dynamizer.encode(value)
|
||||
|
||||
return raw_key_data
|
||||
|
||||
def build_expects(self, fields=None):
|
||||
"""
|
||||
Builds up a list of expecations to hand off to DynamoDB on save.
|
||||
|
||||
Largely internal.
|
||||
"""
|
||||
expects = {}
|
||||
|
||||
if fields is None:
|
||||
fields = self._data.keys() + self._orig_data.keys()
|
||||
|
||||
# Only uniques.
|
||||
fields = set(fields)
|
||||
|
||||
for key in fields:
|
||||
expects[key] = {
|
||||
'Exists': True,
|
||||
}
|
||||
value = None
|
||||
|
||||
# Check for invalid keys.
|
||||
if not key in self._orig_data and not key in self._data:
|
||||
raise ValueError("Unknown key %s provided." % key)
|
||||
|
||||
# States:
|
||||
# * New field (only in _data)
|
||||
# * Unchanged field (in both _data & _orig_data, same data)
|
||||
# * Modified field (in both _data & _orig_data, different data)
|
||||
# * Deleted field (only in _orig_data)
|
||||
orig_value = self._orig_data.get(key, NEWVALUE)
|
||||
current_value = self._data.get(key, NEWVALUE)
|
||||
|
||||
if orig_value == current_value:
|
||||
# Existing field unchanged.
|
||||
value = current_value
|
||||
else:
|
||||
if key in self._data:
|
||||
if not key in self._orig_data:
|
||||
# New field.
|
||||
expects[key]['Exists'] = False
|
||||
else:
|
||||
# Existing field modified.
|
||||
value = orig_value
|
||||
else:
|
||||
# Existing field deleted.
|
||||
value = orig_value
|
||||
|
||||
if value is not None:
|
||||
expects[key]['Value'] = self._dynamizer.encode(value)
|
||||
|
||||
return expects
|
||||
|
||||
def _is_storable(self, value):
|
||||
# We need to prevent ``None``, empty string & empty set from
|
||||
# heading to DDB, but allow false-y values like 0 & False make it.
|
||||
if not value:
|
||||
if not value in (0, 0.0, False):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def prepare_full(self):
|
||||
"""
|
||||
Runs through all fields & encodes them to be handed off to DynamoDB
|
||||
as part of an ``save`` (``put_item``) call.
|
||||
|
||||
Largely internal.
|
||||
"""
|
||||
# This doesn't save on it's own. Rather, we prepare the datastructure
|
||||
# and hand-off to the table to handle creation/update.
|
||||
final_data = {}
|
||||
|
||||
for key, value in self._data.items():
|
||||
if not self._is_storable(value):
|
||||
continue
|
||||
|
||||
final_data[key] = self._dynamizer.encode(value)
|
||||
|
||||
return final_data
|
||||
|
||||
def prepare_partial(self):
|
||||
"""
|
||||
Runs through **ONLY** the changed/deleted fields & encodes them to be
|
||||
handed off to DynamoDB as part of an ``partial_save`` (``update_item``)
|
||||
call.
|
||||
|
||||
Largely internal.
|
||||
"""
|
||||
# This doesn't save on it's own. Rather, we prepare the datastructure
|
||||
# and hand-off to the table to handle creation/update.
|
||||
final_data = {}
|
||||
fields = set()
|
||||
alterations = self._determine_alterations()
|
||||
|
||||
for key, value in alterations['adds'].items():
|
||||
final_data[key] = {
|
||||
'Action': 'PUT',
|
||||
'Value': self._dynamizer.encode(self._data[key])
|
||||
}
|
||||
fields.add(key)
|
||||
|
||||
for key, value in alterations['changes'].items():
|
||||
final_data[key] = {
|
||||
'Action': 'PUT',
|
||||
'Value': self._dynamizer.encode(self._data[key])
|
||||
}
|
||||
fields.add(key)
|
||||
|
||||
for key in alterations['deletes']:
|
||||
final_data[key] = {
|
||||
'Action': 'DELETE',
|
||||
}
|
||||
fields.add(key)
|
||||
|
||||
return final_data, fields
|
||||
|
||||
def partial_save(self):
|
||||
"""
|
||||
Saves only the changed data to DynamoDB.
|
||||
|
||||
Extremely useful for high-volume/high-write data sets, this allows
|
||||
you to update only a handful of fields rather than having to push
|
||||
entire items. This prevents many accidental overwrite situations as
|
||||
well as saves on the amount of data to transfer over the wire.
|
||||
|
||||
Returns ``True`` on success, ``False`` if no save was performed or
|
||||
the write failed.
|
||||
|
||||
Example::
|
||||
|
||||
>>> user['last_name'] = 'Doh!'
|
||||
# Only the last name field will be sent to DynamoDB.
|
||||
>>> user.partial_save()
|
||||
|
||||
"""
|
||||
key = self.get_keys()
|
||||
# Build a new dict of only the data we're changing.
|
||||
final_data, fields = self.prepare_partial()
|
||||
|
||||
if not final_data:
|
||||
return False
|
||||
|
||||
# Remove the key(s) from the ``final_data`` if present.
|
||||
# They should only be present if this is a new item, in which
|
||||
# case we shouldn't be sending as part of the data to update.
|
||||
for fieldname, value in key.items():
|
||||
if fieldname in final_data:
|
||||
del final_data[fieldname]
|
||||
|
||||
try:
|
||||
# It's likely also in ``fields``, so remove it there too.
|
||||
fields.remove(fieldname)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# Build expectations of only the fields we're planning to update.
|
||||
expects = self.build_expects(fields=fields)
|
||||
returned = self.table._update_item(key, final_data, expects=expects)
|
||||
# Mark the object as clean.
|
||||
self.mark_clean()
|
||||
return returned
|
||||
|
||||
def save(self, overwrite=False):
|
||||
"""
|
||||
Saves all data to DynamoDB.
|
||||
|
||||
By default, this attempts to ensure that none of the underlying
|
||||
data has changed. If any fields have changed in between when the
|
||||
``Item`` was constructed & when it is saved, this call will fail so
|
||||
as not to cause any data loss.
|
||||
|
||||
If you're sure possibly overwriting data is acceptable, you can pass
|
||||
an ``overwrite=True``. If that's not acceptable, you may be able to use
|
||||
``Item.partial_save`` to only write the changed field data.
|
||||
|
||||
Optionally accepts an ``overwrite`` parameter, which should be a
|
||||
boolean. If you provide ``True``, the item will be forcibly overwritten
|
||||
within DynamoDB, even if another process changed the data in the
|
||||
meantime. (Default: ``False``)
|
||||
|
||||
Returns ``True`` on success, ``False`` if no save was performed.
|
||||
|
||||
Example::
|
||||
|
||||
>>> user['last_name'] = 'Doh!'
|
||||
# All data on the Item is sent to DynamoDB.
|
||||
>>> user.save()
|
||||
|
||||
# If it fails, you can overwrite.
|
||||
>>> user.save(overwrite=True)
|
||||
|
||||
"""
|
||||
if not self.needs_save() and not overwrite:
|
||||
return False
|
||||
|
||||
final_data = self.prepare_full()
|
||||
expects = None
|
||||
|
||||
if overwrite is False:
|
||||
# Build expectations about *all* of the data.
|
||||
expects = self.build_expects()
|
||||
|
||||
returned = self.table._put_item(final_data, expects=expects)
|
||||
# Mark the object as clean.
|
||||
self.mark_clean()
|
||||
return returned
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
Deletes the item's data to DynamoDB.
|
||||
|
||||
Returns ``True`` on success.
|
||||
|
||||
Example::
|
||||
|
||||
# Buh-bye now.
|
||||
>>> user.delete()
|
||||
|
||||
"""
|
||||
key_data = self.get_keys()
|
||||
return self.table.delete_item(**key_data)
|
File diff suppressed because it is too large
Load Diff
|
@ -1,168 +0,0 @@
|
|||
class ResultSet(object):
|
||||
"""
|
||||
A class used to lazily handle page-to-page navigation through a set of
|
||||
results.
|
||||
|
||||
It presents a transparent iterator interface, so that all the user has
|
||||
to do is use it in a typical ``for`` loop (or list comprehension, etc.)
|
||||
to fetch results, even if they weren't present in the current page of
|
||||
results.
|
||||
|
||||
This is used by the ``Table.query`` & ``Table.scan`` methods.
|
||||
|
||||
Example::
|
||||
|
||||
>>> users = Table('users')
|
||||
>>> results = ResultSet()
|
||||
>>> results.to_call(users.query, username__gte='johndoe')
|
||||
# Now iterate. When it runs out of results, it'll fetch the next page.
|
||||
>>> for res in results:
|
||||
... print res['username']
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
super(ResultSet, self).__init__()
|
||||
self.the_callable = None
|
||||
self.call_args = []
|
||||
self.call_kwargs = {}
|
||||
self._results = []
|
||||
self._offset = -1
|
||||
self._results_left = True
|
||||
self._last_key_seen = None
|
||||
|
||||
@property
|
||||
def first_key(self):
|
||||
return 'exclusive_start_key'
|
||||
|
||||
def _reset(self):
|
||||
"""
|
||||
Resets the internal state of the ``ResultSet``.
|
||||
|
||||
This prevents results from being cached long-term & consuming
|
||||
excess memory.
|
||||
|
||||
Largely internal.
|
||||
"""
|
||||
self._results = []
|
||||
self._offset = 0
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
self._offset += 1
|
||||
|
||||
if self._offset >= len(self._results):
|
||||
if self._results_left is False:
|
||||
raise StopIteration()
|
||||
|
||||
self.fetch_more()
|
||||
|
||||
# It's possible that previous call to ``fetch_more`` may not return
|
||||
# anything useful but there may be more results. Loop until we get
|
||||
# something back, making sure we guard for no results left.
|
||||
while not len(self._results) and self._results_left:
|
||||
self.fetch_more()
|
||||
|
||||
if self._offset < len(self._results):
|
||||
return self._results[self._offset]
|
||||
else:
|
||||
raise StopIteration()
|
||||
|
||||
def to_call(self, the_callable, *args, **kwargs):
|
||||
"""
|
||||
Sets up the callable & any arguments to run it with.
|
||||
|
||||
This is stored for subsequent calls so that those queries can be
|
||||
run without requiring user intervention.
|
||||
|
||||
Example::
|
||||
|
||||
# Just an example callable.
|
||||
>>> def squares_to(y):
|
||||
... for x in range(1, y):
|
||||
... yield x**2
|
||||
>>> rs = ResultSet()
|
||||
# Set up what to call & arguments.
|
||||
>>> rs.to_call(squares_to, y=3)
|
||||
|
||||
"""
|
||||
if not callable(the_callable):
|
||||
raise ValueError(
|
||||
'You must supply an object or function to be called.'
|
||||
)
|
||||
|
||||
self.the_callable = the_callable
|
||||
self.call_args = args
|
||||
self.call_kwargs = kwargs
|
||||
|
||||
def fetch_more(self):
|
||||
"""
|
||||
When the iterator runs out of results, this method is run to re-execute
|
||||
the callable (& arguments) to fetch the next page.
|
||||
|
||||
Largely internal.
|
||||
"""
|
||||
self._reset()
|
||||
|
||||
args = self.call_args[:]
|
||||
kwargs = self.call_kwargs.copy()
|
||||
|
||||
if self._last_key_seen is not None:
|
||||
kwargs[self.first_key] = self._last_key_seen
|
||||
|
||||
results = self.the_callable(*args, **kwargs)
|
||||
new_results = results.get('results', [])
|
||||
self._last_key_seen = results.get('last_key', None)
|
||||
|
||||
if len(new_results):
|
||||
self._results.extend(results['results'])
|
||||
|
||||
# Decrease the limit, if it's present.
|
||||
if self.call_kwargs.get('limit'):
|
||||
self.call_kwargs['limit'] -= len(results['results'])
|
||||
# and if limit hits zero, we don't have any more
|
||||
# results to look for
|
||||
if 0 == self.call_kwargs['limit']:
|
||||
self._results_left = False
|
||||
|
||||
if self._last_key_seen is None:
|
||||
self._results_left = False
|
||||
|
||||
|
||||
class BatchGetResultSet(ResultSet):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._keys_left = kwargs.pop('keys', [])
|
||||
self._max_batch_get = kwargs.pop('max_batch_get', 100)
|
||||
super(BatchGetResultSet, self).__init__(*args, **kwargs)
|
||||
|
||||
def fetch_more(self):
|
||||
self._reset()
|
||||
|
||||
args = self.call_args[:]
|
||||
kwargs = self.call_kwargs.copy()
|
||||
|
||||
# Slice off the max we can fetch.
|
||||
kwargs['keys'] = self._keys_left[:self._max_batch_get]
|
||||
self._keys_left = self._keys_left[self._max_batch_get:]
|
||||
|
||||
results = self.the_callable(*args, **kwargs)
|
||||
|
||||
if not len(results.get('results', [])):
|
||||
self._results_left = False
|
||||
return
|
||||
|
||||
self._results.extend(results['results'])
|
||||
|
||||
for offset, key_data in enumerate(results.get('unprocessed_keys', [])):
|
||||
# We've got an unprocessed key. Reinsert it into the list.
|
||||
# DynamoDB only returns valid keys, so there should be no risk of
|
||||
# missing keys ever making it here.
|
||||
self._keys_left.insert(offset, key_data)
|
||||
|
||||
if len(self._keys_left) <= 0:
|
||||
self._results_left = False
|
||||
|
||||
# Decrease the limit, if it's present.
|
||||
if self.call_kwargs.get('limit'):
|
||||
self.call_kwargs['limit'] -= len(results['results'])
|
File diff suppressed because it is too large
Load Diff
|
@ -1,40 +0,0 @@
|
|||
# Shadow the DynamoDB v1 bits.
|
||||
# This way, no end user should have to cross-import between versions & we
|
||||
# reserve the namespace to extend v2 if it's ever needed.
|
||||
from boto.dynamodb.types import Dynamizer
|
||||
|
||||
|
||||
# Some constants for our use.
|
||||
STRING = 'S'
|
||||
NUMBER = 'N'
|
||||
BINARY = 'B'
|
||||
STRING_SET = 'SS'
|
||||
NUMBER_SET = 'NS'
|
||||
BINARY_SET = 'BS'
|
||||
|
||||
QUERY_OPERATORS = {
|
||||
'eq': 'EQ',
|
||||
'lte': 'LE',
|
||||
'lt': 'LT',
|
||||
'gte': 'GE',
|
||||
'gt': 'GT',
|
||||
'beginswith': 'BEGINS_WITH',
|
||||
'between': 'BETWEEN',
|
||||
}
|
||||
|
||||
FILTER_OPERATORS = {
|
||||
'eq': 'EQ',
|
||||
'ne': 'NE',
|
||||
'lte': 'LE',
|
||||
'lt': 'LT',
|
||||
'gte': 'GE',
|
||||
'gt': 'GT',
|
||||
# FIXME: Is this necessary? i.e. ``whatever__null=False``
|
||||
'nnull': 'NOT_NULL',
|
||||
'null': 'NULL',
|
||||
'contains': 'CONTAINS',
|
||||
'ncontains': 'NOT_CONTAINS',
|
||||
'beginswith': 'BEGINS_WITH',
|
||||
'in': 'IN',
|
||||
'between': 'BETWEEN',
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
"""
|
||||
This module provides an interface to the Elastic Compute Cloud (EC2)
|
||||
service from AWS.
|
||||
"""
|
||||
from boto.ec2.connection import EC2Connection
|
||||
from boto.regioninfo import RegionInfo
|
||||
|
||||
|
||||
RegionData = {
|
||||
'us-east-1': 'ec2.us-east-1.amazonaws.com',
|
||||
'us-gov-west-1': 'ec2.us-gov-west-1.amazonaws.com',
|
||||
'us-west-1': 'ec2.us-west-1.amazonaws.com',
|
||||
'us-west-2': 'ec2.us-west-2.amazonaws.com',
|
||||
'sa-east-1': 'ec2.sa-east-1.amazonaws.com',
|
||||
'eu-west-1': 'ec2.eu-west-1.amazonaws.com',
|
||||
'ap-northeast-1': 'ec2.ap-northeast-1.amazonaws.com',
|
||||
'ap-southeast-1': 'ec2.ap-southeast-1.amazonaws.com',
|
||||
'ap-southeast-2': 'ec2.ap-southeast-2.amazonaws.com',
|
||||
}
|
||||
|
||||
|
||||
def regions(**kw_params):
|
||||
"""
|
||||
Get all available regions for the EC2 service.
|
||||
You may pass any of the arguments accepted by the EC2Connection
|
||||
object's constructor as keyword arguments and they will be
|
||||
passed along to the EC2Connection object.
|
||||
|
||||
:rtype: list
|
||||
:return: A list of :class:`boto.ec2.regioninfo.RegionInfo`
|
||||
"""
|
||||
regions = []
|
||||
for region_name in RegionData:
|
||||
region = RegionInfo(name=region_name,
|
||||
endpoint=RegionData[region_name],
|
||||
connection_cls=EC2Connection)
|
||||
regions.append(region)
|
||||
return regions
|
||||
|
||||
|
||||
def connect_to_region(region_name, **kw_params):
|
||||
"""
|
||||
Given a valid region name, return a
|
||||
:class:`boto.ec2.connection.EC2Connection`.
|
||||
Any additional parameters after the region_name are passed on to
|
||||
the connect method of the region object.
|
||||
|
||||
:type: str
|
||||
:param region_name: The name of the region to connect to.
|
||||
|
||||
:rtype: :class:`boto.ec2.connection.EC2Connection` or ``None``
|
||||
:return: A connection to the given region, or None if an invalid region
|
||||
name is given
|
||||
"""
|
||||
if 'region' in kw_params and isinstance(kw_params['region'], RegionInfo)\
|
||||
and region_name == kw_params['region'].name:
|
||||
return EC2Connection(**kw_params)
|
||||
|
||||
for region in regions(**kw_params):
|
||||
if region.name == region_name:
|
||||
return region.connect(**kw_params)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_region(region_name, **kw_params):
|
||||
"""
|
||||
Find and return a :class:`boto.ec2.regioninfo.RegionInfo` object
|
||||
given a region name.
|
||||
|
||||
:type: str
|
||||
:param: The name of the region.
|
||||
|
||||
:rtype: :class:`boto.ec2.regioninfo.RegionInfo`
|
||||
:return: The RegionInfo object for the given region or None if
|
||||
an invalid region name is provided.
|
||||
"""
|
||||
for region in regions(**kw_params):
|
||||
if region.name == region_name:
|
||||
return region
|
||||
return None
|
|
@ -1,120 +0,0 @@
|
|||
# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
from boto.ec2.ec2object import EC2Object
|
||||
|
||||
class Address(EC2Object):
|
||||
"""
|
||||
Represents an EC2 Elastic IP Address
|
||||
|
||||
:ivar public_ip: The Elastic IP address.
|
||||
:ivar instance_id: The instance the address is associated with (if any).
|
||||
:ivar domain: Indicates whether the address is a EC2 address or a VPC address (standard|vpc).
|
||||
:ivar allocation_id: The allocation ID for the address (VPC addresses only).
|
||||
:ivar association_id: The association ID for the address (VPC addresses only).
|
||||
:ivar network_interface_id: The network interface (if any) that the address is associated with (VPC addresses only).
|
||||
:ivar network_interface_owner_id: The owner IID (VPC addresses only).
|
||||
:ivar private_ip_address: The private IP address associated with the Elastic IP address (VPC addresses only).
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None, public_ip=None, instance_id=None):
|
||||
EC2Object.__init__(self, connection)
|
||||
self.connection = connection
|
||||
self.public_ip = public_ip
|
||||
self.instance_id = instance_id
|
||||
self.domain = None
|
||||
self.allocation_id = None
|
||||
self.association_id = None
|
||||
self.network_interface_id = None
|
||||
self.network_interface_owner_id = None
|
||||
self.private_ip_address = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'Address:%s' % self.public_ip
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'publicIp':
|
||||
self.public_ip = value
|
||||
elif name == 'instanceId':
|
||||
self.instance_id = value
|
||||
elif name == 'domain':
|
||||
self.domain = value
|
||||
elif name == 'allocationId':
|
||||
self.allocation_id = value
|
||||
elif name == 'associationId':
|
||||
self.association_id = value
|
||||
elif name == 'networkInterfaceId':
|
||||
self.network_interface_id = value
|
||||
elif name == 'networkInterfaceOwnerId':
|
||||
self.network_interface_owner_id = value
|
||||
elif name == 'privateIpAddress':
|
||||
self.private_ip_address = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def release(self, dry_run=False):
|
||||
"""
|
||||
Free up this Elastic IP address.
|
||||
:see: :meth:`boto.ec2.connection.EC2Connection.release_address`
|
||||
"""
|
||||
if self.allocation_id:
|
||||
return self.connection.release_address(
|
||||
None,
|
||||
self.allocation_id,
|
||||
dry_run=dry_run)
|
||||
else:
|
||||
return self.connection.release_address(
|
||||
self.public_ip,
|
||||
dry_run=dry_run
|
||||
)
|
||||
|
||||
delete = release
|
||||
|
||||
def associate(self, instance_id, dry_run=False):
|
||||
"""
|
||||
Associate this Elastic IP address with a currently running instance.
|
||||
:see: :meth:`boto.ec2.connection.EC2Connection.associate_address`
|
||||
"""
|
||||
return self.connection.associate_address(
|
||||
instance_id,
|
||||
self.public_ip,
|
||||
dry_run=dry_run
|
||||
)
|
||||
|
||||
def disassociate(self, dry_run=False):
|
||||
"""
|
||||
Disassociate this Elastic IP address from a currently running instance.
|
||||
:see: :meth:`boto.ec2.connection.EC2Connection.disassociate_address`
|
||||
"""
|
||||
if self.association_id:
|
||||
return self.connection.disassociate_address(
|
||||
None,
|
||||
self.association_id,
|
||||
dry_run=dry_run
|
||||
)
|
||||
else:
|
||||
return self.connection.disassociate_address(
|
||||
self.public_ip,
|
||||
dry_run=dry_run
|
||||
)
|
||||
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
class AccountAttribute(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.attribute_name = None
|
||||
self.attribute_values = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'attributeValueSet':
|
||||
self.attribute_values = AttributeValues()
|
||||
return self.attribute_values
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'attributeName':
|
||||
self.attribute_name = value
|
||||
|
||||
|
||||
class AttributeValues(list):
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'attributeValue':
|
||||
self.append(value)
|
||||
|
||||
|
||||
class VPCAttribute(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.vpc_id = None
|
||||
self.enable_dns_hostnames = None
|
||||
self.enable_dns_support = None
|
||||
self._current_attr = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name in ('enableDnsHostnames', 'enableDnsSupport'):
|
||||
self._current_attr = name
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'vpcId':
|
||||
self.vpc_id = value
|
||||
elif name == 'value':
|
||||
if value == 'true':
|
||||
value = True
|
||||
else:
|
||||
value = False
|
||||
if self._current_attr == 'enableDnsHostnames':
|
||||
self.enable_dns_hostnames = value
|
||||
elif self._current_attr == 'enableDnsSupport':
|
||||
self.enable_dns_support = value
|
|
@ -1,835 +0,0 @@
|
|||
# Copyright (c) 2009-2011 Reza Lotun http://reza.lotun.name/
|
||||
# Copyright (c) 2011 Jann Kleen
|
||||
# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
"""
|
||||
This module provides an interface to the Elastic Compute Cloud (EC2)
|
||||
Auto Scaling service.
|
||||
"""
|
||||
|
||||
import base64
|
||||
|
||||
import boto
|
||||
from boto.connection import AWSQueryConnection
|
||||
from boto.ec2.regioninfo import RegionInfo
|
||||
from boto.ec2.autoscale.request import Request
|
||||
from boto.ec2.autoscale.launchconfig import LaunchConfiguration
|
||||
from boto.ec2.autoscale.group import AutoScalingGroup
|
||||
from boto.ec2.autoscale.group import ProcessType
|
||||
from boto.ec2.autoscale.activity import Activity
|
||||
from boto.ec2.autoscale.policy import AdjustmentType
|
||||
from boto.ec2.autoscale.policy import MetricCollectionTypes
|
||||
from boto.ec2.autoscale.policy import ScalingPolicy
|
||||
from boto.ec2.autoscale.policy import TerminationPolicies
|
||||
from boto.ec2.autoscale.instance import Instance
|
||||
from boto.ec2.autoscale.scheduled import ScheduledUpdateGroupAction
|
||||
from boto.ec2.autoscale.tag import Tag
|
||||
|
||||
RegionData = {
|
||||
'us-east-1': 'autoscaling.us-east-1.amazonaws.com',
|
||||
'us-gov-west-1': 'autoscaling.us-gov-west-1.amazonaws.com',
|
||||
'us-west-1': 'autoscaling.us-west-1.amazonaws.com',
|
||||
'us-west-2': 'autoscaling.us-west-2.amazonaws.com',
|
||||
'sa-east-1': 'autoscaling.sa-east-1.amazonaws.com',
|
||||
'eu-west-1': 'autoscaling.eu-west-1.amazonaws.com',
|
||||
'ap-northeast-1': 'autoscaling.ap-northeast-1.amazonaws.com',
|
||||
'ap-southeast-1': 'autoscaling.ap-southeast-1.amazonaws.com',
|
||||
'ap-southeast-2': 'autoscaling.ap-southeast-2.amazonaws.com',
|
||||
}
|
||||
|
||||
|
||||
def regions():
|
||||
"""
|
||||
Get all available regions for the Auto Scaling service.
|
||||
|
||||
:rtype: list
|
||||
:return: A list of :class:`boto.RegionInfo` instances
|
||||
"""
|
||||
regions = []
|
||||
for region_name in RegionData:
|
||||
region = RegionInfo(name=region_name,
|
||||
endpoint=RegionData[region_name],
|
||||
connection_cls=AutoScaleConnection)
|
||||
regions.append(region)
|
||||
return regions
|
||||
|
||||
|
||||
def connect_to_region(region_name, **kw_params):
|
||||
"""
|
||||
Given a valid region name, return a
|
||||
:class:`boto.ec2.autoscale.AutoScaleConnection`.
|
||||
|
||||
:param str region_name: The name of the region to connect to.
|
||||
|
||||
:rtype: :class:`boto.ec2.AutoScaleConnection` or ``None``
|
||||
:return: A connection to the given region, or None if an invalid region
|
||||
name is given
|
||||
"""
|
||||
for region in regions():
|
||||
if region.name == region_name:
|
||||
return region.connect(**kw_params)
|
||||
return None
|
||||
|
||||
|
||||
class AutoScaleConnection(AWSQueryConnection):
|
||||
APIVersion = boto.config.get('Boto', 'autoscale_version', '2011-01-01')
|
||||
DefaultRegionEndpoint = boto.config.get('Boto', 'autoscale_endpoint',
|
||||
'autoscaling.us-east-1.amazonaws.com')
|
||||
DefaultRegionName = boto.config.get('Boto', 'autoscale_region_name',
|
||||
'us-east-1')
|
||||
|
||||
def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
|
||||
is_secure=True, port=None, proxy=None, proxy_port=None,
|
||||
proxy_user=None, proxy_pass=None, debug=0,
|
||||
https_connection_factory=None, region=None, path='/',
|
||||
security_token=None, validate_certs=True):
|
||||
"""
|
||||
Init method to create a new connection to the AutoScaling service.
|
||||
|
||||
B{Note:} The host argument is overridden by the host specified in the
|
||||
boto configuration file.
|
||||
"""
|
||||
if not region:
|
||||
region = RegionInfo(self, self.DefaultRegionName,
|
||||
self.DefaultRegionEndpoint,
|
||||
AutoScaleConnection)
|
||||
self.region = region
|
||||
AWSQueryConnection.__init__(self, aws_access_key_id,
|
||||
aws_secret_access_key,
|
||||
is_secure, port, proxy, proxy_port,
|
||||
proxy_user, proxy_pass,
|
||||
self.region.endpoint, debug,
|
||||
https_connection_factory, path=path,
|
||||
security_token=security_token,
|
||||
validate_certs=validate_certs)
|
||||
|
||||
def _required_auth_capability(self):
|
||||
return ['hmac-v4']
|
||||
|
||||
def build_list_params(self, params, items, label):
|
||||
"""
|
||||
Items is a list of dictionaries or strings::
|
||||
|
||||
[
|
||||
{
|
||||
'Protocol' : 'HTTP',
|
||||
'LoadBalancerPort' : '80',
|
||||
'InstancePort' : '80'
|
||||
},
|
||||
..
|
||||
] etc.
|
||||
|
||||
or::
|
||||
|
||||
['us-east-1b',...]
|
||||
"""
|
||||
# different from EC2 list params
|
||||
for i in xrange(1, len(items) + 1):
|
||||
if isinstance(items[i - 1], dict):
|
||||
for k, v in items[i - 1].iteritems():
|
||||
if isinstance(v, dict):
|
||||
for kk, vv in v.iteritems():
|
||||
params['%s.member.%d.%s.%s' % (label, i, k, kk)] = vv
|
||||
else:
|
||||
params['%s.member.%d.%s' % (label, i, k)] = v
|
||||
elif isinstance(items[i - 1], basestring):
|
||||
params['%s.member.%d' % (label, i)] = items[i - 1]
|
||||
|
||||
def _update_group(self, op, as_group):
|
||||
params = {'AutoScalingGroupName': as_group.name,
|
||||
'LaunchConfigurationName': as_group.launch_config_name,
|
||||
'MinSize': as_group.min_size,
|
||||
'MaxSize': as_group.max_size}
|
||||
# get availability zone information (required param)
|
||||
zones = as_group.availability_zones
|
||||
self.build_list_params(params, zones, 'AvailabilityZones')
|
||||
if as_group.desired_capacity:
|
||||
params['DesiredCapacity'] = as_group.desired_capacity
|
||||
if as_group.vpc_zone_identifier:
|
||||
params['VPCZoneIdentifier'] = as_group.vpc_zone_identifier
|
||||
if as_group.health_check_period:
|
||||
params['HealthCheckGracePeriod'] = as_group.health_check_period
|
||||
if as_group.health_check_type:
|
||||
params['HealthCheckType'] = as_group.health_check_type
|
||||
if as_group.default_cooldown:
|
||||
params['DefaultCooldown'] = as_group.default_cooldown
|
||||
if as_group.placement_group:
|
||||
params['PlacementGroup'] = as_group.placement_group
|
||||
if as_group.termination_policies:
|
||||
self.build_list_params(params, as_group.termination_policies,
|
||||
'TerminationPolicies')
|
||||
if op.startswith('Create'):
|
||||
# you can only associate load balancers with an autoscale
|
||||
# group at creation time
|
||||
if as_group.load_balancers:
|
||||
self.build_list_params(params, as_group.load_balancers,
|
||||
'LoadBalancerNames')
|
||||
if as_group.tags:
|
||||
for i, tag in enumerate(as_group.tags):
|
||||
tag.build_params(params, i + 1)
|
||||
return self.get_object(op, params, Request)
|
||||
|
||||
def create_auto_scaling_group(self, as_group):
|
||||
"""
|
||||
Create auto scaling group.
|
||||
"""
|
||||
return self._update_group('CreateAutoScalingGroup', as_group)
|
||||
|
||||
def delete_auto_scaling_group(self, name, force_delete=False):
|
||||
"""
|
||||
Deletes the specified auto scaling group if the group has no instances
|
||||
and no scaling activities in progress.
|
||||
"""
|
||||
if(force_delete):
|
||||
params = {'AutoScalingGroupName': name, 'ForceDelete': 'true'}
|
||||
else:
|
||||
params = {'AutoScalingGroupName': name}
|
||||
return self.get_object('DeleteAutoScalingGroup', params, Request)
|
||||
|
||||
def create_launch_configuration(self, launch_config):
|
||||
"""
|
||||
Creates a new Launch Configuration.
|
||||
|
||||
:type launch_config: :class:`boto.ec2.autoscale.launchconfig.LaunchConfiguration`
|
||||
:param launch_config: LaunchConfiguration object.
|
||||
"""
|
||||
params = {'ImageId': launch_config.image_id,
|
||||
'LaunchConfigurationName': launch_config.name,
|
||||
'InstanceType': launch_config.instance_type}
|
||||
if launch_config.key_name:
|
||||
params['KeyName'] = launch_config.key_name
|
||||
if launch_config.user_data:
|
||||
params['UserData'] = base64.b64encode(launch_config.user_data)
|
||||
if launch_config.kernel_id:
|
||||
params['KernelId'] = launch_config.kernel_id
|
||||
if launch_config.ramdisk_id:
|
||||
params['RamdiskId'] = launch_config.ramdisk_id
|
||||
if launch_config.block_device_mappings:
|
||||
[x.autoscale_build_list_params(params) for x in launch_config.block_device_mappings]
|
||||
if launch_config.security_groups:
|
||||
self.build_list_params(params, launch_config.security_groups,
|
||||
'SecurityGroups')
|
||||
if launch_config.instance_monitoring:
|
||||
params['InstanceMonitoring.Enabled'] = 'true'
|
||||
else:
|
||||
params['InstanceMonitoring.Enabled'] = 'false'
|
||||
if launch_config.spot_price is not None:
|
||||
params['SpotPrice'] = str(launch_config.spot_price)
|
||||
if launch_config.instance_profile_name is not None:
|
||||
params['IamInstanceProfile'] = launch_config.instance_profile_name
|
||||
if launch_config.ebs_optimized:
|
||||
params['EbsOptimized'] = 'true'
|
||||
else:
|
||||
params['EbsOptimized'] = 'false'
|
||||
return self.get_object('CreateLaunchConfiguration', params,
|
||||
Request, verb='POST')
|
||||
|
||||
def create_scaling_policy(self, scaling_policy):
|
||||
"""
|
||||
Creates a new Scaling Policy.
|
||||
|
||||
:type scaling_policy: :class:`boto.ec2.autoscale.policy.ScalingPolicy`
|
||||
:param scaling_policy: ScalingPolicy object.
|
||||
"""
|
||||
params = {'AdjustmentType': scaling_policy.adjustment_type,
|
||||
'AutoScalingGroupName': scaling_policy.as_name,
|
||||
'PolicyName': scaling_policy.name,
|
||||
'ScalingAdjustment': scaling_policy.scaling_adjustment}
|
||||
|
||||
if scaling_policy.adjustment_type == "PercentChangeInCapacity" and \
|
||||
scaling_policy.min_adjustment_step is not None:
|
||||
params['MinAdjustmentStep'] = scaling_policy.min_adjustment_step
|
||||
|
||||
if scaling_policy.cooldown is not None:
|
||||
params['Cooldown'] = scaling_policy.cooldown
|
||||
|
||||
return self.get_object('PutScalingPolicy', params, Request)
|
||||
|
||||
def delete_launch_configuration(self, launch_config_name):
|
||||
"""
|
||||
Deletes the specified LaunchConfiguration.
|
||||
|
||||
The specified launch configuration must not be attached to an Auto
|
||||
Scaling group. Once this call completes, the launch configuration is no
|
||||
longer available for use.
|
||||
"""
|
||||
params = {'LaunchConfigurationName': launch_config_name}
|
||||
return self.get_object('DeleteLaunchConfiguration', params, Request)
|
||||
|
||||
def get_all_groups(self, names=None, max_records=None, next_token=None):
|
||||
"""
|
||||
Returns a full description of each Auto Scaling group in the given
|
||||
list. This includes all Amazon EC2 instances that are members of the
|
||||
group. If a list of names is not provided, the service returns the full
|
||||
details of all Auto Scaling groups.
|
||||
|
||||
This action supports pagination by returning a token if there are more
|
||||
pages to retrieve. To get the next page, call this action again with
|
||||
the returned token as the NextToken parameter.
|
||||
|
||||
:type names: list
|
||||
:param names: List of group names which should be searched for.
|
||||
|
||||
:type max_records: int
|
||||
:param max_records: Maximum amount of groups to return.
|
||||
|
||||
:rtype: list
|
||||
:returns: List of :class:`boto.ec2.autoscale.group.AutoScalingGroup`
|
||||
instances.
|
||||
"""
|
||||
params = {}
|
||||
if max_records:
|
||||
params['MaxRecords'] = max_records
|
||||
if next_token:
|
||||
params['NextToken'] = next_token
|
||||
if names:
|
||||
self.build_list_params(params, names, 'AutoScalingGroupNames')
|
||||
return self.get_list('DescribeAutoScalingGroups', params,
|
||||
[('member', AutoScalingGroup)])
|
||||
|
||||
def get_all_launch_configurations(self, **kwargs):
|
||||
"""
|
||||
Returns a full description of the launch configurations given the
|
||||
specified names.
|
||||
|
||||
If no names are specified, then the full details of all launch
|
||||
configurations are returned.
|
||||
|
||||
:type names: list
|
||||
:param names: List of configuration names which should be searched for.
|
||||
|
||||
:type max_records: int
|
||||
:param max_records: Maximum amount of configurations to return.
|
||||
|
||||
:type next_token: str
|
||||
:param next_token: If you have more results than can be returned
|
||||
at once, pass in this parameter to page through all results.
|
||||
|
||||
:rtype: list
|
||||
:returns: List of
|
||||
:class:`boto.ec2.autoscale.launchconfig.LaunchConfiguration`
|
||||
instances.
|
||||
"""
|
||||
params = {}
|
||||
max_records = kwargs.get('max_records', None)
|
||||
names = kwargs.get('names', None)
|
||||
if max_records is not None:
|
||||
params['MaxRecords'] = max_records
|
||||
if names:
|
||||
self.build_list_params(params, names, 'LaunchConfigurationNames')
|
||||
next_token = kwargs.get('next_token')
|
||||
if next_token:
|
||||
params['NextToken'] = next_token
|
||||
return self.get_list('DescribeLaunchConfigurations', params,
|
||||
[('member', LaunchConfiguration)])
|
||||
|
||||
def get_all_activities(self, autoscale_group, activity_ids=None,
|
||||
max_records=None, next_token=None):
|
||||
"""
|
||||
Get all activities for the given autoscaling group.
|
||||
|
||||
This action supports pagination by returning a token if there are more
|
||||
pages to retrieve. To get the next page, call this action again with
|
||||
the returned token as the NextToken parameter
|
||||
|
||||
:type autoscale_group: str or
|
||||
:class:`boto.ec2.autoscale.group.AutoScalingGroup` object
|
||||
:param autoscale_group: The auto scaling group to get activities on.
|
||||
|
||||
:type max_records: int
|
||||
:param max_records: Maximum amount of activities to return.
|
||||
|
||||
:rtype: list
|
||||
:returns: List of
|
||||
:class:`boto.ec2.autoscale.activity.Activity` instances.
|
||||
"""
|
||||
name = autoscale_group
|
||||
if isinstance(autoscale_group, AutoScalingGroup):
|
||||
name = autoscale_group.name
|
||||
params = {'AutoScalingGroupName': name}
|
||||
if max_records:
|
||||
params['MaxRecords'] = max_records
|
||||
if next_token:
|
||||
params['NextToken'] = next_token
|
||||
if activity_ids:
|
||||
self.build_list_params(params, activity_ids, 'ActivityIds')
|
||||
return self.get_list('DescribeScalingActivities',
|
||||
params, [('member', Activity)])
|
||||
|
||||
def get_termination_policies(self):
|
||||
"""Gets all valid termination policies.
|
||||
|
||||
These values can then be used as the termination_policies arg
|
||||
when creating and updating autoscale groups.
|
||||
"""
|
||||
return self.get_object('DescribeTerminationPolicyTypes',
|
||||
{}, TerminationPolicies)
|
||||
|
||||
def delete_scheduled_action(self, scheduled_action_name,
|
||||
autoscale_group=None):
|
||||
"""
|
||||
Deletes a previously scheduled action.
|
||||
|
||||
:type scheduled_action_name: str
|
||||
:param scheduled_action_name: The name of the action you want
|
||||
to delete.
|
||||
|
||||
:type autoscale_group: str
|
||||
:param autoscale_group: The name of the autoscale group.
|
||||
"""
|
||||
params = {'ScheduledActionName': scheduled_action_name}
|
||||
if autoscale_group:
|
||||
params['AutoScalingGroupName'] = autoscale_group
|
||||
return self.get_status('DeleteScheduledAction', params)
|
||||
|
||||
def terminate_instance(self, instance_id, decrement_capacity=True):
|
||||
"""
|
||||
Terminates the specified instance. The desired group size can
|
||||
also be adjusted, if desired.
|
||||
|
||||
:type instance_id: str
|
||||
:param instance_id: The ID of the instance to be terminated.
|
||||
|
||||
:type decrement_capability: bool
|
||||
:param decrement_capacity: Whether to decrement the size of the
|
||||
autoscaling group or not.
|
||||
"""
|
||||
params = {'InstanceId': instance_id}
|
||||
if decrement_capacity:
|
||||
params['ShouldDecrementDesiredCapacity'] = 'true'
|
||||
else:
|
||||
params['ShouldDecrementDesiredCapacity'] = 'false'
|
||||
return self.get_object('TerminateInstanceInAutoScalingGroup', params,
|
||||
Activity)
|
||||
|
||||
def delete_policy(self, policy_name, autoscale_group=None):
|
||||
"""
|
||||
Delete a policy.
|
||||
|
||||
:type policy_name: str
|
||||
:param policy_name: The name or ARN of the policy to delete.
|
||||
|
||||
:type autoscale_group: str
|
||||
:param autoscale_group: The name of the autoscale group.
|
||||
"""
|
||||
params = {'PolicyName': policy_name}
|
||||
if autoscale_group:
|
||||
params['AutoScalingGroupName'] = autoscale_group
|
||||
return self.get_status('DeletePolicy', params)
|
||||
|
||||
def get_all_adjustment_types(self):
|
||||
return self.get_list('DescribeAdjustmentTypes', {},
|
||||
[('member', AdjustmentType)])
|
||||
|
||||
def get_all_autoscaling_instances(self, instance_ids=None,
|
||||
max_records=None, next_token=None):
|
||||
"""
|
||||
Returns a description of each Auto Scaling instance in the instance_ids
|
||||
list. If a list is not provided, the service returns the full details
|
||||
of all instances up to a maximum of fifty.
|
||||
|
||||
This action supports pagination by returning a token if there are more
|
||||
pages to retrieve. To get the next page, call this action again with
|
||||
the returned token as the NextToken parameter.
|
||||
|
||||
:type instance_ids: list
|
||||
:param instance_ids: List of Autoscaling Instance IDs which should be
|
||||
searched for.
|
||||
|
||||
:type max_records: int
|
||||
:param max_records: Maximum number of results to return.
|
||||
|
||||
:rtype: list
|
||||
:returns: List of
|
||||
:class:`boto.ec2.autoscale.instance.Instance` objects.
|
||||
"""
|
||||
params = {}
|
||||
if instance_ids:
|
||||
self.build_list_params(params, instance_ids, 'InstanceIds')
|
||||
if max_records:
|
||||
params['MaxRecords'] = max_records
|
||||
if next_token:
|
||||
params['NextToken'] = next_token
|
||||
return self.get_list('DescribeAutoScalingInstances',
|
||||
params, [('member', Instance)])
|
||||
|
||||
def get_all_metric_collection_types(self):
|
||||
"""
|
||||
Returns a list of metrics and a corresponding list of granularities
|
||||
for each metric.
|
||||
"""
|
||||
return self.get_object('DescribeMetricCollectionTypes',
|
||||
{}, MetricCollectionTypes)
|
||||
|
||||
def get_all_policies(self, as_group=None, policy_names=None,
|
||||
max_records=None, next_token=None):
|
||||
"""
|
||||
Returns descriptions of what each policy does. This action supports
|
||||
pagination. If the response includes a token, there are more records
|
||||
available. To get the additional records, repeat the request with the
|
||||
response token as the NextToken parameter.
|
||||
|
||||
If no group name or list of policy names are provided, all
|
||||
available policies are returned.
|
||||
|
||||
:type as_group: str
|
||||
:param as_group: The name of the
|
||||
:class:`boto.ec2.autoscale.group.AutoScalingGroup` to filter for.
|
||||
|
||||
:type policy_names: list
|
||||
:param policy_names: List of policy names which should be searched for.
|
||||
|
||||
:type max_records: int
|
||||
:param max_records: Maximum amount of groups to return.
|
||||
|
||||
:type next_token: str
|
||||
:param next_token: If you have more results than can be returned
|
||||
at once, pass in this parameter to page through all results.
|
||||
"""
|
||||
params = {}
|
||||
if as_group:
|
||||
params['AutoScalingGroupName'] = as_group
|
||||
if policy_names:
|
||||
self.build_list_params(params, policy_names, 'PolicyNames')
|
||||
if max_records:
|
||||
params['MaxRecords'] = max_records
|
||||
if next_token:
|
||||
params['NextToken'] = next_token
|
||||
return self.get_list('DescribePolicies', params,
|
||||
[('member', ScalingPolicy)])
|
||||
|
||||
def get_all_scaling_process_types(self):
|
||||
"""
|
||||
Returns scaling process types for use in the ResumeProcesses and
|
||||
SuspendProcesses actions.
|
||||
"""
|
||||
return self.get_list('DescribeScalingProcessTypes', {},
|
||||
[('member', ProcessType)])
|
||||
|
||||
def suspend_processes(self, as_group, scaling_processes=None):
|
||||
"""
|
||||
Suspends Auto Scaling processes for an Auto Scaling group.
|
||||
|
||||
:type as_group: string
|
||||
:param as_group: The auto scaling group to suspend processes on.
|
||||
|
||||
:type scaling_processes: list
|
||||
:param scaling_processes: Processes you want to suspend. If omitted,
|
||||
all processes will be suspended.
|
||||
"""
|
||||
params = {'AutoScalingGroupName': as_group}
|
||||
if scaling_processes:
|
||||
self.build_list_params(params, scaling_processes,
|
||||
'ScalingProcesses')
|
||||
return self.get_status('SuspendProcesses', params)
|
||||
|
||||
def resume_processes(self, as_group, scaling_processes=None):
|
||||
"""
|
||||
Resumes Auto Scaling processes for an Auto Scaling group.
|
||||
|
||||
:type as_group: string
|
||||
:param as_group: The auto scaling group to resume processes on.
|
||||
|
||||
:type scaling_processes: list
|
||||
:param scaling_processes: Processes you want to resume. If omitted, all
|
||||
processes will be resumed.
|
||||
"""
|
||||
params = {'AutoScalingGroupName': as_group}
|
||||
|
||||
if scaling_processes:
|
||||
self.build_list_params(params, scaling_processes,
|
||||
'ScalingProcesses')
|
||||
return self.get_status('ResumeProcesses', params)
|
||||
|
||||
def create_scheduled_group_action(self, as_group, name, time=None,
|
||||
desired_capacity=None,
|
||||
min_size=None, max_size=None,
|
||||
start_time=None, end_time=None,
|
||||
recurrence=None):
|
||||
"""
|
||||
Creates a scheduled scaling action for a Auto Scaling group. If you
|
||||
leave a parameter unspecified, the corresponding value remains
|
||||
unchanged in the affected Auto Scaling group.
|
||||
|
||||
:type as_group: string
|
||||
:param as_group: The auto scaling group to get activities on.
|
||||
|
||||
:type name: string
|
||||
:param name: Scheduled action name.
|
||||
|
||||
:type time: datetime.datetime
|
||||
:param time: The time for this action to start. (Depracated)
|
||||
|
||||
:type desired_capacity: int
|
||||
:param desired_capacity: The number of EC2 instances that should
|
||||
be running in this group.
|
||||
|
||||
:type min_size: int
|
||||
:param min_size: The minimum size for the new auto scaling group.
|
||||
|
||||
:type max_size: int
|
||||
:param max_size: The minimum size for the new auto scaling group.
|
||||
|
||||
:type start_time: datetime.datetime
|
||||
:param start_time: The time for this action to start. When StartTime and EndTime are specified with Recurrence, they form the boundaries of when the recurring action will start and stop.
|
||||
|
||||
:type end_time: datetime.datetime
|
||||
:param end_time: The time for this action to end. When StartTime and EndTime are specified with Recurrence, they form the boundaries of when the recurring action will start and stop.
|
||||
|
||||
:type recurrence: string
|
||||
:param recurrence: The time when recurring future actions will start. Start time is specified by the user following the Unix cron syntax format. EXAMPLE: '0 10 * * *'
|
||||
"""
|
||||
params = {'AutoScalingGroupName': as_group,
|
||||
'ScheduledActionName': name}
|
||||
if start_time is not None:
|
||||
params['StartTime'] = start_time.isoformat()
|
||||
if end_time is not None:
|
||||
params['EndTime'] = end_time.isoformat()
|
||||
if recurrence is not None:
|
||||
params['Recurrence'] = recurrence
|
||||
if time:
|
||||
params['Time'] = time.isoformat()
|
||||
if desired_capacity is not None:
|
||||
params['DesiredCapacity'] = desired_capacity
|
||||
if min_size is not None:
|
||||
params['MinSize'] = min_size
|
||||
if max_size is not None:
|
||||
params['MaxSize'] = max_size
|
||||
return self.get_status('PutScheduledUpdateGroupAction', params)
|
||||
|
||||
def get_all_scheduled_actions(self, as_group=None, start_time=None,
|
||||
end_time=None, scheduled_actions=None,
|
||||
max_records=None, next_token=None):
|
||||
params = {}
|
||||
if as_group:
|
||||
params['AutoScalingGroupName'] = as_group
|
||||
if scheduled_actions:
|
||||
self.build_list_params(params, scheduled_actions,
|
||||
'ScheduledActionNames')
|
||||
if max_records:
|
||||
params['MaxRecords'] = max_records
|
||||
if next_token:
|
||||
params['NextToken'] = next_token
|
||||
return self.get_list('DescribeScheduledActions', params,
|
||||
[('member', ScheduledUpdateGroupAction)])
|
||||
|
||||
def disable_metrics_collection(self, as_group, metrics=None):
|
||||
"""
|
||||
Disables monitoring of group metrics for the Auto Scaling group
|
||||
specified in AutoScalingGroupName. You can specify the list of affected
|
||||
metrics with the Metrics parameter.
|
||||
"""
|
||||
params = {'AutoScalingGroupName': as_group}
|
||||
|
||||
if metrics:
|
||||
self.build_list_params(params, metrics, 'Metrics')
|
||||
return self.get_status('DisableMetricsCollection', params)
|
||||
|
||||
def enable_metrics_collection(self, as_group, granularity, metrics=None):
|
||||
"""
|
||||
Enables monitoring of group metrics for the Auto Scaling group
|
||||
specified in AutoScalingGroupName. You can specify the list of enabled
|
||||
metrics with the Metrics parameter.
|
||||
|
||||
Auto scaling metrics collection can be turned on only if the
|
||||
InstanceMonitoring.Enabled flag, in the Auto Scaling group's launch
|
||||
configuration, is set to true.
|
||||
|
||||
:type autoscale_group: string
|
||||
:param autoscale_group: The auto scaling group to get activities on.
|
||||
|
||||
:type granularity: string
|
||||
:param granularity: The granularity to associate with the metrics to
|
||||
collect. Currently, the only legal granularity is "1Minute".
|
||||
|
||||
:type metrics: string list
|
||||
:param metrics: The list of metrics to collect. If no metrics are
|
||||
specified, all metrics are enabled.
|
||||
"""
|
||||
params = {'AutoScalingGroupName': as_group,
|
||||
'Granularity': granularity}
|
||||
if metrics:
|
||||
self.build_list_params(params, metrics, 'Metrics')
|
||||
return self.get_status('EnableMetricsCollection', params)
|
||||
|
||||
def execute_policy(self, policy_name, as_group=None, honor_cooldown=None):
|
||||
params = {'PolicyName': policy_name}
|
||||
if as_group:
|
||||
params['AutoScalingGroupName'] = as_group
|
||||
if honor_cooldown:
|
||||
params['HonorCooldown'] = honor_cooldown
|
||||
return self.get_status('ExecutePolicy', params)
|
||||
|
||||
def put_notification_configuration(self, autoscale_group, topic, notification_types):
|
||||
"""
|
||||
Configures an Auto Scaling group to send notifications when
|
||||
specified events take place.
|
||||
|
||||
:type autoscale_group: str or
|
||||
:class:`boto.ec2.autoscale.group.AutoScalingGroup` object
|
||||
:param autoscale_group: The Auto Scaling group to put notification
|
||||
configuration on.
|
||||
|
||||
:type topic: str
|
||||
:param topic: The Amazon Resource Name (ARN) of the Amazon Simple
|
||||
Notification Service (SNS) topic.
|
||||
|
||||
:type notification_types: list
|
||||
:param notification_types: The type of events that will trigger
|
||||
the notification.
|
||||
"""
|
||||
|
||||
name = autoscale_group
|
||||
if isinstance(autoscale_group, AutoScalingGroup):
|
||||
name = autoscale_group.name
|
||||
|
||||
params = {'AutoScalingGroupName': name,
|
||||
'TopicARN': topic}
|
||||
self.build_list_params(params, notification_types, 'NotificationTypes')
|
||||
return self.get_status('PutNotificationConfiguration', params)
|
||||
|
||||
def delete_notification_configuration(self, autoscale_group, topic):
|
||||
"""
|
||||
Deletes notifications created by put_notification_configuration.
|
||||
|
||||
:type autoscale_group: str or
|
||||
:class:`boto.ec2.autoscale.group.AutoScalingGroup` object
|
||||
:param autoscale_group: The Auto Scaling group to put notification
|
||||
configuration on.
|
||||
|
||||
:type topic: str
|
||||
:param topic: The Amazon Resource Name (ARN) of the Amazon Simple
|
||||
Notification Service (SNS) topic.
|
||||
"""
|
||||
|
||||
name = autoscale_group
|
||||
if isinstance(autoscale_group, AutoScalingGroup):
|
||||
name = autoscale_group.name
|
||||
|
||||
params = {'AutoScalingGroupName': name,
|
||||
'TopicARN': topic}
|
||||
|
||||
return self.get_status('DeleteNotificationConfiguration', params)
|
||||
|
||||
def set_instance_health(self, instance_id, health_status,
|
||||
should_respect_grace_period=True):
|
||||
"""
|
||||
Explicitly set the health status of an instance.
|
||||
|
||||
:type instance_id: str
|
||||
:param instance_id: The identifier of the EC2 instance.
|
||||
|
||||
:type health_status: str
|
||||
:param health_status: The health status of the instance.
|
||||
"Healthy" means that the instance is healthy and should remain
|
||||
in service. "Unhealthy" means that the instance is unhealthy.
|
||||
Auto Scaling should terminate and replace it.
|
||||
|
||||
:type should_respect_grace_period: bool
|
||||
:param should_respect_grace_period: If True, this call should
|
||||
respect the grace period associated with the group.
|
||||
"""
|
||||
params = {'InstanceId': instance_id,
|
||||
'HealthStatus': health_status}
|
||||
if should_respect_grace_period:
|
||||
params['ShouldRespectGracePeriod'] = 'true'
|
||||
else:
|
||||
params['ShouldRespectGracePeriod'] = 'false'
|
||||
return self.get_status('SetInstanceHealth', params)
|
||||
|
||||
def set_desired_capacity(self, group_name, desired_capacity, honor_cooldown=False):
|
||||
"""
|
||||
Adjusts the desired size of the AutoScalingGroup by initiating scaling
|
||||
activities. When reducing the size of the group, it is not possible to define
|
||||
which Amazon EC2 instances will be terminated. This applies to any Auto Scaling
|
||||
decisions that might result in terminating instances.
|
||||
|
||||
:type group_name: string
|
||||
:param group_name: name of the auto scaling group
|
||||
|
||||
:type desired_capacity: integer
|
||||
:param desired_capacity: new capacity setting for auto scaling group
|
||||
|
||||
:type honor_cooldown: boolean
|
||||
:param honor_cooldown: by default, overrides any cooldown period
|
||||
"""
|
||||
params = {'AutoScalingGroupName': group_name,
|
||||
'DesiredCapacity': desired_capacity}
|
||||
if honor_cooldown:
|
||||
params['HonorCooldown'] = json.dumps('True')
|
||||
|
||||
return self.get_status('SetDesiredCapacity', params)
|
||||
|
||||
# Tag methods
|
||||
|
||||
def get_all_tags(self, filters=None, max_records=None, next_token=None):
|
||||
"""
|
||||
Lists the Auto Scaling group tags.
|
||||
|
||||
This action supports pagination by returning a token if there
|
||||
are more pages to retrieve. To get the next page, call this
|
||||
action again with the returned token as the NextToken
|
||||
parameter.
|
||||
|
||||
:type filters: dict
|
||||
:param filters: The value of the filter type used to identify
|
||||
the tags to be returned. NOT IMPLEMENTED YET.
|
||||
|
||||
:type max_records: int
|
||||
:param max_records: Maximum number of tags to return.
|
||||
|
||||
:rtype: list
|
||||
:returns: List of :class:`boto.ec2.autoscale.tag.Tag`
|
||||
instances.
|
||||
"""
|
||||
params = {}
|
||||
if max_records:
|
||||
params['MaxRecords'] = max_records
|
||||
if next_token:
|
||||
params['NextToken'] = next_token
|
||||
return self.get_list('DescribeTags', params,
|
||||
[('member', Tag)])
|
||||
|
||||
def create_or_update_tags(self, tags):
|
||||
"""
|
||||
Creates new tags or updates existing tags for an Auto Scaling group.
|
||||
|
||||
:type tags: List of :class:`boto.ec2.autoscale.tag.Tag`
|
||||
:param tags: The new or updated tags.
|
||||
"""
|
||||
params = {}
|
||||
for i, tag in enumerate(tags):
|
||||
tag.build_params(params, i + 1)
|
||||
return self.get_status('CreateOrUpdateTags', params, verb='POST')
|
||||
|
||||
def delete_tags(self, tags):
|
||||
"""
|
||||
Deletes existing tags for an Auto Scaling group.
|
||||
|
||||
:type tags: List of :class:`boto.ec2.autoscale.tag.Tag`
|
||||
:param tags: The new or updated tags.
|
||||
"""
|
||||
params = {}
|
||||
for i, tag in enumerate(tags):
|
||||
tag.build_params(params, i + 1)
|
||||
return self.get_status('DeleteTags', params, verb='POST')
|
|
@ -1,74 +0,0 @@
|
|||
# Copyright (c) 2009-2011 Reza Lotun http://reza.lotun.name/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class Activity(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.start_time = None
|
||||
self.end_time = None
|
||||
self.activity_id = None
|
||||
self.progress = None
|
||||
self.status_code = None
|
||||
self.cause = None
|
||||
self.description = None
|
||||
self.status_message = None
|
||||
self.group_name = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'Activity<%s>: For group:%s, progress:%s, cause:%s' % (self.activity_id,
|
||||
self.group_name,
|
||||
self.status_message,
|
||||
self.cause)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'ActivityId':
|
||||
self.activity_id = value
|
||||
elif name == 'AutoScalingGroupName':
|
||||
self.group_name = value
|
||||
elif name == 'StartTime':
|
||||
try:
|
||||
self.start_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
except ValueError:
|
||||
self.start_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
|
||||
elif name == 'EndTime':
|
||||
try:
|
||||
self.end_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
except ValueError:
|
||||
self.end_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
|
||||
elif name == 'Progress':
|
||||
self.progress = value
|
||||
elif name == 'Cause':
|
||||
self.cause = value
|
||||
elif name == 'Description':
|
||||
self.description = value
|
||||
elif name == 'StatusMessage':
|
||||
self.status_message = value
|
||||
elif name == 'StatusCode':
|
||||
self.status_code = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
|
@ -1,343 +0,0 @@
|
|||
# Copyright (c) 2009-2011 Reza Lotun http://reza.lotun.name/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
from boto.ec2.elb.listelement import ListElement
|
||||
from boto.resultset import ResultSet
|
||||
from boto.ec2.autoscale.launchconfig import LaunchConfiguration
|
||||
from boto.ec2.autoscale.request import Request
|
||||
from boto.ec2.autoscale.instance import Instance
|
||||
from boto.ec2.autoscale.tag import Tag
|
||||
|
||||
|
||||
class ProcessType(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.process_name = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'ProcessType(%s)' % self.process_name
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'ProcessName':
|
||||
self.process_name = value
|
||||
|
||||
|
||||
class SuspendedProcess(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.process_name = None
|
||||
self.reason = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'SuspendedProcess(%s, %s)' % (self.process_name, self.reason)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'ProcessName':
|
||||
self.process_name = value
|
||||
elif name == 'SuspensionReason':
|
||||
self.reason = value
|
||||
|
||||
|
||||
class EnabledMetric(object):
|
||||
def __init__(self, connection=None, metric=None, granularity=None):
|
||||
self.connection = connection
|
||||
self.metric = metric
|
||||
self.granularity = granularity
|
||||
|
||||
def __repr__(self):
|
||||
return 'EnabledMetric(%s, %s)' % (self.metric, self.granularity)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Granularity':
|
||||
self.granularity = value
|
||||
elif name == 'Metric':
|
||||
self.metric = value
|
||||
|
||||
|
||||
class TerminationPolicies(list):
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'member':
|
||||
self.append(value)
|
||||
|
||||
|
||||
class AutoScalingGroup(object):
|
||||
def __init__(self, connection=None, name=None,
|
||||
launch_config=None, availability_zones=None,
|
||||
load_balancers=None, default_cooldown=None,
|
||||
health_check_type=None, health_check_period=None,
|
||||
placement_group=None, vpc_zone_identifier=None,
|
||||
desired_capacity=None, min_size=None, max_size=None,
|
||||
tags=None, termination_policies=None, **kwargs):
|
||||
"""
|
||||
Creates a new AutoScalingGroup with the specified name.
|
||||
|
||||
You must not have already used up your entire quota of
|
||||
AutoScalingGroups in order for this call to be successful. Once the
|
||||
creation request is completed, the AutoScalingGroup is ready to be
|
||||
used in other calls.
|
||||
|
||||
:type name: str
|
||||
:param name: Name of autoscaling group (required).
|
||||
|
||||
:type availability_zones: list
|
||||
:param availability_zones: List of availability zones (required).
|
||||
|
||||
:type default_cooldown: int
|
||||
:param default_cooldown: Number of seconds after a Scaling Activity
|
||||
completes before any further scaling activities can start.
|
||||
|
||||
:type desired_capacity: int
|
||||
:param desired_capacity: The desired capacity for the group.
|
||||
|
||||
:type health_check_period: str
|
||||
:param health_check_period: Length of time in seconds after a new
|
||||
EC2 instance comes into service that Auto Scaling starts
|
||||
checking its health.
|
||||
|
||||
:type health_check_type: str
|
||||
:param health_check_type: The service you want the health status from,
|
||||
Amazon EC2 or Elastic Load Balancer.
|
||||
|
||||
:type launch_config_name: str or LaunchConfiguration
|
||||
:param launch_config_name: Name of launch configuration (required).
|
||||
|
||||
:type load_balancers: list
|
||||
:param load_balancers: List of load balancers.
|
||||
|
||||
:type max_size: int
|
||||
:param max_size: Maximum size of group (required).
|
||||
|
||||
:type min_size: int
|
||||
:param min_size: Minimum size of group (required).
|
||||
|
||||
:type placement_group: str
|
||||
:param placement_group: Physical location of your cluster placement
|
||||
group created in Amazon EC2.
|
||||
|
||||
:type vpc_zone_identifier: str
|
||||
:param vpc_zone_identifier: The subnet identifier of the Virtual
|
||||
Private Cloud.
|
||||
|
||||
:type termination_policies: list
|
||||
:param termination_policies: A list of termination policies. Valid values
|
||||
are: "OldestInstance", "NewestInstance", "OldestLaunchConfiguration",
|
||||
"ClosestToNextInstanceHour", "Default". If no value is specified,
|
||||
the "Default" value is used.
|
||||
|
||||
:rtype: :class:`boto.ec2.autoscale.group.AutoScalingGroup`
|
||||
:return: An autoscale group.
|
||||
"""
|
||||
self.name = name or kwargs.get('group_name') # backwards compat
|
||||
self.connection = connection
|
||||
self.min_size = int(min_size) if min_size is not None else None
|
||||
self.max_size = int(max_size) if max_size is not None else None
|
||||
self.created_time = None
|
||||
# backwards compatibility
|
||||
default_cooldown = default_cooldown or kwargs.get('cooldown')
|
||||
if default_cooldown is not None:
|
||||
default_cooldown = int(default_cooldown)
|
||||
self.default_cooldown = default_cooldown
|
||||
self.launch_config_name = launch_config
|
||||
if launch_config and isinstance(launch_config, LaunchConfiguration):
|
||||
self.launch_config_name = launch_config.name
|
||||
self.desired_capacity = desired_capacity
|
||||
lbs = load_balancers or []
|
||||
self.load_balancers = ListElement(lbs)
|
||||
zones = availability_zones or []
|
||||
self.availability_zones = ListElement(zones)
|
||||
self.health_check_period = health_check_period
|
||||
self.health_check_type = health_check_type
|
||||
self.placement_group = placement_group
|
||||
self.autoscaling_group_arn = None
|
||||
self.vpc_zone_identifier = vpc_zone_identifier
|
||||
self.instances = None
|
||||
self.tags = tags or None
|
||||
termination_policies = termination_policies or []
|
||||
self.termination_policies = ListElement(termination_policies)
|
||||
|
||||
# backwards compatible access to 'cooldown' param
|
||||
def _get_cooldown(self):
|
||||
return self.default_cooldown
|
||||
|
||||
def _set_cooldown(self, val):
|
||||
self.default_cooldown = val
|
||||
|
||||
cooldown = property(_get_cooldown, _set_cooldown)
|
||||
|
||||
def __repr__(self):
|
||||
return 'AutoScaleGroup<%s>' % self.name
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'Instances':
|
||||
self.instances = ResultSet([('member', Instance)])
|
||||
return self.instances
|
||||
elif name == 'LoadBalancerNames':
|
||||
return self.load_balancers
|
||||
elif name == 'AvailabilityZones':
|
||||
return self.availability_zones
|
||||
elif name == 'EnabledMetrics':
|
||||
self.enabled_metrics = ResultSet([('member', EnabledMetric)])
|
||||
return self.enabled_metrics
|
||||
elif name == 'SuspendedProcesses':
|
||||
self.suspended_processes = ResultSet([('member', SuspendedProcess)])
|
||||
return self.suspended_processes
|
||||
elif name == 'Tags':
|
||||
self.tags = ResultSet([('member', Tag)])
|
||||
return self.tags
|
||||
elif name == 'TerminationPolicies':
|
||||
return self.termination_policies
|
||||
else:
|
||||
return
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'MinSize':
|
||||
self.min_size = int(value)
|
||||
elif name == 'AutoScalingGroupARN':
|
||||
self.autoscaling_group_arn = value
|
||||
elif name == 'CreatedTime':
|
||||
self.created_time = value
|
||||
elif name == 'DefaultCooldown':
|
||||
self.default_cooldown = int(value)
|
||||
elif name == 'LaunchConfigurationName':
|
||||
self.launch_config_name = value
|
||||
elif name == 'DesiredCapacity':
|
||||
self.desired_capacity = int(value)
|
||||
elif name == 'MaxSize':
|
||||
self.max_size = int(value)
|
||||
elif name == 'AutoScalingGroupName':
|
||||
self.name = value
|
||||
elif name == 'PlacementGroup':
|
||||
self.placement_group = value
|
||||
elif name == 'HealthCheckGracePeriod':
|
||||
try:
|
||||
self.health_check_period = int(value)
|
||||
except ValueError:
|
||||
self.health_check_period = None
|
||||
elif name == 'HealthCheckType':
|
||||
self.health_check_type = value
|
||||
elif name == 'VPCZoneIdentifier':
|
||||
self.vpc_zone_identifier = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def set_capacity(self, capacity):
|
||||
"""
|
||||
Set the desired capacity for the group.
|
||||
"""
|
||||
params = {'AutoScalingGroupName': self.name,
|
||||
'DesiredCapacity': capacity}
|
||||
req = self.connection.get_object('SetDesiredCapacity', params,
|
||||
Request)
|
||||
self.connection.last_request = req
|
||||
return req
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
Sync local changes with AutoScaling group.
|
||||
"""
|
||||
return self.connection._update_group('UpdateAutoScalingGroup', self)
|
||||
|
||||
def shutdown_instances(self):
|
||||
"""
|
||||
Convenience method which shuts down all instances associated with
|
||||
this group.
|
||||
"""
|
||||
self.min_size = 0
|
||||
self.max_size = 0
|
||||
self.desired_capacity = 0
|
||||
self.update()
|
||||
|
||||
def delete(self, force_delete=False):
|
||||
"""
|
||||
Delete this auto-scaling group if no instances attached or no
|
||||
scaling activities in progress.
|
||||
"""
|
||||
return self.connection.delete_auto_scaling_group(self.name,
|
||||
force_delete)
|
||||
|
||||
def get_activities(self, activity_ids=None, max_records=50):
|
||||
"""
|
||||
Get all activies for this group.
|
||||
"""
|
||||
return self.connection.get_all_activities(self, activity_ids,
|
||||
max_records)
|
||||
|
||||
def put_notification_configuration(self, topic, notification_types):
|
||||
"""
|
||||
Configures an Auto Scaling group to send notifications when
|
||||
specified events take place.
|
||||
"""
|
||||
return self.connection.put_notification_configuration(self,
|
||||
topic,
|
||||
notification_types)
|
||||
|
||||
def delete_notification_configuration(self, topic):
|
||||
"""
|
||||
Deletes notifications created by put_notification_configuration.
|
||||
"""
|
||||
return self.connection.delete_notification_configuration(self, topic)
|
||||
|
||||
def suspend_processes(self, scaling_processes=None):
|
||||
"""
|
||||
Suspends Auto Scaling processes for an Auto Scaling group.
|
||||
"""
|
||||
return self.connection.suspend_processes(self.name, scaling_processes)
|
||||
|
||||
def resume_processes(self, scaling_processes=None):
|
||||
"""
|
||||
Resumes Auto Scaling processes for an Auto Scaling group.
|
||||
"""
|
||||
return self.connection.resume_processes(self.name, scaling_processes)
|
||||
|
||||
|
||||
class AutoScalingGroupMetric(object):
|
||||
def __init__(self, connection=None):
|
||||
|
||||
self.connection = connection
|
||||
self.metric = None
|
||||
self.granularity = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'AutoScalingGroupMetric:%s' % self.metric
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Metric':
|
||||
self.metric = value
|
||||
elif name == 'Granularity':
|
||||
self.granularity = value
|
||||
else:
|
||||
setattr(self, name, value)
|
|
@ -1,60 +0,0 @@
|
|||
# Copyright (c) 2009 Reza Lotun http://reza.lotun.name/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
class Instance(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.instance_id = None
|
||||
self.health_status = None
|
||||
self.launch_config_name = None
|
||||
self.lifecycle_state = None
|
||||
self.availability_zone = None
|
||||
self.group_name = None
|
||||
|
||||
def __repr__(self):
|
||||
r = 'Instance<id:%s, state:%s, health:%s' % (self.instance_id,
|
||||
self.lifecycle_state,
|
||||
self.health_status)
|
||||
if self.group_name:
|
||||
r += ' group:%s' % self.group_name
|
||||
r += '>'
|
||||
return r
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'InstanceId':
|
||||
self.instance_id = value
|
||||
elif name == 'HealthStatus':
|
||||
self.health_status = value
|
||||
elif name == 'LaunchConfigurationName':
|
||||
self.launch_config_name = value
|
||||
elif name == 'LifecycleState':
|
||||
self.lifecycle_state = value
|
||||
elif name == 'AvailabilityZone':
|
||||
self.availability_zone = value
|
||||
elif name == 'AutoScalingGroupName':
|
||||
self.group_name = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
|
@ -1,216 +0,0 @@
|
|||
# Copyright (c) 2009 Reza Lotun http://reza.lotun.name/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
from datetime import datetime
|
||||
from boto.resultset import ResultSet
|
||||
from boto.ec2.elb.listelement import ListElement
|
||||
import boto.utils
|
||||
import base64
|
||||
|
||||
# this should use the corresponding object from boto.ec2
|
||||
|
||||
|
||||
class Ebs(object):
|
||||
def __init__(self, connection=None, snapshot_id=None, volume_size=None):
|
||||
self.connection = connection
|
||||
self.snapshot_id = snapshot_id
|
||||
self.volume_size = volume_size
|
||||
|
||||
def __repr__(self):
|
||||
return 'Ebs(%s, %s)' % (self.snapshot_id, self.volume_size)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'SnapshotId':
|
||||
self.snapshot_id = value
|
||||
elif name == 'VolumeSize':
|
||||
self.volume_size = value
|
||||
|
||||
|
||||
class InstanceMonitoring(object):
|
||||
def __init__(self, connection=None, enabled='false'):
|
||||
self.connection = connection
|
||||
self.enabled = enabled
|
||||
|
||||
def __repr__(self):
|
||||
return 'InstanceMonitoring(%s)' % self.enabled
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Enabled':
|
||||
self.enabled = value
|
||||
|
||||
|
||||
# this should use the BlockDeviceMapping from boto.ec2.blockdevicemapping
|
||||
class BlockDeviceMapping(object):
|
||||
def __init__(self, connection=None, device_name=None, virtual_name=None):
|
||||
self.connection = connection
|
||||
self.device_name = None
|
||||
self.virtual_name = None
|
||||
self.ebs = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'BlockDeviceMapping(%s, %s)' % (self.device_name,
|
||||
self.virtual_name)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'Ebs':
|
||||
self.ebs = Ebs(self)
|
||||
return self.ebs
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'DeviceName':
|
||||
self.device_name = value
|
||||
elif name == 'VirtualName':
|
||||
self.virtual_name = value
|
||||
|
||||
|
||||
class LaunchConfiguration(object):
|
||||
def __init__(self, connection=None, name=None, image_id=None,
|
||||
key_name=None, security_groups=None, user_data=None,
|
||||
instance_type='m1.small', kernel_id=None,
|
||||
ramdisk_id=None, block_device_mappings=None,
|
||||
instance_monitoring=False, spot_price=None,
|
||||
instance_profile_name=None, ebs_optimized=False):
|
||||
"""
|
||||
A launch configuration.
|
||||
|
||||
:type name: str
|
||||
:param name: Name of the launch configuration to create.
|
||||
|
||||
:type image_id: str
|
||||
:param image_id: Unique ID of the Amazon Machine Image (AMI) which was
|
||||
assigned during registration.
|
||||
|
||||
:type key_name: str
|
||||
:param key_name: The name of the EC2 key pair.
|
||||
|
||||
:type security_groups: list
|
||||
:param security_groups: Names of the security groups with which to
|
||||
associate the EC2 instances.
|
||||
|
||||
:type user_data: str
|
||||
:param user_data: The user data available to launched EC2 instances.
|
||||
|
||||
:type instance_type: str
|
||||
:param instance_type: The instance type
|
||||
|
||||
:type kern_id: str
|
||||
:param kern_id: Kernel id for instance
|
||||
|
||||
:type ramdisk_id: str
|
||||
:param ramdisk_id: RAM disk id for instance
|
||||
|
||||
:type block_device_mappings: list
|
||||
:param block_device_mappings: Specifies how block devices are exposed
|
||||
for instances
|
||||
|
||||
:type instance_monitoring: bool
|
||||
:param instance_monitoring: Whether instances in group are launched
|
||||
with detailed monitoring.
|
||||
|
||||
:type spot_price: float
|
||||
:param spot_price: The spot price you are bidding. Only applies
|
||||
if you are building an autoscaling group with spot instances.
|
||||
|
||||
:type instance_profile_name: string
|
||||
:param instance_profile_name: The name or the Amazon Resource
|
||||
Name (ARN) of the instance profile associated with the IAM
|
||||
role for the instance.
|
||||
|
||||
:type ebs_optimized: bool
|
||||
:param ebs_optimized: Specifies whether the instance is optimized
|
||||
for EBS I/O (true) or not (false).
|
||||
"""
|
||||
self.connection = connection
|
||||
self.name = name
|
||||
self.instance_type = instance_type
|
||||
self.block_device_mappings = block_device_mappings
|
||||
self.key_name = key_name
|
||||
sec_groups = security_groups or []
|
||||
self.security_groups = ListElement(sec_groups)
|
||||
self.image_id = image_id
|
||||
self.ramdisk_id = ramdisk_id
|
||||
self.created_time = None
|
||||
self.kernel_id = kernel_id
|
||||
self.user_data = user_data
|
||||
self.created_time = None
|
||||
self.instance_monitoring = instance_monitoring
|
||||
self.spot_price = spot_price
|
||||
self.instance_profile_name = instance_profile_name
|
||||
self.launch_configuration_arn = None
|
||||
self.ebs_optimized = ebs_optimized
|
||||
|
||||
def __repr__(self):
|
||||
return 'LaunchConfiguration:%s' % self.name
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'SecurityGroups':
|
||||
return self.security_groups
|
||||
elif name == 'BlockDeviceMappings':
|
||||
self.block_device_mappings = ResultSet([('member',
|
||||
BlockDeviceMapping)])
|
||||
return self.block_device_mappings
|
||||
elif name == 'InstanceMonitoring':
|
||||
self.instance_monitoring = InstanceMonitoring(self)
|
||||
return self.instance_monitoring
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'InstanceType':
|
||||
self.instance_type = value
|
||||
elif name == 'LaunchConfigurationName':
|
||||
self.name = value
|
||||
elif name == 'KeyName':
|
||||
self.key_name = value
|
||||
elif name == 'ImageId':
|
||||
self.image_id = value
|
||||
elif name == 'CreatedTime':
|
||||
self.created_time = boto.utils.parse_ts(value)
|
||||
elif name == 'KernelId':
|
||||
self.kernel_id = value
|
||||
elif name == 'RamdiskId':
|
||||
self.ramdisk_id = value
|
||||
elif name == 'UserData':
|
||||
try:
|
||||
self.user_data = base64.b64decode(value)
|
||||
except TypeError:
|
||||
self.user_data = value
|
||||
elif name == 'LaunchConfigurationARN':
|
||||
self.launch_configuration_arn = value
|
||||
elif name == 'InstanceMonitoring':
|
||||
self.instance_monitoring = value
|
||||
elif name == 'SpotPrice':
|
||||
self.spot_price = float(value)
|
||||
elif name == 'IamInstanceProfile':
|
||||
self.instance_profile_name = value
|
||||
elif name == 'EbsOptimized':
|
||||
self.ebs_optimized = True if value.lower() == 'true' else False
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def delete(self):
|
||||
""" Delete this launch configuration. """
|
||||
return self.connection.delete_launch_configuration(self.name)
|
|
@ -1,173 +0,0 @@
|
|||
# Copyright (c) 2009-2010 Reza Lotun http://reza.lotun.name/
|
||||
# Copyright (c) 2011 Jann Kleen
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
from boto.resultset import ResultSet
|
||||
from boto.ec2.elb.listelement import ListElement
|
||||
|
||||
class Alarm(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.name = None
|
||||
self.alarm_arn = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'Alarm:%s' % self.name
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'AlarmName':
|
||||
self.name = value
|
||||
elif name == 'AlarmARN':
|
||||
self.alarm_arn = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class AdjustmentType(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.adjustment_types = ListElement([])
|
||||
|
||||
def __repr__(self):
|
||||
return 'AdjustmentType:%s' % self.adjustment_types
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'AdjustmentType':
|
||||
return self.adjustment_types
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
return
|
||||
|
||||
|
||||
class MetricCollectionTypes(object):
|
||||
class BaseType(object):
|
||||
arg = ''
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
self.val = None
|
||||
def __repr__(self):
|
||||
return '%s:%s' % (self.arg, self.val)
|
||||
def startElement(self, name, attrs, connection):
|
||||
return
|
||||
def endElement(self, name, value, connection):
|
||||
if name == self.arg:
|
||||
self.val = value
|
||||
class Metric(BaseType):
|
||||
arg = 'Metric'
|
||||
class Granularity(BaseType):
|
||||
arg = 'Granularity'
|
||||
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.metrics = []
|
||||
self.granularities = []
|
||||
|
||||
def __repr__(self):
|
||||
return 'MetricCollectionTypes:<%s, %s>' % (self.metrics, self.granularities)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'Granularities':
|
||||
self.granularities = ResultSet([('member', self.Granularity)])
|
||||
return self.granularities
|
||||
elif name == 'Metrics':
|
||||
self.metrics = ResultSet([('member', self.Metric)])
|
||||
return self.metrics
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
return
|
||||
|
||||
|
||||
class ScalingPolicy(object):
|
||||
def __init__(self, connection=None, **kwargs):
|
||||
"""
|
||||
Scaling Policy
|
||||
|
||||
:type name: str
|
||||
:param name: Name of scaling policy.
|
||||
|
||||
:type adjustment_type: str
|
||||
:param adjustment_type: Specifies the type of adjustment. Valid values are `ChangeInCapacity`, `ExactCapacity` and `PercentChangeInCapacity`.
|
||||
|
||||
:type as_name: str or int
|
||||
:param as_name: Name or ARN of the Auto Scaling Group.
|
||||
|
||||
:type scaling_adjustment: int
|
||||
:param scaling_adjustment: Value of adjustment (type specified in `adjustment_type`).
|
||||
|
||||
:type min_adjustment_step: int
|
||||
:param min_adjustment_step: Value of min adjustment step required to
|
||||
apply the scaling policy (only make sense when use `PercentChangeInCapacity` as adjustment_type.).
|
||||
|
||||
:type cooldown: int
|
||||
:param cooldown: Time (in seconds) before Alarm related Scaling Activities can start after the previous Scaling Activity ends.
|
||||
|
||||
"""
|
||||
self.name = kwargs.get('name', None)
|
||||
self.adjustment_type = kwargs.get('adjustment_type', None)
|
||||
self.as_name = kwargs.get('as_name', None)
|
||||
self.scaling_adjustment = kwargs.get('scaling_adjustment', None)
|
||||
self.cooldown = kwargs.get('cooldown', None)
|
||||
self.connection = connection
|
||||
self.min_adjustment_step = kwargs.get('min_adjustment_step', None)
|
||||
|
||||
def __repr__(self):
|
||||
return 'ScalingPolicy(%s group:%s adjustment:%s)' % (self.name,
|
||||
self.as_name,
|
||||
self.adjustment_type)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'Alarms':
|
||||
self.alarms = ResultSet([('member', Alarm)])
|
||||
return self.alarms
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'PolicyName':
|
||||
self.name = value
|
||||
elif name == 'AutoScalingGroupName':
|
||||
self.as_name = value
|
||||
elif name == 'PolicyARN':
|
||||
self.policy_arn = value
|
||||
elif name == 'ScalingAdjustment':
|
||||
self.scaling_adjustment = int(value)
|
||||
elif name == 'Cooldown':
|
||||
self.cooldown = int(value)
|
||||
elif name == 'AdjustmentType':
|
||||
self.adjustment_type = value
|
||||
elif name == 'MinAdjustmentStep':
|
||||
self.min_adjustment_step = int(value)
|
||||
|
||||
def delete(self):
|
||||
return self.connection.delete_policy(self.name, self.as_name)
|
||||
|
||||
|
||||
class TerminationPolicies(list):
|
||||
def __init__(self, connection=None, **kwargs):
|
||||
pass
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'member':
|
||||
self.append(value)
|
|
@ -1,38 +0,0 @@
|
|||
# Copyright (c) 2009 Reza Lotun http://reza.lotun.name/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
class Request(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.request_id = ''
|
||||
|
||||
def __repr__(self):
|
||||
return 'Request:%s' % self.request_id
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'RequestId':
|
||||
self.request_id = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
# Copyright (c) 2009-2010 Reza Lotun http://reza.lotun.name/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class ScheduledUpdateGroupAction(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.name = None
|
||||
self.action_arn = None
|
||||
self.as_group = None
|
||||
self.time = None
|
||||
self.start_time = None
|
||||
self.end_time = None
|
||||
self.recurrence = None
|
||||
self.desired_capacity = None
|
||||
self.max_size = None
|
||||
self.min_size = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'ScheduledUpdateGroupAction:%s' % self.name
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'DesiredCapacity':
|
||||
self.desired_capacity = value
|
||||
elif name == 'ScheduledActionName':
|
||||
self.name = value
|
||||
elif name == 'AutoScalingGroupName':
|
||||
self.as_group = value
|
||||
elif name == 'MaxSize':
|
||||
self.max_size = int(value)
|
||||
elif name == 'MinSize':
|
||||
self.min_size = int(value)
|
||||
elif name == 'ScheduledActionARN':
|
||||
self.action_arn = value
|
||||
elif name == 'Recurrence':
|
||||
self.recurrence = value
|
||||
elif name == 'Time':
|
||||
try:
|
||||
self.time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
except ValueError:
|
||||
self.time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
|
||||
elif name == 'StartTime':
|
||||
try:
|
||||
self.start_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
except ValueError:
|
||||
self.start_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
|
||||
elif name == 'EndTime':
|
||||
try:
|
||||
self.end_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
except ValueError:
|
||||
self.end_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
class Tag(object):
|
||||
"""
|
||||
A name/value tag on an AutoScalingGroup resource.
|
||||
|
||||
:ivar key: The key of the tag.
|
||||
:ivar value: The value of the tag.
|
||||
:ivar propagate_at_launch: Boolean value which specifies whether the
|
||||
new tag will be applied to instances launched after the tag is created.
|
||||
:ivar resource_id: The name of the autoscaling group.
|
||||
:ivar resource_type: The only supported resource type at this time
|
||||
is "auto-scaling-group".
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None, key=None, value=None,
|
||||
propagate_at_launch=False, resource_id=None,
|
||||
resource_type='auto-scaling-group'):
|
||||
self.connection = connection
|
||||
self.key = key
|
||||
self.value = value
|
||||
self.propagate_at_launch = propagate_at_launch
|
||||
self.resource_id = resource_id
|
||||
self.resource_type = resource_type
|
||||
|
||||
def __repr__(self):
|
||||
return 'Tag(%s=%s)' % (self.key, self.value)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Key':
|
||||
self.key = value
|
||||
elif name == 'Value':
|
||||
self.value = value
|
||||
elif name == 'PropagateAtLaunch':
|
||||
if value.lower() == 'true':
|
||||
self.propagate_at_launch = True
|
||||
else:
|
||||
self.propagate_at_launch = False
|
||||
elif name == 'ResourceId':
|
||||
self.resource_id = value
|
||||
elif name == 'ResourceType':
|
||||
self.resource_type = value
|
||||
|
||||
def build_params(self, params, i):
|
||||
"""
|
||||
Populates a dictionary with the name/value pairs necessary
|
||||
to identify this Tag in a request.
|
||||
"""
|
||||
prefix = 'Tags.member.%d.' % i
|
||||
params[prefix + 'ResourceId'] = self.resource_id
|
||||
params[prefix + 'ResourceType'] = self.resource_type
|
||||
params[prefix + 'Key'] = self.key
|
||||
params[prefix + 'Value'] = self.value
|
||||
if self.propagate_at_launch:
|
||||
params[prefix + 'PropagateAtLaunch'] = 'true'
|
||||
else:
|
||||
params[prefix + 'PropagateAtLaunch'] = 'false'
|
||||
|
||||
def delete(self):
|
||||
return self.connection.delete_tags([self])
|
|
@ -1,150 +0,0 @@
|
|||
# Copyright (c) 2009-2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
|
||||
class BlockDeviceType(object):
|
||||
"""
|
||||
Represents parameters for a block device.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
connection=None,
|
||||
ephemeral_name=None,
|
||||
no_device=False,
|
||||
volume_id=None,
|
||||
snapshot_id=None,
|
||||
status=None,
|
||||
attach_time=None,
|
||||
delete_on_termination=False,
|
||||
size=None,
|
||||
volume_type=None,
|
||||
iops=None):
|
||||
self.connection = connection
|
||||
self.ephemeral_name = ephemeral_name
|
||||
self.no_device = no_device
|
||||
self.volume_id = volume_id
|
||||
self.snapshot_id = snapshot_id
|
||||
self.status = status
|
||||
self.attach_time = attach_time
|
||||
self.delete_on_termination = delete_on_termination
|
||||
self.size = size
|
||||
self.volume_type = volume_type
|
||||
self.iops = iops
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'volumeId':
|
||||
self.volume_id = value
|
||||
elif name == 'virtualName':
|
||||
self.ephemeral_name = value
|
||||
elif name == 'NoDevice':
|
||||
self.no_device = (value == 'true')
|
||||
elif name == 'snapshotId':
|
||||
self.snapshot_id = value
|
||||
elif name == 'volumeSize':
|
||||
self.size = int(value)
|
||||
elif name == 'status':
|
||||
self.status = value
|
||||
elif name == 'attachTime':
|
||||
self.attach_time = value
|
||||
elif name == 'deleteOnTermination':
|
||||
self.delete_on_termination = (value == 'true')
|
||||
elif name == 'volumeType':
|
||||
self.volume_type = value
|
||||
elif name == 'iops':
|
||||
self.iops = int(value)
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
# for backwards compatibility
|
||||
EBSBlockDeviceType = BlockDeviceType
|
||||
|
||||
|
||||
class BlockDeviceMapping(dict):
|
||||
"""
|
||||
Represents a collection of BlockDeviceTypes when creating ec2 instances.
|
||||
|
||||
Example:
|
||||
dev_sda1 = BlockDeviceType()
|
||||
dev_sda1.size = 100 # change root volume to 100GB instead of default
|
||||
bdm = BlockDeviceMapping()
|
||||
bdm['/dev/sda1'] = dev_sda1
|
||||
reservation = image.run(..., block_device_map=bdm, ...)
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None):
|
||||
"""
|
||||
:type connection: :class:`boto.ec2.EC2Connection`
|
||||
:param connection: Optional connection.
|
||||
"""
|
||||
dict.__init__(self)
|
||||
self.connection = connection
|
||||
self.current_name = None
|
||||
self.current_value = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'ebs' or name == 'virtualName':
|
||||
self.current_value = BlockDeviceType(self)
|
||||
return self.current_value
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'device' or name == 'deviceName':
|
||||
self.current_name = value
|
||||
elif name == 'item':
|
||||
self[self.current_name] = self.current_value
|
||||
|
||||
def ec2_build_list_params(self, params, prefix=''):
|
||||
pre = '%sBlockDeviceMapping' % prefix
|
||||
return self._build_list_params(params, prefix=pre)
|
||||
|
||||
def autoscale_build_list_params(self, params, prefix=''):
|
||||
pre = '%sBlockDeviceMappings.member' % prefix
|
||||
return self._build_list_params(params, prefix=pre)
|
||||
|
||||
def _build_list_params(self, params, prefix=''):
|
||||
i = 1
|
||||
for dev_name in self:
|
||||
pre = '%s.%d' % (prefix, i)
|
||||
params['%s.DeviceName' % pre] = dev_name
|
||||
block_dev = self[dev_name]
|
||||
if block_dev.ephemeral_name:
|
||||
params['%s.VirtualName' % pre] = block_dev.ephemeral_name
|
||||
else:
|
||||
if block_dev.no_device:
|
||||
params['%s.NoDevice' % pre] = ''
|
||||
else:
|
||||
if block_dev.snapshot_id:
|
||||
params['%s.Ebs.SnapshotId' % pre] = block_dev.snapshot_id
|
||||
if block_dev.size:
|
||||
params['%s.Ebs.VolumeSize' % pre] = block_dev.size
|
||||
if block_dev.delete_on_termination:
|
||||
params['%s.Ebs.DeleteOnTermination' % pre] = 'true'
|
||||
else:
|
||||
params['%s.Ebs.DeleteOnTermination' % pre] = 'false'
|
||||
if block_dev.volume_type:
|
||||
params['%s.Ebs.VolumeType' % pre] = block_dev.volume_type
|
||||
if block_dev.iops is not None:
|
||||
params['%s.Ebs.Iops' % pre] = block_dev.iops
|
||||
i += 1
|
|
@ -1,78 +0,0 @@
|
|||
# Copyright (c) 2010 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
"""
|
||||
Represents an EC2 Bundle Task
|
||||
"""
|
||||
|
||||
from boto.ec2.ec2object import EC2Object
|
||||
|
||||
class BundleInstanceTask(EC2Object):
|
||||
|
||||
def __init__(self, connection=None):
|
||||
EC2Object.__init__(self, connection)
|
||||
self.id = None
|
||||
self.instance_id = None
|
||||
self.progress = None
|
||||
self.start_time = None
|
||||
self.state = None
|
||||
self.bucket = None
|
||||
self.prefix = None
|
||||
self.upload_policy = None
|
||||
self.upload_policy_signature = None
|
||||
self.update_time = None
|
||||
self.code = None
|
||||
self.message = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'BundleInstanceTask:%s' % self.id
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'bundleId':
|
||||
self.id = value
|
||||
elif name == 'instanceId':
|
||||
self.instance_id = value
|
||||
elif name == 'progress':
|
||||
self.progress = value
|
||||
elif name == 'startTime':
|
||||
self.start_time = value
|
||||
elif name == 'state':
|
||||
self.state = value
|
||||
elif name == 'bucket':
|
||||
self.bucket = value
|
||||
elif name == 'prefix':
|
||||
self.prefix = value
|
||||
elif name == 'uploadPolicy':
|
||||
self.upload_policy = value
|
||||
elif name == 'uploadPolicySignature':
|
||||
self.upload_policy_signature = value
|
||||
elif name == 'updateTime':
|
||||
self.update_time = value
|
||||
elif name == 'code':
|
||||
self.code = value
|
||||
elif name == 'message':
|
||||
self.message = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
import boto.ec2
|
||||
from boto.sdb.db.property import StringProperty, IntegerProperty
|
||||
from boto.manage import propget
|
||||
|
||||
InstanceTypes = ['m1.small', 'm1.large', 'm1.xlarge',
|
||||
'c1.medium', 'c1.xlarge', 'm2.xlarge',
|
||||
'm2.2xlarge', 'm2.4xlarge', 'cc1.4xlarge',
|
||||
't1.micro']
|
||||
|
||||
class BuyReservation(object):
|
||||
|
||||
def get_region(self, params):
|
||||
if not params.get('region', None):
|
||||
prop = StringProperty(name='region', verbose_name='EC2 Region',
|
||||
choices=boto.ec2.regions)
|
||||
params['region'] = propget.get(prop, choices=boto.ec2.regions)
|
||||
|
||||
def get_instance_type(self, params):
|
||||
if not params.get('instance_type', None):
|
||||
prop = StringProperty(name='instance_type', verbose_name='Instance Type',
|
||||
choices=InstanceTypes)
|
||||
params['instance_type'] = propget.get(prop)
|
||||
|
||||
def get_quantity(self, params):
|
||||
if not params.get('quantity', None):
|
||||
prop = IntegerProperty(name='quantity', verbose_name='Number of Instances')
|
||||
params['quantity'] = propget.get(prop)
|
||||
|
||||
def get_zone(self, params):
|
||||
if not params.get('zone', None):
|
||||
prop = StringProperty(name='zone', verbose_name='EC2 Availability Zone',
|
||||
choices=self.ec2.get_all_zones)
|
||||
params['zone'] = propget.get(prop)
|
||||
|
||||
def get(self, params):
|
||||
self.get_region(params)
|
||||
self.ec2 = params['region'].connect()
|
||||
self.get_instance_type(params)
|
||||
self.get_zone(params)
|
||||
self.get_quantity(params)
|
||||
|
||||
if __name__ == "__main__":
|
||||
obj = BuyReservation()
|
||||
params = {}
|
||||
obj.get(params)
|
||||
offerings = obj.ec2.get_all_reserved_instances_offerings(instance_type=params['instance_type'],
|
||||
availability_zone=params['zone'].name)
|
||||
print '\nThe following Reserved Instances Offerings are available:\n'
|
||||
for offering in offerings:
|
||||
offering.describe()
|
||||
prop = StringProperty(name='offering', verbose_name='Offering',
|
||||
choices=offerings)
|
||||
offering = propget.get(prop)
|
||||
print '\nYou have chosen this offering:'
|
||||
offering.describe()
|
||||
unit_price = float(offering.fixed_price)
|
||||
total_price = unit_price * params['quantity']
|
||||
print '!!! You are about to purchase %d of these offerings for a total of $%.2f !!!' % (params['quantity'], total_price)
|
||||
answer = raw_input('Are you sure you want to do this? If so, enter YES: ')
|
||||
if answer.strip().lower() == 'yes':
|
||||
offering.purchase(params['quantity'])
|
||||
else:
|
||||
print 'Purchase cancelled'
|
|
@ -1,604 +0,0 @@
|
|||
# Copyright (c) 2006-2011 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
"""
|
||||
This module provides an interface to the Elastic Compute Cloud (EC2)
|
||||
CloudWatch service from AWS.
|
||||
"""
|
||||
from boto.compat import json
|
||||
from boto.connection import AWSQueryConnection
|
||||
from boto.ec2.cloudwatch.metric import Metric
|
||||
from boto.ec2.cloudwatch.alarm import MetricAlarm, MetricAlarms, AlarmHistoryItem
|
||||
from boto.ec2.cloudwatch.datapoint import Datapoint
|
||||
from boto.regioninfo import RegionInfo
|
||||
import boto
|
||||
|
||||
RegionData = {
|
||||
'us-east-1': 'monitoring.us-east-1.amazonaws.com',
|
||||
'us-gov-west-1': 'monitoring.us-gov-west-1.amazonaws.com',
|
||||
'us-west-1': 'monitoring.us-west-1.amazonaws.com',
|
||||
'us-west-2': 'monitoring.us-west-2.amazonaws.com',
|
||||
'sa-east-1': 'monitoring.sa-east-1.amazonaws.com',
|
||||
'eu-west-1': 'monitoring.eu-west-1.amazonaws.com',
|
||||
'ap-northeast-1': 'monitoring.ap-northeast-1.amazonaws.com',
|
||||
'ap-southeast-1': 'monitoring.ap-southeast-1.amazonaws.com',
|
||||
'ap-southeast-2': 'monitoring.ap-southeast-2.amazonaws.com',
|
||||
}
|
||||
|
||||
|
||||
def regions():
|
||||
"""
|
||||
Get all available regions for the CloudWatch service.
|
||||
|
||||
:rtype: list
|
||||
:return: A list of :class:`boto.RegionInfo` instances
|
||||
"""
|
||||
regions = []
|
||||
for region_name in RegionData:
|
||||
region = RegionInfo(name=region_name,
|
||||
endpoint=RegionData[region_name],
|
||||
connection_cls=CloudWatchConnection)
|
||||
regions.append(region)
|
||||
return regions
|
||||
|
||||
|
||||
def connect_to_region(region_name, **kw_params):
|
||||
"""
|
||||
Given a valid region name, return a
|
||||
:class:`boto.ec2.cloudwatch.CloudWatchConnection`.
|
||||
|
||||
:param str region_name: The name of the region to connect to.
|
||||
|
||||
:rtype: :class:`boto.ec2.CloudWatchConnection` or ``None``
|
||||
:return: A connection to the given region, or None if an invalid region
|
||||
name is given
|
||||
"""
|
||||
for region in regions():
|
||||
if region.name == region_name:
|
||||
return region.connect(**kw_params)
|
||||
return None
|
||||
|
||||
|
||||
class CloudWatchConnection(AWSQueryConnection):
|
||||
|
||||
APIVersion = boto.config.get('Boto', 'cloudwatch_version', '2010-08-01')
|
||||
DefaultRegionName = boto.config.get('Boto', 'cloudwatch_region_name',
|
||||
'us-east-1')
|
||||
DefaultRegionEndpoint = boto.config.get('Boto',
|
||||
'cloudwatch_region_endpoint',
|
||||
'monitoring.us-east-1.amazonaws.com')
|
||||
|
||||
def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
|
||||
is_secure=True, port=None, proxy=None, proxy_port=None,
|
||||
proxy_user=None, proxy_pass=None, debug=0,
|
||||
https_connection_factory=None, region=None, path='/',
|
||||
security_token=None, validate_certs=True):
|
||||
"""
|
||||
Init method to create a new connection to EC2 Monitoring Service.
|
||||
|
||||
B{Note:} The host argument is overridden by the host specified in the
|
||||
boto configuration file.
|
||||
"""
|
||||
if not region:
|
||||
region = RegionInfo(self, self.DefaultRegionName,
|
||||
self.DefaultRegionEndpoint)
|
||||
self.region = region
|
||||
|
||||
# Ugly hack to get around both a bug in Python and a
|
||||
# misconfigured SSL cert for the eu-west-1 endpoint
|
||||
if self.region.name == 'eu-west-1':
|
||||
validate_certs = False
|
||||
|
||||
AWSQueryConnection.__init__(self, aws_access_key_id,
|
||||
aws_secret_access_key,
|
||||
is_secure, port, proxy, proxy_port,
|
||||
proxy_user, proxy_pass,
|
||||
self.region.endpoint, debug,
|
||||
https_connection_factory, path,
|
||||
security_token,
|
||||
validate_certs=validate_certs)
|
||||
|
||||
def _required_auth_capability(self):
|
||||
return ['ec2']
|
||||
|
||||
def build_dimension_param(self, dimension, params):
|
||||
prefix = 'Dimensions.member'
|
||||
i = 0
|
||||
for dim_name in dimension:
|
||||
dim_value = dimension[dim_name]
|
||||
if dim_value:
|
||||
if isinstance(dim_value, basestring):
|
||||
dim_value = [dim_value]
|
||||
for value in dim_value:
|
||||
params['%s.%d.Name' % (prefix, i+1)] = dim_name
|
||||
params['%s.%d.Value' % (prefix, i+1)] = value
|
||||
i += 1
|
||||
else:
|
||||
params['%s.%d.Name' % (prefix, i+1)] = dim_name
|
||||
i += 1
|
||||
|
||||
def build_list_params(self, params, items, label):
|
||||
if isinstance(items, basestring):
|
||||
items = [items]
|
||||
for index, item in enumerate(items):
|
||||
i = index + 1
|
||||
if isinstance(item, dict):
|
||||
for k, v in item.iteritems():
|
||||
params[label % (i, 'Name')] = k
|
||||
if v is not None:
|
||||
params[label % (i, 'Value')] = v
|
||||
else:
|
||||
params[label % i] = item
|
||||
|
||||
def build_put_params(self, params, name, value=None, timestamp=None,
|
||||
unit=None, dimensions=None, statistics=None):
|
||||
args = (name, value, unit, dimensions, statistics, timestamp)
|
||||
length = max(map(lambda a: len(a) if isinstance(a, list) else 1, args))
|
||||
|
||||
def aslist(a):
|
||||
if isinstance(a, list):
|
||||
if len(a) != length:
|
||||
raise Exception('Must specify equal number of elements; expected %d.' % length)
|
||||
return a
|
||||
return [a] * length
|
||||
|
||||
for index, (n, v, u, d, s, t) in enumerate(zip(*map(aslist, args))):
|
||||
metric_data = {'MetricName': n}
|
||||
|
||||
if timestamp:
|
||||
metric_data['Timestamp'] = t.isoformat()
|
||||
|
||||
if unit:
|
||||
metric_data['Unit'] = u
|
||||
|
||||
if dimensions:
|
||||
self.build_dimension_param(d, metric_data)
|
||||
|
||||
if statistics:
|
||||
metric_data['StatisticValues.Maximum'] = s['maximum']
|
||||
metric_data['StatisticValues.Minimum'] = s['minimum']
|
||||
metric_data['StatisticValues.SampleCount'] = s['samplecount']
|
||||
metric_data['StatisticValues.Sum'] = s['sum']
|
||||
if value != None:
|
||||
msg = 'You supplied a value and statistics for a ' + \
|
||||
'metric.Posting statistics and not value.'
|
||||
boto.log.warn(msg)
|
||||
elif value != None:
|
||||
metric_data['Value'] = v
|
||||
else:
|
||||
raise Exception('Must specify a value or statistics to put.')
|
||||
|
||||
for key, val in metric_data.iteritems():
|
||||
params['MetricData.member.%d.%s' % (index + 1, key)] = val
|
||||
|
||||
def get_metric_statistics(self, period, start_time, end_time, metric_name,
|
||||
namespace, statistics, dimensions=None,
|
||||
unit=None):
|
||||
"""
|
||||
Get time-series data for one or more statistics of a given metric.
|
||||
|
||||
:type period: integer
|
||||
:param period: The granularity, in seconds, of the returned datapoints.
|
||||
Period must be at least 60 seconds and must be a multiple
|
||||
of 60. The default value is 60.
|
||||
|
||||
:type start_time: datetime
|
||||
:param start_time: The time stamp to use for determining the
|
||||
first datapoint to return. The value specified is
|
||||
inclusive; results include datapoints with the time stamp
|
||||
specified.
|
||||
|
||||
:type end_time: datetime
|
||||
:param end_time: The time stamp to use for determining the
|
||||
last datapoint to return. The value specified is
|
||||
exclusive; results will include datapoints up to the time
|
||||
stamp specified.
|
||||
|
||||
:type metric_name: string
|
||||
:param metric_name: The metric name.
|
||||
|
||||
:type namespace: string
|
||||
:param namespace: The metric's namespace.
|
||||
|
||||
:type statistics: list
|
||||
:param statistics: A list of statistics names Valid values:
|
||||
Average | Sum | SampleCount | Maximum | Minimum
|
||||
|
||||
:type dimensions: dict
|
||||
:param dimensions: A dictionary of dimension key/values where
|
||||
the key is the dimension name and the value
|
||||
is either a scalar value or an iterator
|
||||
of values to be associated with that
|
||||
dimension.
|
||||
|
||||
:type unit: string
|
||||
:param unit: The unit for the metric. Value values are:
|
||||
Seconds | Microseconds | Milliseconds | Bytes | Kilobytes |
|
||||
Megabytes | Gigabytes | Terabytes | Bits | Kilobits |
|
||||
Megabits | Gigabits | Terabits | Percent | Count |
|
||||
Bytes/Second | Kilobytes/Second | Megabytes/Second |
|
||||
Gigabytes/Second | Terabytes/Second | Bits/Second |
|
||||
Kilobits/Second | Megabits/Second | Gigabits/Second |
|
||||
Terabits/Second | Count/Second | None
|
||||
|
||||
:rtype: list
|
||||
"""
|
||||
params = {'Period': period,
|
||||
'MetricName': metric_name,
|
||||
'Namespace': namespace,
|
||||
'StartTime': start_time.isoformat(),
|
||||
'EndTime': end_time.isoformat()}
|
||||
self.build_list_params(params, statistics, 'Statistics.member.%d')
|
||||
if dimensions:
|
||||
self.build_dimension_param(dimensions, params)
|
||||
if unit:
|
||||
params['Unit'] = unit
|
||||
return self.get_list('GetMetricStatistics', params,
|
||||
[('member', Datapoint)])
|
||||
|
||||
def list_metrics(self, next_token=None, dimensions=None,
|
||||
metric_name=None, namespace=None):
|
||||
"""
|
||||
Returns a list of the valid metrics for which there is recorded
|
||||
data available.
|
||||
|
||||
:type next_token: str
|
||||
:param next_token: A maximum of 500 metrics will be returned
|
||||
at one time. If more results are available, the ResultSet
|
||||
returned will contain a non-Null next_token attribute.
|
||||
Passing that token as a parameter to list_metrics will
|
||||
retrieve the next page of metrics.
|
||||
|
||||
:type dimensions: dict
|
||||
:param dimensions: A dictionary containing name/value
|
||||
pairs that will be used to filter the results. The key in
|
||||
the dictionary is the name of a Dimension. The value in
|
||||
the dictionary is either a scalar value of that Dimension
|
||||
name that you want to filter on, a list of values to
|
||||
filter on or None if you want all metrics with that
|
||||
Dimension name.
|
||||
|
||||
:type metric_name: str
|
||||
:param metric_name: The name of the Metric to filter against. If None,
|
||||
all Metric names will be returned.
|
||||
|
||||
:type namespace: str
|
||||
:param namespace: A Metric namespace to filter against (e.g. AWS/EC2).
|
||||
If None, Metrics from all namespaces will be returned.
|
||||
"""
|
||||
params = {}
|
||||
if next_token:
|
||||
params['NextToken'] = next_token
|
||||
if dimensions:
|
||||
self.build_dimension_param(dimensions, params)
|
||||
if metric_name:
|
||||
params['MetricName'] = metric_name
|
||||
if namespace:
|
||||
params['Namespace'] = namespace
|
||||
|
||||
return self.get_list('ListMetrics', params, [('member', Metric)])
|
||||
|
||||
def put_metric_data(self, namespace, name, value=None, timestamp=None,
|
||||
unit=None, dimensions=None, statistics=None):
|
||||
"""
|
||||
Publishes metric data points to Amazon CloudWatch. Amazon Cloudwatch
|
||||
associates the data points with the specified metric. If the specified
|
||||
metric does not exist, Amazon CloudWatch creates the metric. If a list
|
||||
is specified for some, but not all, of the arguments, the remaining
|
||||
arguments are repeated a corresponding number of times.
|
||||
|
||||
:type namespace: str
|
||||
:param namespace: The namespace of the metric.
|
||||
|
||||
:type name: str or list
|
||||
:param name: The name of the metric.
|
||||
|
||||
:type value: float or list
|
||||
:param value: The value for the metric.
|
||||
|
||||
:type timestamp: datetime or list
|
||||
:param timestamp: The time stamp used for the metric. If not specified,
|
||||
the default value is set to the time the metric data was received.
|
||||
|
||||
:type unit: string or list
|
||||
:param unit: The unit of the metric. Valid Values: Seconds |
|
||||
Microseconds | Milliseconds | Bytes | Kilobytes |
|
||||
Megabytes | Gigabytes | Terabytes | Bits | Kilobits |
|
||||
Megabits | Gigabits | Terabits | Percent | Count |
|
||||
Bytes/Second | Kilobytes/Second | Megabytes/Second |
|
||||
Gigabytes/Second | Terabytes/Second | Bits/Second |
|
||||
Kilobits/Second | Megabits/Second | Gigabits/Second |
|
||||
Terabits/Second | Count/Second | None
|
||||
|
||||
:type dimensions: dict
|
||||
:param dimensions: Add extra name value pairs to associate
|
||||
with the metric, i.e.:
|
||||
{'name1': value1, 'name2': (value2, value3)}
|
||||
|
||||
:type statistics: dict or list
|
||||
:param statistics: Use a statistic set instead of a value, for example::
|
||||
|
||||
{'maximum': 30, 'minimum': 1, 'samplecount': 100, 'sum': 10000}
|
||||
"""
|
||||
params = {'Namespace': namespace}
|
||||
self.build_put_params(params, name, value=value, timestamp=timestamp,
|
||||
unit=unit, dimensions=dimensions, statistics=statistics)
|
||||
|
||||
return self.get_status('PutMetricData', params, verb="POST")
|
||||
|
||||
def describe_alarms(self, action_prefix=None, alarm_name_prefix=None,
|
||||
alarm_names=None, max_records=None, state_value=None,
|
||||
next_token=None):
|
||||
"""
|
||||
Retrieves alarms with the specified names. If no name is specified, all
|
||||
alarms for the user are returned. Alarms can be retrieved by using only
|
||||
a prefix for the alarm name, the alarm state, or a prefix for any
|
||||
action.
|
||||
|
||||
:type action_prefix: string
|
||||
:param action_name: The action name prefix.
|
||||
|
||||
:type alarm_name_prefix: string
|
||||
:param alarm_name_prefix: The alarm name prefix. AlarmNames cannot
|
||||
be specified if this parameter is specified.
|
||||
|
||||
:type alarm_names: list
|
||||
:param alarm_names: A list of alarm names to retrieve information for.
|
||||
|
||||
:type max_records: int
|
||||
:param max_records: The maximum number of alarm descriptions
|
||||
to retrieve.
|
||||
|
||||
:type state_value: string
|
||||
:param state_value: The state value to be used in matching alarms.
|
||||
|
||||
:type next_token: string
|
||||
:param next_token: The token returned by a previous call to
|
||||
indicate that there is more data.
|
||||
|
||||
:rtype list
|
||||
"""
|
||||
params = {}
|
||||
if action_prefix:
|
||||
params['ActionPrefix'] = action_prefix
|
||||
if alarm_name_prefix:
|
||||
params['AlarmNamePrefix'] = alarm_name_prefix
|
||||
elif alarm_names:
|
||||
self.build_list_params(params, alarm_names, 'AlarmNames.member.%s')
|
||||
if max_records:
|
||||
params['MaxRecords'] = max_records
|
||||
if next_token:
|
||||
params['NextToken'] = next_token
|
||||
if state_value:
|
||||
params['StateValue'] = state_value
|
||||
|
||||
result = self.get_list('DescribeAlarms', params,
|
||||
[('MetricAlarms', MetricAlarms)])
|
||||
ret = result[0]
|
||||
ret.next_token = result.next_token
|
||||
return ret
|
||||
|
||||
def describe_alarm_history(self, alarm_name=None,
|
||||
start_date=None, end_date=None,
|
||||
max_records=None, history_item_type=None,
|
||||
next_token=None):
|
||||
"""
|
||||
Retrieves history for the specified alarm. Filter alarms by date range
|
||||
or item type. If an alarm name is not specified, Amazon CloudWatch
|
||||
returns histories for all of the owner's alarms.
|
||||
|
||||
Amazon CloudWatch retains the history of deleted alarms for a period of
|
||||
six weeks. If an alarm has been deleted, its history can still be
|
||||
queried.
|
||||
|
||||
:type alarm_name: string
|
||||
:param alarm_name: The name of the alarm.
|
||||
|
||||
:type start_date: datetime
|
||||
:param start_date: The starting date to retrieve alarm history.
|
||||
|
||||
:type end_date: datetime
|
||||
:param end_date: The starting date to retrieve alarm history.
|
||||
|
||||
:type history_item_type: string
|
||||
:param history_item_type: The type of alarm histories to retreive
|
||||
(ConfigurationUpdate | StateUpdate | Action)
|
||||
|
||||
:type max_records: int
|
||||
:param max_records: The maximum number of alarm descriptions
|
||||
to retrieve.
|
||||
|
||||
:type next_token: string
|
||||
:param next_token: The token returned by a previous call to indicate
|
||||
that there is more data.
|
||||
|
||||
:rtype list
|
||||
"""
|
||||
params = {}
|
||||
if alarm_name:
|
||||
params['AlarmName'] = alarm_name
|
||||
if start_date:
|
||||
params['StartDate'] = start_date.isoformat()
|
||||
if end_date:
|
||||
params['EndDate'] = end_date.isoformat()
|
||||
if history_item_type:
|
||||
params['HistoryItemType'] = history_item_type
|
||||
if max_records:
|
||||
params['MaxRecords'] = max_records
|
||||
if next_token:
|
||||
params['NextToken'] = next_token
|
||||
return self.get_list('DescribeAlarmHistory', params,
|
||||
[('member', AlarmHistoryItem)])
|
||||
|
||||
def describe_alarms_for_metric(self, metric_name, namespace, period=None,
|
||||
statistic=None, dimensions=None, unit=None):
|
||||
"""
|
||||
Retrieves all alarms for a single metric. Specify a statistic, period,
|
||||
or unit to filter the set of alarms further.
|
||||
|
||||
:type metric_name: string
|
||||
:param metric_name: The name of the metric
|
||||
|
||||
:type namespace: string
|
||||
:param namespace: The namespace of the metric.
|
||||
|
||||
:type period: int
|
||||
:param period: The period in seconds over which the statistic
|
||||
is applied.
|
||||
|
||||
:type statistic: string
|
||||
:param statistic: The statistic for the metric.
|
||||
|
||||
:param dimension_filters: A dictionary containing name/value
|
||||
pairs that will be used to filter the results. The key in
|
||||
the dictionary is the name of a Dimension. The value in
|
||||
the dictionary is either a scalar value of that Dimension
|
||||
name that you want to filter on, a list of values to
|
||||
filter on or None if you want all metrics with that
|
||||
Dimension name.
|
||||
|
||||
:type unit: string
|
||||
|
||||
:rtype list
|
||||
"""
|
||||
params = {'MetricName': metric_name,
|
||||
'Namespace': namespace}
|
||||
if period:
|
||||
params['Period'] = period
|
||||
if statistic:
|
||||
params['Statistic'] = statistic
|
||||
if dimensions:
|
||||
self.build_dimension_param(dimensions, params)
|
||||
if unit:
|
||||
params['Unit'] = unit
|
||||
return self.get_list('DescribeAlarmsForMetric', params,
|
||||
[('member', MetricAlarm)])
|
||||
|
||||
def put_metric_alarm(self, alarm):
|
||||
"""
|
||||
Creates or updates an alarm and associates it with the specified Amazon
|
||||
CloudWatch metric. Optionally, this operation can associate one or more
|
||||
Amazon Simple Notification Service resources with the alarm.
|
||||
|
||||
When this operation creates an alarm, the alarm state is immediately
|
||||
set to INSUFFICIENT_DATA. The alarm is evaluated and its StateValue is
|
||||
set appropriately. Any actions associated with the StateValue is then
|
||||
executed.
|
||||
|
||||
When updating an existing alarm, its StateValue is left unchanged.
|
||||
|
||||
:type alarm: boto.ec2.cloudwatch.alarm.MetricAlarm
|
||||
:param alarm: MetricAlarm object.
|
||||
"""
|
||||
params = {
|
||||
'AlarmName': alarm.name,
|
||||
'MetricName': alarm.metric,
|
||||
'Namespace': alarm.namespace,
|
||||
'Statistic': alarm.statistic,
|
||||
'ComparisonOperator': alarm.comparison,
|
||||
'Threshold': alarm.threshold,
|
||||
'EvaluationPeriods': alarm.evaluation_periods,
|
||||
'Period': alarm.period,
|
||||
}
|
||||
if alarm.actions_enabled is not None:
|
||||
params['ActionsEnabled'] = alarm.actions_enabled
|
||||
if alarm.alarm_actions:
|
||||
self.build_list_params(params, alarm.alarm_actions,
|
||||
'AlarmActions.member.%s')
|
||||
if alarm.description:
|
||||
params['AlarmDescription'] = alarm.description
|
||||
if alarm.dimensions:
|
||||
self.build_dimension_param(alarm.dimensions, params)
|
||||
if alarm.insufficient_data_actions:
|
||||
self.build_list_params(params, alarm.insufficient_data_actions,
|
||||
'InsufficientDataActions.member.%s')
|
||||
if alarm.ok_actions:
|
||||
self.build_list_params(params, alarm.ok_actions,
|
||||
'OKActions.member.%s')
|
||||
if alarm.unit:
|
||||
params['Unit'] = alarm.unit
|
||||
alarm.connection = self
|
||||
return self.get_status('PutMetricAlarm', params)
|
||||
create_alarm = put_metric_alarm
|
||||
update_alarm = put_metric_alarm
|
||||
|
||||
def delete_alarms(self, alarms):
|
||||
"""
|
||||
Deletes all specified alarms. In the event of an error, no
|
||||
alarms are deleted.
|
||||
|
||||
:type alarms: list
|
||||
:param alarms: List of alarm names.
|
||||
"""
|
||||
params = {}
|
||||
self.build_list_params(params, alarms, 'AlarmNames.member.%s')
|
||||
return self.get_status('DeleteAlarms', params)
|
||||
|
||||
def set_alarm_state(self, alarm_name, state_reason, state_value,
|
||||
state_reason_data=None):
|
||||
"""
|
||||
Temporarily sets the state of an alarm. When the updated StateValue
|
||||
differs from the previous value, the action configured for the
|
||||
appropriate state is invoked. This is not a permanent change. The next
|
||||
periodic alarm check (in about a minute) will set the alarm to its
|
||||
actual state.
|
||||
|
||||
:type alarm_name: string
|
||||
:param alarm_name: Descriptive name for alarm.
|
||||
|
||||
:type state_reason: string
|
||||
:param state_reason: Human readable reason.
|
||||
|
||||
:type state_value: string
|
||||
:param state_value: OK | ALARM | INSUFFICIENT_DATA
|
||||
|
||||
:type state_reason_data: string
|
||||
:param state_reason_data: Reason string (will be jsonified).
|
||||
"""
|
||||
params = {'AlarmName': alarm_name,
|
||||
'StateReason': state_reason,
|
||||
'StateValue': state_value}
|
||||
if state_reason_data:
|
||||
params['StateReasonData'] = json.dumps(state_reason_data)
|
||||
|
||||
return self.get_status('SetAlarmState', params)
|
||||
|
||||
def enable_alarm_actions(self, alarm_names):
|
||||
"""
|
||||
Enables actions for the specified alarms.
|
||||
|
||||
:type alarms: list
|
||||
:param alarms: List of alarm names.
|
||||
"""
|
||||
params = {}
|
||||
self.build_list_params(params, alarm_names, 'AlarmNames.member.%s')
|
||||
return self.get_status('EnableAlarmActions', params)
|
||||
|
||||
def disable_alarm_actions(self, alarm_names):
|
||||
"""
|
||||
Disables actions for the specified alarms.
|
||||
|
||||
:type alarms: list
|
||||
:param alarms: List of alarm names.
|
||||
"""
|
||||
params = {}
|
||||
self.build_list_params(params, alarm_names, 'AlarmNames.member.%s')
|
||||
return self.get_status('DisableAlarmActions', params)
|
|
@ -1,316 +0,0 @@
|
|||
# Copyright (c) 2010 Reza Lotun http://reza.lotun.name
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
from datetime import datetime
|
||||
from boto.resultset import ResultSet
|
||||
from boto.ec2.cloudwatch.listelement import ListElement
|
||||
from boto.ec2.cloudwatch.dimension import Dimension
|
||||
from boto.compat import json
|
||||
|
||||
|
||||
class MetricAlarms(list):
|
||||
def __init__(self, connection=None):
|
||||
"""
|
||||
Parses a list of MetricAlarms.
|
||||
"""
|
||||
list.__init__(self)
|
||||
self.connection = connection
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'member':
|
||||
metric_alarm = MetricAlarm(connection)
|
||||
self.append(metric_alarm)
|
||||
return metric_alarm
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
pass
|
||||
|
||||
|
||||
class MetricAlarm(object):
|
||||
|
||||
OK = 'OK'
|
||||
ALARM = 'ALARM'
|
||||
INSUFFICIENT_DATA = 'INSUFFICIENT_DATA'
|
||||
|
||||
_cmp_map = {
|
||||
'>=': 'GreaterThanOrEqualToThreshold',
|
||||
'>': 'GreaterThanThreshold',
|
||||
'<': 'LessThanThreshold',
|
||||
'<=': 'LessThanOrEqualToThreshold',
|
||||
}
|
||||
_rev_cmp_map = dict((v, k) for (k, v) in _cmp_map.iteritems())
|
||||
|
||||
def __init__(self, connection=None, name=None, metric=None,
|
||||
namespace=None, statistic=None, comparison=None,
|
||||
threshold=None, period=None, evaluation_periods=None,
|
||||
unit=None, description='', dimensions=None,
|
||||
alarm_actions=None, insufficient_data_actions=None,
|
||||
ok_actions=None):
|
||||
"""
|
||||
Creates a new Alarm.
|
||||
|
||||
:type name: str
|
||||
:param name: Name of alarm.
|
||||
|
||||
:type metric: str
|
||||
:param metric: Name of alarm's associated metric.
|
||||
|
||||
:type namespace: str
|
||||
:param namespace: The namespace for the alarm's metric.
|
||||
|
||||
:type statistic: str
|
||||
:param statistic: The statistic to apply to the alarm's associated
|
||||
metric.
|
||||
Valid values: SampleCount|Average|Sum|Minimum|Maximum
|
||||
|
||||
:type comparison: str
|
||||
:param comparison: Comparison used to compare statistic with threshold.
|
||||
Valid values: >= | > | < | <=
|
||||
|
||||
:type threshold: float
|
||||
:param threshold: The value against which the specified statistic
|
||||
is compared.
|
||||
|
||||
:type period: int
|
||||
:param period: The period in seconds over which teh specified
|
||||
statistic is applied.
|
||||
|
||||
:type evaluation_periods: int
|
||||
:param evaluation_periods: The number of periods over which data is
|
||||
compared to the specified threshold.
|
||||
|
||||
:type unit: str
|
||||
:param unit: Allowed Values are:
|
||||
Seconds|Microseconds|Milliseconds,
|
||||
Bytes|Kilobytes|Megabytes|Gigabytes|Terabytes,
|
||||
Bits|Kilobits|Megabits|Gigabits|Terabits,
|
||||
Percent|Count|
|
||||
Bytes/Second|Kilobytes/Second|Megabytes/Second|
|
||||
Gigabytes/Second|Terabytes/Second,
|
||||
Bits/Second|Kilobits/Second|Megabits/Second,
|
||||
Gigabits/Second|Terabits/Second|Count/Second|None
|
||||
|
||||
:type description: str
|
||||
:param description: Description of MetricAlarm
|
||||
|
||||
:type dimensions: list of dicts
|
||||
:param dimensions: Dimensions of alarm, such as:
|
||||
[{'InstanceId':['i-0123456,i-0123457']}]
|
||||
|
||||
:type alarm_actions: list of strs
|
||||
:param alarm_actions: A list of the ARNs of the actions to take in
|
||||
ALARM state
|
||||
|
||||
:type insufficient_data_actions: list of strs
|
||||
:param insufficient_data_actions: A list of the ARNs of the actions to
|
||||
take in INSUFFICIENT_DATA state
|
||||
|
||||
:type ok_actions: list of strs
|
||||
:param ok_actions: A list of the ARNs of the actions to take in OK state
|
||||
"""
|
||||
self.name = name
|
||||
self.connection = connection
|
||||
self.metric = metric
|
||||
self.namespace = namespace
|
||||
self.statistic = statistic
|
||||
if threshold is not None:
|
||||
self.threshold = float(threshold)
|
||||
else:
|
||||
self.threshold = None
|
||||
self.comparison = self._cmp_map.get(comparison)
|
||||
if period is not None:
|
||||
self.period = int(period)
|
||||
else:
|
||||
self.period = None
|
||||
if evaluation_periods is not None:
|
||||
self.evaluation_periods = int(evaluation_periods)
|
||||
else:
|
||||
self.evaluation_periods = None
|
||||
self.actions_enabled = None
|
||||
self.alarm_arn = None
|
||||
self.last_updated = None
|
||||
self.description = description
|
||||
self.dimensions = dimensions
|
||||
self.state_reason = None
|
||||
self.state_value = None
|
||||
self.unit = unit
|
||||
self.alarm_actions = alarm_actions
|
||||
self.insufficient_data_actions = insufficient_data_actions
|
||||
self.ok_actions = ok_actions
|
||||
|
||||
def __repr__(self):
|
||||
return 'MetricAlarm:%s[%s(%s) %s %s]' % (self.name, self.metric,
|
||||
self.statistic,
|
||||
self.comparison,
|
||||
self.threshold)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'AlarmActions':
|
||||
self.alarm_actions = ListElement()
|
||||
return self.alarm_actions
|
||||
elif name == 'InsufficientDataActions':
|
||||
self.insufficient_data_actions = ListElement()
|
||||
return self.insufficient_data_actions
|
||||
elif name == 'OKActions':
|
||||
self.ok_actions = ListElement()
|
||||
return self.ok_actions
|
||||
elif name == 'Dimensions':
|
||||
self.dimensions = Dimension()
|
||||
return self.dimensions
|
||||
else:
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'ActionsEnabled':
|
||||
self.actions_enabled = value
|
||||
elif name == 'AlarmArn':
|
||||
self.alarm_arn = value
|
||||
elif name == 'AlarmConfigurationUpdatedTimestamp':
|
||||
self.last_updated = value
|
||||
elif name == 'AlarmDescription':
|
||||
self.description = value
|
||||
elif name == 'AlarmName':
|
||||
self.name = value
|
||||
elif name == 'ComparisonOperator':
|
||||
setattr(self, 'comparison', self._rev_cmp_map[value])
|
||||
elif name == 'EvaluationPeriods':
|
||||
self.evaluation_periods = int(value)
|
||||
elif name == 'MetricName':
|
||||
self.metric = value
|
||||
elif name == 'Namespace':
|
||||
self.namespace = value
|
||||
elif name == 'Period':
|
||||
self.period = int(value)
|
||||
elif name == 'StateReason':
|
||||
self.state_reason = value
|
||||
elif name == 'StateValue':
|
||||
self.state_value = value
|
||||
elif name == 'Statistic':
|
||||
self.statistic = value
|
||||
elif name == 'Threshold':
|
||||
self.threshold = float(value)
|
||||
elif name == 'Unit':
|
||||
self.unit = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def set_state(self, value, reason, data=None):
|
||||
""" Temporarily sets the state of an alarm.
|
||||
|
||||
:type value: str
|
||||
:param value: OK | ALARM | INSUFFICIENT_DATA
|
||||
|
||||
:type reason: str
|
||||
:param reason: Reason alarm set (human readable).
|
||||
|
||||
:type data: str
|
||||
:param data: Reason data (will be jsonified).
|
||||
"""
|
||||
return self.connection.set_alarm_state(self.name, reason, value, data)
|
||||
|
||||
def update(self):
|
||||
return self.connection.update_alarm(self)
|
||||
|
||||
def enable_actions(self):
|
||||
return self.connection.enable_alarm_actions([self.name])
|
||||
|
||||
def disable_actions(self):
|
||||
return self.connection.disable_alarm_actions([self.name])
|
||||
|
||||
def describe_history(self, start_date=None, end_date=None, max_records=None,
|
||||
history_item_type=None, next_token=None):
|
||||
return self.connection.describe_alarm_history(self.name, start_date,
|
||||
end_date, max_records,
|
||||
history_item_type,
|
||||
next_token)
|
||||
|
||||
def add_alarm_action(self, action_arn=None):
|
||||
"""
|
||||
Adds an alarm action, represented as an SNS topic, to this alarm.
|
||||
What do do when alarm is triggered.
|
||||
|
||||
:type action_arn: str
|
||||
:param action_arn: SNS topics to which notification should be
|
||||
sent if the alarm goes to state ALARM.
|
||||
"""
|
||||
if not action_arn:
|
||||
return # Raise exception instead?
|
||||
self.actions_enabled = 'true'
|
||||
self.alarm_actions.append(action_arn)
|
||||
|
||||
def add_insufficient_data_action(self, action_arn=None):
|
||||
"""
|
||||
Adds an insufficient_data action, represented as an SNS topic, to
|
||||
this alarm. What to do when the insufficient_data state is reached.
|
||||
|
||||
:type action_arn: str
|
||||
:param action_arn: SNS topics to which notification should be
|
||||
sent if the alarm goes to state INSUFFICIENT_DATA.
|
||||
"""
|
||||
if not action_arn:
|
||||
return
|
||||
self.actions_enabled = 'true'
|
||||
self.insufficient_data_actions.append(action_arn)
|
||||
|
||||
def add_ok_action(self, action_arn=None):
|
||||
"""
|
||||
Adds an ok action, represented as an SNS topic, to this alarm. What
|
||||
to do when the ok state is reached.
|
||||
|
||||
:type action_arn: str
|
||||
:param action_arn: SNS topics to which notification should be
|
||||
sent if the alarm goes to state INSUFFICIENT_DATA.
|
||||
"""
|
||||
if not action_arn:
|
||||
return
|
||||
self.actions_enabled = 'true'
|
||||
self.ok_actions.append(action_arn)
|
||||
|
||||
def delete(self):
|
||||
self.connection.delete_alarms([self.name])
|
||||
|
||||
class AlarmHistoryItem(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
|
||||
def __repr__(self):
|
||||
return 'AlarmHistory:%s[%s at %s]' % (self.name, self.summary, self.timestamp)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'AlarmName':
|
||||
self.name = value
|
||||
elif name == 'HistoryData':
|
||||
self.data = json.loads(value)
|
||||
elif name == 'HistoryItemType':
|
||||
self.tem_type = value
|
||||
elif name == 'HistorySummary':
|
||||
self.summary = value
|
||||
elif name == 'Timestamp':
|
||||
try:
|
||||
self.timestamp = datetime.strptime(value,
|
||||
'%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
except ValueError:
|
||||
self.timestamp = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
from datetime import datetime
|
||||
|
||||
class Datapoint(dict):
|
||||
|
||||
def __init__(self, connection=None):
|
||||
dict.__init__(self)
|
||||
self.connection = connection
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name in ['Average', 'Maximum', 'Minimum', 'Sum', 'SampleCount']:
|
||||
self[name] = float(value)
|
||||
elif name == 'Timestamp':
|
||||
self[name] = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
|
||||
elif name != 'member':
|
||||
self[name] = value
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
# Copyright (c) 2006-2012 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
class Dimension(dict):
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Name':
|
||||
self._name = value
|
||||
elif name == 'Value':
|
||||
if self._name in self:
|
||||
self[self._name].append(value)
|
||||
else:
|
||||
self[self._name] = [value]
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
class ListElement(list):
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'member':
|
||||
self.append(value)
|
||||
|
||||
|
|
@ -1,175 +0,0 @@
|
|||
# Copyright (c) 2006-2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
|
||||
from boto.ec2.cloudwatch.alarm import MetricAlarm
|
||||
from boto.ec2.cloudwatch.dimension import Dimension
|
||||
|
||||
|
||||
class Metric(object):
|
||||
|
||||
Statistics = ['Minimum', 'Maximum', 'Sum', 'Average', 'SampleCount']
|
||||
Units = ['Seconds', 'Microseconds', 'Milliseconds', 'Bytes', 'Kilobytes',
|
||||
'Megabytes', 'Gigabytes', 'Terabytes', 'Bits', 'Kilobits',
|
||||
'Megabits', 'Gigabits', 'Terabits', 'Percent', 'Count',
|
||||
'Bytes/Second', 'Kilobytes/Second', 'Megabytes/Second',
|
||||
'Gigabytes/Second', 'Terabytes/Second', 'Bits/Second',
|
||||
'Kilobits/Second', 'Megabits/Second', 'Gigabits/Second',
|
||||
'Terabits/Second', 'Count/Second', None]
|
||||
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.name = None
|
||||
self.namespace = None
|
||||
self.dimensions = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'Metric:%s' % self.name
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'Dimensions':
|
||||
self.dimensions = Dimension()
|
||||
return self.dimensions
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'MetricName':
|
||||
self.name = value
|
||||
elif name == 'Namespace':
|
||||
self.namespace = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def query(self, start_time, end_time, statistics, unit=None, period=60):
|
||||
"""
|
||||
:type start_time: datetime
|
||||
:param start_time: The time stamp to use for determining the
|
||||
first datapoint to return. The value specified is
|
||||
inclusive; results include datapoints with the time stamp
|
||||
specified.
|
||||
|
||||
:type end_time: datetime
|
||||
:param end_time: The time stamp to use for determining the
|
||||
last datapoint to return. The value specified is
|
||||
exclusive; results will include datapoints up to the time
|
||||
stamp specified.
|
||||
|
||||
:type statistics: list
|
||||
:param statistics: A list of statistics names Valid values:
|
||||
Average | Sum | SampleCount | Maximum | Minimum
|
||||
|
||||
:type dimensions: dict
|
||||
:param dimensions: A dictionary of dimension key/values where
|
||||
the key is the dimension name and the value
|
||||
is either a scalar value or an iterator
|
||||
of values to be associated with that
|
||||
dimension.
|
||||
|
||||
:type unit: string
|
||||
:param unit: The unit for the metric. Value values are:
|
||||
Seconds | Microseconds | Milliseconds | Bytes | Kilobytes |
|
||||
Megabytes | Gigabytes | Terabytes | Bits | Kilobits |
|
||||
Megabits | Gigabits | Terabits | Percent | Count |
|
||||
Bytes/Second | Kilobytes/Second | Megabytes/Second |
|
||||
Gigabytes/Second | Terabytes/Second | Bits/Second |
|
||||
Kilobits/Second | Megabits/Second | Gigabits/Second |
|
||||
Terabits/Second | Count/Second | None
|
||||
|
||||
:type period: integer
|
||||
:param period: The granularity, in seconds, of the returned datapoints.
|
||||
Period must be at least 60 seconds and must be a multiple
|
||||
of 60. The default value is 60.
|
||||
|
||||
"""
|
||||
if not isinstance(statistics, list):
|
||||
statistics = [statistics]
|
||||
return self.connection.get_metric_statistics(period,
|
||||
start_time,
|
||||
end_time,
|
||||
self.name,
|
||||
self.namespace,
|
||||
statistics,
|
||||
self.dimensions,
|
||||
unit)
|
||||
|
||||
def create_alarm(self, name, comparison, threshold,
|
||||
period, evaluation_periods,
|
||||
statistic, enabled=True, description=None,
|
||||
dimensions=None, alarm_actions=None, ok_actions=None,
|
||||
insufficient_data_actions=None, unit=None):
|
||||
"""
|
||||
Creates or updates an alarm and associates it with this metric.
|
||||
Optionally, this operation can associate one or more
|
||||
Amazon Simple Notification Service resources with the alarm.
|
||||
|
||||
When this operation creates an alarm, the alarm state is immediately
|
||||
set to INSUFFICIENT_DATA. The alarm is evaluated and its StateValue is
|
||||
set appropriately. Any actions associated with the StateValue is then
|
||||
executed.
|
||||
|
||||
When updating an existing alarm, its StateValue is left unchanged.
|
||||
|
||||
:type alarm: boto.ec2.cloudwatch.alarm.MetricAlarm
|
||||
:param alarm: MetricAlarm object.
|
||||
"""
|
||||
if not dimensions:
|
||||
dimensions = self.dimensions
|
||||
alarm = MetricAlarm(self.connection, name, self.name,
|
||||
self.namespace, statistic, comparison,
|
||||
threshold, period, evaluation_periods,
|
||||
unit, description, dimensions,
|
||||
alarm_actions, insufficient_data_actions,
|
||||
ok_actions)
|
||||
if self.connection.put_metric_alarm(alarm):
|
||||
return alarm
|
||||
|
||||
def describe_alarms(self, period=None, statistic=None,
|
||||
dimensions=None, unit=None):
|
||||
"""
|
||||
Retrieves all alarms for this metric. Specify a statistic, period,
|
||||
or unit to filter the set of alarms further.
|
||||
|
||||
:type period: int
|
||||
:param period: The period in seconds over which the statistic
|
||||
is applied.
|
||||
|
||||
:type statistic: string
|
||||
:param statistic: The statistic for the metric.
|
||||
|
||||
:param dimension_filters: A dictionary containing name/value
|
||||
pairs that will be used to filter the results. The key in
|
||||
the dictionary is the name of a Dimension. The value in
|
||||
the dictionary is either a scalar value of that Dimension
|
||||
name that you want to filter on, a list of values to
|
||||
filter on or None if you want all metrics with that
|
||||
Dimension name.
|
||||
|
||||
:type unit: string
|
||||
|
||||
:rtype list
|
||||
"""
|
||||
return self.connection.describe_alarms_for_metric(self.name,
|
||||
self.namespace,
|
||||
period,
|
||||
statistic,
|
||||
dimensions,
|
||||
unit)
|
File diff suppressed because it is too large
Load Diff
|
@ -1,115 +0,0 @@
|
|||
# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2010, Eucalyptus Systems, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
"""
|
||||
Represents an EC2 Object
|
||||
"""
|
||||
from boto.ec2.tag import TagSet
|
||||
|
||||
class EC2Object(object):
|
||||
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
if self.connection and hasattr(self.connection, 'region'):
|
||||
self.region = connection.region
|
||||
else:
|
||||
self.region = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class TaggedEC2Object(EC2Object):
|
||||
"""
|
||||
Any EC2 resource that can be tagged should be represented
|
||||
by a Python object that subclasses this class. This class
|
||||
has the mechanism in place to handle the tagSet element in
|
||||
the Describe* responses. If tags are found, it will create
|
||||
a TagSet object and allow it to parse and collect the tags
|
||||
into a dict that is stored in the "tags" attribute of the
|
||||
object.
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None):
|
||||
EC2Object.__init__(self, connection)
|
||||
self.tags = TagSet()
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'tagSet':
|
||||
return self.tags
|
||||
else:
|
||||
return None
|
||||
|
||||
def add_tag(self, key, value='', dry_run=False):
|
||||
"""
|
||||
Add a tag to this object. Tag's are stored by AWS and can be used
|
||||
to organize and filter resources. Adding a tag involves a round-trip
|
||||
to the EC2 service.
|
||||
|
||||
:type key: str
|
||||
:param key: The key or name of the tag being stored.
|
||||
|
||||
:type value: str
|
||||
:param value: An optional value that can be stored with the tag.
|
||||
If you want only the tag name and no value, the
|
||||
value should be the empty string.
|
||||
"""
|
||||
status = self.connection.create_tags(
|
||||
[self.id],
|
||||
{key : value},
|
||||
dry_run=dry_run
|
||||
)
|
||||
if self.tags is None:
|
||||
self.tags = TagSet()
|
||||
self.tags[key] = value
|
||||
|
||||
def remove_tag(self, key, value=None, dry_run=False):
|
||||
"""
|
||||
Remove a tag from this object. Removing a tag involves a round-trip
|
||||
to the EC2 service.
|
||||
|
||||
:type key: str
|
||||
:param key: The key or name of the tag being stored.
|
||||
|
||||
:type value: str
|
||||
:param value: An optional value that can be stored with the tag.
|
||||
If a value is provided, it must match the value
|
||||
currently stored in EC2. If not, the tag will not
|
||||
be removed. If a value of None is provided, all
|
||||
tags with the specified name will be deleted.
|
||||
NOTE: There is an important distinction between
|
||||
a value of '' and a value of None.
|
||||
"""
|
||||
if value:
|
||||
tags = {key : value}
|
||||
else:
|
||||
tags = [key]
|
||||
status = self.connection.delete_tags(
|
||||
[self.id],
|
||||
tags,
|
||||
dry_run=dry_run
|
||||
)
|
||||
if key in self.tags:
|
||||
del self.tags[key]
|
|
@ -1,651 +0,0 @@
|
|||
# Copyright (c) 2006-2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
#
|
||||
"""
|
||||
This module provides an interface to the Elastic Compute Cloud (EC2)
|
||||
load balancing service from AWS.
|
||||
"""
|
||||
from boto.connection import AWSQueryConnection
|
||||
from boto.ec2.instanceinfo import InstanceInfo
|
||||
from boto.ec2.elb.loadbalancer import LoadBalancer, LoadBalancerZones
|
||||
from boto.ec2.elb.instancestate import InstanceState
|
||||
from boto.ec2.elb.healthcheck import HealthCheck
|
||||
from boto.ec2.elb.listelement import ListElement
|
||||
from boto.regioninfo import RegionInfo
|
||||
import boto
|
||||
|
||||
RegionData = {
|
||||
'us-east-1': 'elasticloadbalancing.us-east-1.amazonaws.com',
|
||||
'us-gov-west-1': 'elasticloadbalancing.us-gov-west-1.amazonaws.com',
|
||||
'us-west-1': 'elasticloadbalancing.us-west-1.amazonaws.com',
|
||||
'us-west-2': 'elasticloadbalancing.us-west-2.amazonaws.com',
|
||||
'sa-east-1': 'elasticloadbalancing.sa-east-1.amazonaws.com',
|
||||
'eu-west-1': 'elasticloadbalancing.eu-west-1.amazonaws.com',
|
||||
'ap-northeast-1': 'elasticloadbalancing.ap-northeast-1.amazonaws.com',
|
||||
'ap-southeast-1': 'elasticloadbalancing.ap-southeast-1.amazonaws.com',
|
||||
'ap-southeast-2': 'elasticloadbalancing.ap-southeast-2.amazonaws.com',
|
||||
}
|
||||
|
||||
|
||||
def regions():
|
||||
"""
|
||||
Get all available regions for the ELB service.
|
||||
|
||||
:rtype: list
|
||||
:return: A list of :class:`boto.RegionInfo` instances
|
||||
"""
|
||||
regions = []
|
||||
for region_name in RegionData:
|
||||
region = RegionInfo(name=region_name,
|
||||
endpoint=RegionData[region_name],
|
||||
connection_cls=ELBConnection)
|
||||
regions.append(region)
|
||||
return regions
|
||||
|
||||
|
||||
def connect_to_region(region_name, **kw_params):
|
||||
"""
|
||||
Given a valid region name, return a
|
||||
:class:`boto.ec2.elb.ELBConnection`.
|
||||
|
||||
:param str region_name: The name of the region to connect to.
|
||||
|
||||
:rtype: :class:`boto.ec2.ELBConnection` or ``None``
|
||||
:return: A connection to the given region, or None if an invalid region
|
||||
name is given
|
||||
"""
|
||||
for region in regions():
|
||||
if region.name == region_name:
|
||||
return region.connect(**kw_params)
|
||||
return None
|
||||
|
||||
|
||||
class ELBConnection(AWSQueryConnection):
|
||||
|
||||
APIVersion = boto.config.get('Boto', 'elb_version', '2012-06-01')
|
||||
DefaultRegionName = boto.config.get('Boto', 'elb_region_name', 'us-east-1')
|
||||
DefaultRegionEndpoint = boto.config.get('Boto', 'elb_region_endpoint',
|
||||
'elasticloadbalancing.us-east-1.amazonaws.com')
|
||||
|
||||
def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
|
||||
is_secure=True, port=None, proxy=None, proxy_port=None,
|
||||
proxy_user=None, proxy_pass=None, debug=0,
|
||||
https_connection_factory=None, region=None, path='/',
|
||||
security_token=None, validate_certs=True):
|
||||
"""
|
||||
Init method to create a new connection to EC2 Load Balancing Service.
|
||||
|
||||
.. note:: The region argument is overridden by the region specified in
|
||||
the boto configuration file.
|
||||
"""
|
||||
if not region:
|
||||
region = RegionInfo(self, self.DefaultRegionName,
|
||||
self.DefaultRegionEndpoint)
|
||||
self.region = region
|
||||
AWSQueryConnection.__init__(self, aws_access_key_id,
|
||||
aws_secret_access_key,
|
||||
is_secure, port, proxy, proxy_port,
|
||||
proxy_user, proxy_pass,
|
||||
self.region.endpoint, debug,
|
||||
https_connection_factory, path,
|
||||
security_token,
|
||||
validate_certs=validate_certs)
|
||||
|
||||
def _required_auth_capability(self):
|
||||
return ['ec2']
|
||||
|
||||
def build_list_params(self, params, items, label):
|
||||
if isinstance(items, str):
|
||||
items = [items]
|
||||
for index, item in enumerate(items):
|
||||
params[label % (index + 1)] = item
|
||||
|
||||
def get_all_load_balancers(self, load_balancer_names=None):
|
||||
"""
|
||||
Retrieve all load balancers associated with your account.
|
||||
|
||||
:type load_balancer_names: list
|
||||
:keyword load_balancer_names: An optional list of load balancer names.
|
||||
|
||||
:rtype: :py:class:`boto.resultset.ResultSet`
|
||||
:return: A ResultSet containing instances of
|
||||
:class:`boto.ec2.elb.loadbalancer.LoadBalancer`
|
||||
"""
|
||||
params = {}
|
||||
if load_balancer_names:
|
||||
self.build_list_params(params, load_balancer_names,
|
||||
'LoadBalancerNames.member.%d')
|
||||
return self.get_list('DescribeLoadBalancers', params,
|
||||
[('member', LoadBalancer)])
|
||||
|
||||
def create_load_balancer(self, name, zones, listeners=None, subnets=None,
|
||||
security_groups=None, scheme='internet-facing', complex_listeners=None):
|
||||
"""
|
||||
Create a new load balancer for your account. By default the load
|
||||
balancer will be created in EC2. To create a load balancer inside a
|
||||
VPC, parameter zones must be set to None and subnets must not be None.
|
||||
The load balancer will be automatically created under the VPC that
|
||||
contains the subnet(s) specified.
|
||||
|
||||
:type name: string
|
||||
:param name: The mnemonic name associated with the new load balancer
|
||||
|
||||
:type zones: List of strings
|
||||
:param zones: The names of the availability zone(s) to add.
|
||||
|
||||
:type listeners: List of tuples
|
||||
:param listeners: Each tuple contains three or four values,
|
||||
(LoadBalancerPortNumber, InstancePortNumber, Protocol,
|
||||
[SSLCertificateId]) where LoadBalancerPortNumber and
|
||||
InstancePortNumber are integer values between 1 and 65535,
|
||||
Protocol is a string containing either 'TCP', 'SSL', HTTP', or
|
||||
'HTTPS'; SSLCertificateID is the ARN of a AWS AIM
|
||||
certificate, and must be specified when doing HTTPS.
|
||||
|
||||
:type subnets: list of strings
|
||||
:param subnets: A list of subnet IDs in your VPC to attach to
|
||||
your LoadBalancer.
|
||||
|
||||
:type security_groups: list of strings
|
||||
:param security_groups: The security groups assigned to your
|
||||
LoadBalancer within your VPC.
|
||||
|
||||
:type scheme: string
|
||||
:param scheme: The type of a LoadBalancer. By default, Elastic
|
||||
Load Balancing creates an internet-facing LoadBalancer with
|
||||
a publicly resolvable DNS name, which resolves to public IP
|
||||
addresses.
|
||||
|
||||
Specify the value internal for this option to create an
|
||||
internal LoadBalancer with a DNS name that resolves to
|
||||
private IP addresses.
|
||||
|
||||
This option is only available for LoadBalancers attached
|
||||
to an Amazon VPC.
|
||||
|
||||
:type complex_listeners: List of tuples
|
||||
:param complex_listeners: Each tuple contains four or five values,
|
||||
(LoadBalancerPortNumber, InstancePortNumber, Protocol, InstanceProtocol,
|
||||
SSLCertificateId).
|
||||
|
||||
Where:
|
||||
- LoadBalancerPortNumber and InstancePortNumber are integer
|
||||
values between 1 and 65535
|
||||
- Protocol and InstanceProtocol is a string containing either 'TCP',
|
||||
'SSL', 'HTTP', or 'HTTPS'
|
||||
- SSLCertificateId is the ARN of an SSL certificate loaded into
|
||||
AWS IAM
|
||||
|
||||
:rtype: :class:`boto.ec2.elb.loadbalancer.LoadBalancer`
|
||||
:return: The newly created
|
||||
:class:`boto.ec2.elb.loadbalancer.LoadBalancer`
|
||||
"""
|
||||
if not listeners and not complex_listeners:
|
||||
# Must specify one of the two options
|
||||
return None
|
||||
|
||||
params = {'LoadBalancerName': name,
|
||||
'Scheme': scheme}
|
||||
|
||||
# Handle legacy listeners
|
||||
if listeners:
|
||||
for index, listener in enumerate(listeners):
|
||||
i = index + 1
|
||||
protocol = listener[2].upper()
|
||||
params['Listeners.member.%d.LoadBalancerPort' % i] = listener[0]
|
||||
params['Listeners.member.%d.InstancePort' % i] = listener[1]
|
||||
params['Listeners.member.%d.Protocol' % i] = listener[2]
|
||||
if protocol == 'HTTPS' or protocol == 'SSL':
|
||||
params['Listeners.member.%d.SSLCertificateId' % i] = listener[3]
|
||||
|
||||
# Handle the full listeners
|
||||
if complex_listeners:
|
||||
for index, listener in enumerate(complex_listeners):
|
||||
i = index + 1
|
||||
protocol = listener[2].upper()
|
||||
InstanceProtocol = listener[3].upper()
|
||||
params['Listeners.member.%d.LoadBalancerPort' % i] = listener[0]
|
||||
params['Listeners.member.%d.InstancePort' % i] = listener[1]
|
||||
params['Listeners.member.%d.Protocol' % i] = listener[2]
|
||||
params['Listeners.member.%d.InstanceProtocol' % i] = listener[3]
|
||||
if protocol == 'HTTPS' or protocol == 'SSL':
|
||||
params['Listeners.member.%d.SSLCertificateId' % i] = listener[4]
|
||||
|
||||
if zones:
|
||||
self.build_list_params(params, zones, 'AvailabilityZones.member.%d')
|
||||
|
||||
if subnets:
|
||||
self.build_list_params(params, subnets, 'Subnets.member.%d')
|
||||
|
||||
if security_groups:
|
||||
self.build_list_params(params, security_groups,
|
||||
'SecurityGroups.member.%d')
|
||||
|
||||
load_balancer = self.get_object('CreateLoadBalancer',
|
||||
params, LoadBalancer)
|
||||
load_balancer.name = name
|
||||
load_balancer.listeners = listeners
|
||||
load_balancer.availability_zones = zones
|
||||
load_balancer.subnets = subnets
|
||||
load_balancer.security_groups = security_groups
|
||||
return load_balancer
|
||||
|
||||
def create_load_balancer_listeners(self, name, listeners=None, complex_listeners=None):
|
||||
"""
|
||||
Creates a Listener (or group of listeners) for an existing
|
||||
Load Balancer
|
||||
|
||||
:type name: string
|
||||
:param name: The name of the load balancer to create the listeners for
|
||||
|
||||
:type listeners: List of tuples
|
||||
:param listeners: Each tuple contains three or four values,
|
||||
(LoadBalancerPortNumber, InstancePortNumber, Protocol,
|
||||
[SSLCertificateId]) where LoadBalancerPortNumber and
|
||||
InstancePortNumber are integer values between 1 and 65535,
|
||||
Protocol is a string containing either 'TCP', 'SSL', HTTP', or
|
||||
'HTTPS'; SSLCertificateID is the ARN of a AWS AIM
|
||||
certificate, and must be specified when doing HTTPS.
|
||||
|
||||
:type complex_listeners: List of tuples
|
||||
:param complex_listeners: Each tuple contains four or five values,
|
||||
(LoadBalancerPortNumber, InstancePortNumber, Protocol, InstanceProtocol,
|
||||
SSLCertificateId).
|
||||
|
||||
Where:
|
||||
- LoadBalancerPortNumber and InstancePortNumber are integer
|
||||
values between 1 and 65535
|
||||
- Protocol and InstanceProtocol is a string containing either 'TCP',
|
||||
'SSL', 'HTTP', or 'HTTPS'
|
||||
- SSLCertificateId is the ARN of an SSL certificate loaded into
|
||||
AWS IAM
|
||||
|
||||
:return: The status of the request
|
||||
"""
|
||||
if not listeners and not complex_listeners:
|
||||
# Must specify one of the two options
|
||||
return None
|
||||
|
||||
params = {'LoadBalancerName': name}
|
||||
|
||||
# Handle the simple listeners
|
||||
if listeners:
|
||||
for index, listener in enumerate(listeners):
|
||||
i = index + 1
|
||||
protocol = listener[2].upper()
|
||||
params['Listeners.member.%d.LoadBalancerPort' % i] = listener[0]
|
||||
params['Listeners.member.%d.InstancePort' % i] = listener[1]
|
||||
params['Listeners.member.%d.Protocol' % i] = listener[2]
|
||||
if protocol == 'HTTPS' or protocol == 'SSL':
|
||||
params['Listeners.member.%d.SSLCertificateId' % i] = listener[3]
|
||||
|
||||
# Handle the full listeners
|
||||
if complex_listeners:
|
||||
for index, listener in enumerate(complex_listeners):
|
||||
i = index + 1
|
||||
protocol = listener[2].upper()
|
||||
InstanceProtocol = listener[3].upper()
|
||||
params['Listeners.member.%d.LoadBalancerPort' % i] = listener[0]
|
||||
params['Listeners.member.%d.InstancePort' % i] = listener[1]
|
||||
params['Listeners.member.%d.Protocol' % i] = listener[2]
|
||||
params['Listeners.member.%d.InstanceProtocol' % i] = listener[3]
|
||||
if protocol == 'HTTPS' or protocol == 'SSL':
|
||||
params['Listeners.member.%d.SSLCertificateId' % i] = listener[4]
|
||||
|
||||
return self.get_status('CreateLoadBalancerListeners', params)
|
||||
|
||||
def delete_load_balancer(self, name):
|
||||
"""
|
||||
Delete a Load Balancer from your account.
|
||||
|
||||
:type name: string
|
||||
:param name: The name of the Load Balancer to delete
|
||||
"""
|
||||
params = {'LoadBalancerName': name}
|
||||
return self.get_status('DeleteLoadBalancer', params)
|
||||
|
||||
def delete_load_balancer_listeners(self, name, ports):
|
||||
"""
|
||||
Deletes a load balancer listener (or group of listeners)
|
||||
|
||||
:type name: string
|
||||
:param name: The name of the load balancer to create the listeners for
|
||||
|
||||
:type ports: List int
|
||||
:param ports: Each int represents the port on the ELB to be removed
|
||||
|
||||
:return: The status of the request
|
||||
"""
|
||||
params = {'LoadBalancerName': name}
|
||||
for index, port in enumerate(ports):
|
||||
params['LoadBalancerPorts.member.%d' % (index + 1)] = port
|
||||
return self.get_status('DeleteLoadBalancerListeners', params)
|
||||
|
||||
def enable_availability_zones(self, load_balancer_name, zones_to_add):
|
||||
"""
|
||||
Add availability zones to an existing Load Balancer
|
||||
All zones must be in the same region as the Load Balancer
|
||||
Adding zones that are already registered with the Load Balancer
|
||||
has no effect.
|
||||
|
||||
:type load_balancer_name: string
|
||||
:param load_balancer_name: The name of the Load Balancer
|
||||
|
||||
:type zones: List of strings
|
||||
:param zones: The name of the zone(s) to add.
|
||||
|
||||
:rtype: List of strings
|
||||
:return: An updated list of zones for this Load Balancer.
|
||||
|
||||
"""
|
||||
params = {'LoadBalancerName': load_balancer_name}
|
||||
self.build_list_params(params, zones_to_add,
|
||||
'AvailabilityZones.member.%d')
|
||||
obj = self.get_object('EnableAvailabilityZonesForLoadBalancer',
|
||||
params, LoadBalancerZones)
|
||||
return obj.zones
|
||||
|
||||
def disable_availability_zones(self, load_balancer_name, zones_to_remove):
|
||||
"""
|
||||
Remove availability zones from an existing Load Balancer.
|
||||
All zones must be in the same region as the Load Balancer.
|
||||
Removing zones that are not registered with the Load Balancer
|
||||
has no effect.
|
||||
You cannot remove all zones from an Load Balancer.
|
||||
|
||||
:type load_balancer_name: string
|
||||
:param load_balancer_name: The name of the Load Balancer
|
||||
|
||||
:type zones: List of strings
|
||||
:param zones: The name of the zone(s) to remove.
|
||||
|
||||
:rtype: List of strings
|
||||
:return: An updated list of zones for this Load Balancer.
|
||||
|
||||
"""
|
||||
params = {'LoadBalancerName': load_balancer_name}
|
||||
self.build_list_params(params, zones_to_remove,
|
||||
'AvailabilityZones.member.%d')
|
||||
obj = self.get_object('DisableAvailabilityZonesForLoadBalancer',
|
||||
params, LoadBalancerZones)
|
||||
return obj.zones
|
||||
|
||||
def register_instances(self, load_balancer_name, instances):
|
||||
"""
|
||||
Add new Instances to an existing Load Balancer.
|
||||
|
||||
:type load_balancer_name: string
|
||||
:param load_balancer_name: The name of the Load Balancer
|
||||
|
||||
:type instances: List of strings
|
||||
:param instances: The instance ID's of the EC2 instances to add.
|
||||
|
||||
:rtype: List of strings
|
||||
:return: An updated list of instances for this Load Balancer.
|
||||
|
||||
"""
|
||||
params = {'LoadBalancerName': load_balancer_name}
|
||||
self.build_list_params(params, instances,
|
||||
'Instances.member.%d.InstanceId')
|
||||
return self.get_list('RegisterInstancesWithLoadBalancer',
|
||||
params, [('member', InstanceInfo)])
|
||||
|
||||
def deregister_instances(self, load_balancer_name, instances):
|
||||
"""
|
||||
Remove Instances from an existing Load Balancer.
|
||||
|
||||
:type load_balancer_name: string
|
||||
:param load_balancer_name: The name of the Load Balancer
|
||||
|
||||
:type instances: List of strings
|
||||
:param instances: The instance ID's of the EC2 instances to remove.
|
||||
|
||||
:rtype: List of strings
|
||||
:return: An updated list of instances for this Load Balancer.
|
||||
|
||||
"""
|
||||
params = {'LoadBalancerName': load_balancer_name}
|
||||
self.build_list_params(params, instances,
|
||||
'Instances.member.%d.InstanceId')
|
||||
return self.get_list('DeregisterInstancesFromLoadBalancer',
|
||||
params, [('member', InstanceInfo)])
|
||||
|
||||
def describe_instance_health(self, load_balancer_name, instances=None):
|
||||
"""
|
||||
Get current state of all Instances registered to an Load Balancer.
|
||||
|
||||
:type load_balancer_name: string
|
||||
:param load_balancer_name: The name of the Load Balancer
|
||||
|
||||
:type instances: List of strings
|
||||
:param instances: The instance ID's of the EC2 instances
|
||||
to return status for. If not provided,
|
||||
the state of all instances will be returned.
|
||||
|
||||
:rtype: List of :class:`boto.ec2.elb.instancestate.InstanceState`
|
||||
:return: list of state info for instances in this Load Balancer.
|
||||
|
||||
"""
|
||||
params = {'LoadBalancerName': load_balancer_name}
|
||||
if instances:
|
||||
self.build_list_params(params, instances,
|
||||
'Instances.member.%d.InstanceId')
|
||||
return self.get_list('DescribeInstanceHealth', params,
|
||||
[('member', InstanceState)])
|
||||
|
||||
def configure_health_check(self, name, health_check):
|
||||
"""
|
||||
Define a health check for the EndPoints.
|
||||
|
||||
:type name: string
|
||||
:param name: The mnemonic name associated with the load balancer
|
||||
|
||||
:type health_check: :class:`boto.ec2.elb.healthcheck.HealthCheck`
|
||||
:param health_check: A HealthCheck object populated with the desired
|
||||
values.
|
||||
|
||||
:rtype: :class:`boto.ec2.elb.healthcheck.HealthCheck`
|
||||
:return: The updated :class:`boto.ec2.elb.healthcheck.HealthCheck`
|
||||
"""
|
||||
params = {'LoadBalancerName': name,
|
||||
'HealthCheck.Timeout': health_check.timeout,
|
||||
'HealthCheck.Target': health_check.target,
|
||||
'HealthCheck.Interval': health_check.interval,
|
||||
'HealthCheck.UnhealthyThreshold': health_check.unhealthy_threshold,
|
||||
'HealthCheck.HealthyThreshold': health_check.healthy_threshold}
|
||||
return self.get_object('ConfigureHealthCheck', params, HealthCheck)
|
||||
|
||||
def set_lb_listener_SSL_certificate(self, lb_name, lb_port,
|
||||
ssl_certificate_id):
|
||||
"""
|
||||
Sets the certificate that terminates the specified listener's SSL
|
||||
connections. The specified certificate replaces any prior certificate
|
||||
that was used on the same LoadBalancer and port.
|
||||
"""
|
||||
params = {'LoadBalancerName': lb_name,
|
||||
'LoadBalancerPort': lb_port,
|
||||
'SSLCertificateId': ssl_certificate_id}
|
||||
return self.get_status('SetLoadBalancerListenerSSLCertificate', params)
|
||||
|
||||
def create_app_cookie_stickiness_policy(self, name, lb_name, policy_name):
|
||||
"""
|
||||
Generates a stickiness policy with sticky session lifetimes that follow
|
||||
that of an application-generated cookie. This policy can only be
|
||||
associated with HTTP listeners.
|
||||
|
||||
This policy is similar to the policy created by
|
||||
CreateLBCookieStickinessPolicy, except that the lifetime of the special
|
||||
Elastic Load Balancing cookie follows the lifetime of the
|
||||
application-generated cookie specified in the policy configuration. The
|
||||
load balancer only inserts a new stickiness cookie when the application
|
||||
response includes a new application cookie.
|
||||
|
||||
If the application cookie is explicitly removed or expires, the session
|
||||
stops being sticky until a new application cookie is issued.
|
||||
"""
|
||||
params = {'CookieName': name,
|
||||
'LoadBalancerName': lb_name,
|
||||
'PolicyName': policy_name}
|
||||
return self.get_status('CreateAppCookieStickinessPolicy', params)
|
||||
|
||||
def create_lb_cookie_stickiness_policy(self, cookie_expiration_period,
|
||||
lb_name, policy_name):
|
||||
"""
|
||||
Generates a stickiness policy with sticky session lifetimes controlled
|
||||
by the lifetime of the browser (user-agent) or a specified expiration
|
||||
period. This policy can only be associated only with HTTP listeners.
|
||||
|
||||
When a load balancer implements this policy, the load balancer uses a
|
||||
special cookie to track the backend server instance for each request.
|
||||
When the load balancer receives a request, it first checks to see if
|
||||
this cookie is present in the request. If so, the load balancer sends
|
||||
the request to the application server specified in the cookie. If not,
|
||||
the load balancer sends the request to a server that is chosen based on
|
||||
the existing load balancing algorithm.
|
||||
|
||||
A cookie is inserted into the response for binding subsequent requests
|
||||
from the same user to that server. The validity of the cookie is based
|
||||
on the cookie expiration time, which is specified in the policy
|
||||
configuration.
|
||||
|
||||
None may be passed for cookie_expiration_period.
|
||||
"""
|
||||
params = {'LoadBalancerName': lb_name,
|
||||
'PolicyName': policy_name}
|
||||
if cookie_expiration_period is not None:
|
||||
params['CookieExpirationPeriod'] = cookie_expiration_period
|
||||
return self.get_status('CreateLBCookieStickinessPolicy', params)
|
||||
|
||||
def create_lb_policy(self, lb_name, policy_name, policy_type, policy_attributes):
|
||||
"""
|
||||
Creates a new policy that contais the necessary attributes depending on
|
||||
the policy type. Policies are settings that are saved for your load
|
||||
balancer and that can be applied to the front-end listener, or
|
||||
the back-end application server.
|
||||
"""
|
||||
params = {'LoadBalancerName': lb_name,
|
||||
'PolicyName': policy_name,
|
||||
'PolicyTypeName': policy_type}
|
||||
for index, (name, value) in enumerate(policy_attributes.iteritems(), 1):
|
||||
params['PolicyAttributes.member.%d.AttributeName' % index] = name
|
||||
params['PolicyAttributes.member.%d.AttributeValue' % index] = value
|
||||
else:
|
||||
params['PolicyAttributes'] = ''
|
||||
return self.get_status('CreateLoadBalancerPolicy', params)
|
||||
|
||||
def delete_lb_policy(self, lb_name, policy_name):
|
||||
"""
|
||||
Deletes a policy from the LoadBalancer. The specified policy must not
|
||||
be enabled for any listeners.
|
||||
"""
|
||||
params = {'LoadBalancerName': lb_name,
|
||||
'PolicyName': policy_name}
|
||||
return self.get_status('DeleteLoadBalancerPolicy', params)
|
||||
|
||||
def set_lb_policies_of_listener(self, lb_name, lb_port, policies):
|
||||
"""
|
||||
Associates, updates, or disables a policy with a listener on the load
|
||||
balancer. Currently only zero (0) or one (1) policy can be associated
|
||||
with a listener.
|
||||
"""
|
||||
params = {'LoadBalancerName': lb_name,
|
||||
'LoadBalancerPort': lb_port}
|
||||
self.build_list_params(params, policies, 'PolicyNames.member.%d')
|
||||
return self.get_status('SetLoadBalancerPoliciesOfListener', params)
|
||||
|
||||
def set_lb_policies_of_backend_server(self, lb_name, instance_port, policies):
|
||||
"""
|
||||
Replaces the current set of policies associated with a port on which
|
||||
the back-end server is listening with a new set of policies.
|
||||
"""
|
||||
params = {'LoadBalancerName': lb_name,
|
||||
'InstancePort': instance_port}
|
||||
if policies:
|
||||
self.build_list_params(params, policies, 'PolicyNames.member.%d')
|
||||
else:
|
||||
params['PolicyNames'] = ''
|
||||
return self.get_status('SetLoadBalancerPoliciesForBackendServer', params)
|
||||
|
||||
def apply_security_groups_to_lb(self, name, security_groups):
|
||||
"""
|
||||
Applies security groups to the load balancer.
|
||||
Applying security groups that are already registered with the
|
||||
Load Balancer has no effect.
|
||||
|
||||
:type name: string
|
||||
:param name: The name of the Load Balancer
|
||||
|
||||
:type security_groups: List of strings
|
||||
:param security_groups: The name of the security group(s) to add.
|
||||
|
||||
:rtype: List of strings
|
||||
:return: An updated list of security groups for this Load Balancer.
|
||||
|
||||
"""
|
||||
params = {'LoadBalancerName': name}
|
||||
self.build_list_params(params, security_groups,
|
||||
'SecurityGroups.member.%d')
|
||||
return self.get_list('ApplySecurityGroupsToLoadBalancer',
|
||||
params, None)
|
||||
|
||||
def attach_lb_to_subnets(self, name, subnets):
|
||||
"""
|
||||
Attaches load balancer to one or more subnets.
|
||||
Attaching subnets that are already registered with the
|
||||
Load Balancer has no effect.
|
||||
|
||||
:type name: string
|
||||
:param name: The name of the Load Balancer
|
||||
|
||||
:type subnets: List of strings
|
||||
:param subnets: The name of the subnet(s) to add.
|
||||
|
||||
:rtype: List of strings
|
||||
:return: An updated list of subnets for this Load Balancer.
|
||||
|
||||
"""
|
||||
params = {'LoadBalancerName': name}
|
||||
self.build_list_params(params, subnets,
|
||||
'Subnets.member.%d')
|
||||
return self.get_list('AttachLoadBalancerToSubnets',
|
||||
params, None)
|
||||
|
||||
def detach_lb_from_subnets(self, name, subnets):
|
||||
"""
|
||||
Detaches load balancer from one or more subnets.
|
||||
|
||||
:type name: string
|
||||
:param name: The name of the Load Balancer
|
||||
|
||||
:type subnets: List of strings
|
||||
:param subnets: The name of the subnet(s) to detach.
|
||||
|
||||
:rtype: List of strings
|
||||
:return: An updated list of subnets for this Load Balancer.
|
||||
|
||||
"""
|
||||
params = {'LoadBalancerName': name}
|
||||
self.build_list_params(params, subnets,
|
||||
'Subnets.member.%d')
|
||||
return self.get_list('DetachLoadBalancerFromSubnets',
|
||||
params, None)
|
|
@ -1,89 +0,0 @@
|
|||
# Copyright (c) 2006-2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
class HealthCheck(object):
|
||||
"""
|
||||
Represents an EC2 Access Point Health Check. See
|
||||
:ref:`elb-configuring-a-health-check` for a walkthrough on configuring
|
||||
load balancer health checks.
|
||||
"""
|
||||
def __init__(self, access_point=None, interval=30, target=None,
|
||||
healthy_threshold=3, timeout=5, unhealthy_threshold=5):
|
||||
"""
|
||||
:ivar str access_point: The name of the load balancer this
|
||||
health check is associated with.
|
||||
:ivar int interval: Specifies how many seconds there are between
|
||||
health checks.
|
||||
:ivar str target: Determines what to check on an instance. See the
|
||||
Amazon HealthCheck_ documentation for possible Target values.
|
||||
|
||||
.. _HealthCheck: http://docs.amazonwebservices.com/ElasticLoadBalancing/latest/APIReference/API_HealthCheck.html
|
||||
"""
|
||||
self.access_point = access_point
|
||||
self.interval = interval
|
||||
self.target = target
|
||||
self.healthy_threshold = healthy_threshold
|
||||
self.timeout = timeout
|
||||
self.unhealthy_threshold = unhealthy_threshold
|
||||
|
||||
def __repr__(self):
|
||||
return 'HealthCheck:%s' % self.target
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Interval':
|
||||
self.interval = int(value)
|
||||
elif name == 'Target':
|
||||
self.target = value
|
||||
elif name == 'HealthyThreshold':
|
||||
self.healthy_threshold = int(value)
|
||||
elif name == 'Timeout':
|
||||
self.timeout = int(value)
|
||||
elif name == 'UnhealthyThreshold':
|
||||
self.unhealthy_threshold = int(value)
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
In the case where you have accessed an existing health check on a
|
||||
load balancer, this method applies this instance's health check
|
||||
values to the load balancer it is attached to.
|
||||
|
||||
.. note:: This method will not do anything if the :py:attr:`access_point`
|
||||
attribute isn't set, as is the case with a newly instantiated
|
||||
HealthCheck instance.
|
||||
"""
|
||||
if not self.access_point:
|
||||
return
|
||||
|
||||
new_hc = self.connection.configure_health_check(self.access_point,
|
||||
self)
|
||||
self.interval = new_hc.interval
|
||||
self.target = new_hc.target
|
||||
self.healthy_threshold = new_hc.healthy_threshold
|
||||
self.unhealthy_threshold = new_hc.unhealthy_threshold
|
||||
self.timeout = new_hc.timeout
|
|
@ -1,62 +0,0 @@
|
|||
# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
class InstanceState(object):
|
||||
"""
|
||||
Represents the state of an EC2 Load Balancer Instance
|
||||
"""
|
||||
|
||||
def __init__(self, load_balancer=None, description=None,
|
||||
state=None, instance_id=None, reason_code=None):
|
||||
"""
|
||||
:ivar boto.ec2.elb.loadbalancer.LoadBalancer load_balancer: The
|
||||
load balancer this instance is registered to.
|
||||
:ivar str description: A description of the instance.
|
||||
:ivar str instance_id: The EC2 instance ID.
|
||||
:ivar str reason_code: Provides information about the cause of
|
||||
an OutOfService instance. Specifically, it indicates whether the
|
||||
cause is Elastic Load Balancing or the instance behind the
|
||||
LoadBalancer.
|
||||
:ivar str state: Specifies the current state of the instance.
|
||||
"""
|
||||
self.load_balancer = load_balancer
|
||||
self.description = description
|
||||
self.state = state
|
||||
self.instance_id = instance_id
|
||||
self.reason_code = reason_code
|
||||
|
||||
def __repr__(self):
|
||||
return 'InstanceState:(%s,%s)' % (self.instance_id, self.state)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'Description':
|
||||
self.description = value
|
||||
elif name == 'State':
|
||||
self.state = value
|
||||
elif name == 'InstanceId':
|
||||
self.instance_id = value
|
||||
elif name == 'ReasonCode':
|
||||
self.reason_code = value
|
||||
else:
|
||||
setattr(self, name, value)
|
|
@ -1,36 +0,0 @@
|
|||
# Copyright (c) 2006-2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
class ListElement(list):
|
||||
"""
|
||||
A :py:class:`list` subclass that has some additional methods
|
||||
for interacting with Amazon's XML API.
|
||||
"""
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'member':
|
||||
self.append(value)
|
|
@ -1,85 +0,0 @@
|
|||
# Copyright (c) 2006-2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
from boto.ec2.elb.listelement import ListElement
|
||||
|
||||
|
||||
class Listener(object):
|
||||
"""
|
||||
Represents an EC2 Load Balancer Listener tuple
|
||||
"""
|
||||
|
||||
def __init__(self, load_balancer=None, load_balancer_port=0,
|
||||
instance_port=0, protocol='', ssl_certificate_id=None, instance_protocol=None):
|
||||
self.load_balancer = load_balancer
|
||||
self.load_balancer_port = load_balancer_port
|
||||
self.instance_port = instance_port
|
||||
self.protocol = protocol
|
||||
self.instance_protocol = instance_protocol
|
||||
self.ssl_certificate_id = ssl_certificate_id
|
||||
self.policy_names = ListElement()
|
||||
|
||||
def __repr__(self):
|
||||
r = "(%d, %d, '%s'" % (self.load_balancer_port, self.instance_port, self.protocol)
|
||||
if self.instance_protocol:
|
||||
r += ", '%s'" % self.instance_protocol
|
||||
if self.ssl_certificate_id:
|
||||
r += ', %s' % (self.ssl_certificate_id)
|
||||
r += ')'
|
||||
return r
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'PolicyNames':
|
||||
return self.policy_names
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'LoadBalancerPort':
|
||||
self.load_balancer_port = int(value)
|
||||
elif name == 'InstancePort':
|
||||
self.instance_port = int(value)
|
||||
elif name == 'InstanceProtocol':
|
||||
self.instance_protocol = value
|
||||
elif name == 'Protocol':
|
||||
self.protocol = value
|
||||
elif name == 'SSLCertificateId':
|
||||
self.ssl_certificate_id = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def get_tuple(self):
|
||||
return self.load_balancer_port, self.instance_port, self.protocol
|
||||
|
||||
def get_complex_tuple(self):
|
||||
return self.load_balancer_port, self.instance_port, self.protocol, self.instance_protocol
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key == 0:
|
||||
return self.load_balancer_port
|
||||
if key == 1:
|
||||
return self.instance_port
|
||||
if key == 2:
|
||||
return self.protocol
|
||||
if key == 4:
|
||||
return self.instance_protocol
|
||||
raise KeyError
|
|
@ -1,363 +0,0 @@
|
|||
# Copyright (c) 2006-2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
from boto.ec2.elb.healthcheck import HealthCheck
|
||||
from boto.ec2.elb.listener import Listener
|
||||
from boto.ec2.elb.listelement import ListElement
|
||||
from boto.ec2.elb.policies import Policies, OtherPolicy
|
||||
from boto.ec2.elb.securitygroup import SecurityGroup
|
||||
from boto.ec2.instanceinfo import InstanceInfo
|
||||
from boto.resultset import ResultSet
|
||||
|
||||
|
||||
class Backend(object):
|
||||
"""Backend server description"""
|
||||
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.instance_port = None
|
||||
self.policies = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'Backend(%r:%r)' % (self.instance_port, self.policies)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'PolicyNames':
|
||||
self.policies = ResultSet([('member', OtherPolicy)])
|
||||
return self.policies
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'InstancePort':
|
||||
self.instance_port = int(value)
|
||||
return
|
||||
|
||||
|
||||
class LoadBalancerZones(object):
|
||||
"""
|
||||
Used to collect the zones for a Load Balancer when enable_zones
|
||||
or disable_zones are called.
|
||||
"""
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.zones = ListElement()
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'AvailabilityZones':
|
||||
return self.zones
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
pass
|
||||
|
||||
class LoadBalancer(object):
|
||||
"""
|
||||
Represents an EC2 Load Balancer.
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None, name=None, endpoints=None):
|
||||
"""
|
||||
:ivar boto.ec2.elb.ELBConnection connection: The connection this load
|
||||
balancer was instance was instantiated from.
|
||||
:ivar list listeners: A list of tuples in the form of
|
||||
``(<Inbound port>, <Outbound port>, <Protocol>)``
|
||||
:ivar boto.ec2.elb.healthcheck.HealthCheck health_check: The health
|
||||
check policy for this load balancer.
|
||||
:ivar boto.ec2.elb.policies.Policies policies: Cookie stickiness and
|
||||
other policies.
|
||||
:ivar str dns_name: The external DNS name for the balancer.
|
||||
:ivar str created_time: A date+time string showing when the
|
||||
load balancer was created.
|
||||
:ivar list instances: A list of :py:class:`boto.ec2.instanceinfo.InstanceInfo`
|
||||
instances, representing the EC2 instances this load balancer is
|
||||
distributing requests to.
|
||||
:ivar list availability_zones: The availability zones this balancer
|
||||
covers.
|
||||
:ivar str canonical_hosted_zone_name: Current CNAME for the balancer.
|
||||
:ivar str canonical_hosted_zone_name_id: The Route 53 hosted zone
|
||||
ID of this balancer. Needed when creating an Alias record in a
|
||||
Route 53 hosted zone.
|
||||
:ivar boto.ec2.elb.securitygroup.SecurityGroup source_security_group:
|
||||
The security group that you can use as part of your inbound rules
|
||||
for your load balancer back-end instances to disallow traffic
|
||||
from sources other than your load balancer.
|
||||
:ivar list subnets: A list of subnets this balancer is on.
|
||||
:ivar list security_groups: A list of additional security groups that
|
||||
have been applied.
|
||||
:ivar str vpc_id: The ID of the VPC that this ELB resides within.
|
||||
:ivar list backends: A list of :py:class:`boto.ec2.elb.loadbalancer.Backend
|
||||
back-end server descriptions.
|
||||
"""
|
||||
self.connection = connection
|
||||
self.name = name
|
||||
self.listeners = None
|
||||
self.health_check = None
|
||||
self.policies = None
|
||||
self.dns_name = None
|
||||
self.created_time = None
|
||||
self.instances = None
|
||||
self.availability_zones = ListElement()
|
||||
self.canonical_hosted_zone_name = None
|
||||
self.canonical_hosted_zone_name_id = None
|
||||
self.source_security_group = None
|
||||
self.subnets = ListElement()
|
||||
self.security_groups = ListElement()
|
||||
self.vpc_id = None
|
||||
self.scheme = None
|
||||
self.backends = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'LoadBalancer:%s' % self.name
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'HealthCheck':
|
||||
self.health_check = HealthCheck(self)
|
||||
return self.health_check
|
||||
elif name == 'ListenerDescriptions':
|
||||
self.listeners = ResultSet([('member', Listener)])
|
||||
return self.listeners
|
||||
elif name == 'AvailabilityZones':
|
||||
return self.availability_zones
|
||||
elif name == 'Instances':
|
||||
self.instances = ResultSet([('member', InstanceInfo)])
|
||||
return self.instances
|
||||
elif name == 'Policies':
|
||||
self.policies = Policies(self)
|
||||
return self.policies
|
||||
elif name == 'SourceSecurityGroup':
|
||||
self.source_security_group = SecurityGroup()
|
||||
return self.source_security_group
|
||||
elif name == 'Subnets':
|
||||
return self.subnets
|
||||
elif name == 'SecurityGroups':
|
||||
return self.security_groups
|
||||
elif name == 'VPCId':
|
||||
pass
|
||||
elif name == "BackendServerDescriptions":
|
||||
self.backends = ResultSet([('member', Backend)])
|
||||
return self.backends
|
||||
else:
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'LoadBalancerName':
|
||||
self.name = value
|
||||
elif name == 'DNSName':
|
||||
self.dns_name = value
|
||||
elif name == 'CreatedTime':
|
||||
self.created_time = value
|
||||
elif name == 'InstanceId':
|
||||
self.instances.append(value)
|
||||
elif name == 'CanonicalHostedZoneName':
|
||||
self.canonical_hosted_zone_name = value
|
||||
elif name == 'CanonicalHostedZoneNameID':
|
||||
self.canonical_hosted_zone_name_id = value
|
||||
elif name == 'VPCId':
|
||||
self.vpc_id = value
|
||||
elif name == 'Scheme':
|
||||
self.scheme = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def enable_zones(self, zones):
|
||||
"""
|
||||
Enable availability zones to this Access Point.
|
||||
All zones must be in the same region as the Access Point.
|
||||
|
||||
:type zones: string or List of strings
|
||||
:param zones: The name of the zone(s) to add.
|
||||
|
||||
"""
|
||||
if isinstance(zones, str) or isinstance(zones, unicode):
|
||||
zones = [zones]
|
||||
new_zones = self.connection.enable_availability_zones(self.name, zones)
|
||||
self.availability_zones = new_zones
|
||||
|
||||
def disable_zones(self, zones):
|
||||
"""
|
||||
Disable availability zones from this Access Point.
|
||||
|
||||
:type zones: string or List of strings
|
||||
:param zones: The name of the zone(s) to add.
|
||||
|
||||
"""
|
||||
if isinstance(zones, str) or isinstance(zones, unicode):
|
||||
zones = [zones]
|
||||
new_zones = self.connection.disable_availability_zones(self.name, zones)
|
||||
self.availability_zones = new_zones
|
||||
|
||||
def register_instances(self, instances):
|
||||
"""
|
||||
Adds instances to this load balancer. All instances must be in the same
|
||||
region as the load balancer. Adding endpoints that are already
|
||||
registered with the load balancer has no effect.
|
||||
|
||||
:param list instances: List of instance IDs (strings) that you'd like
|
||||
to add to this load balancer.
|
||||
|
||||
"""
|
||||
if isinstance(instances, str) or isinstance(instances, unicode):
|
||||
instances = [instances]
|
||||
new_instances = self.connection.register_instances(self.name,
|
||||
instances)
|
||||
self.instances = new_instances
|
||||
|
||||
def deregister_instances(self, instances):
|
||||
"""
|
||||
Remove instances from this load balancer. Removing instances that are
|
||||
not registered with the load balancer has no effect.
|
||||
|
||||
:param list instances: List of instance IDs (strings) that you'd like
|
||||
to remove from this load balancer.
|
||||
|
||||
"""
|
||||
if isinstance(instances, str) or isinstance(instances, unicode):
|
||||
instances = [instances]
|
||||
new_instances = self.connection.deregister_instances(self.name,
|
||||
instances)
|
||||
self.instances = new_instances
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
Delete this load balancer.
|
||||
"""
|
||||
return self.connection.delete_load_balancer(self.name)
|
||||
|
||||
def configure_health_check(self, health_check):
|
||||
"""
|
||||
Configures the health check behavior for the instances behind this
|
||||
load balancer. See :ref:`elb-configuring-a-health-check` for a
|
||||
walkthrough.
|
||||
|
||||
:param boto.ec2.elb.healthcheck.HealthCheck health_check: A
|
||||
HealthCheck instance that tells the load balancer how to check
|
||||
its instances for health.
|
||||
"""
|
||||
return self.connection.configure_health_check(self.name, health_check)
|
||||
|
||||
def get_instance_health(self, instances=None):
|
||||
"""
|
||||
Returns a list of :py:class:`boto.ec2.elb.instancestate.InstanceState`
|
||||
objects, which show the health of the instances attached to this
|
||||
load balancer.
|
||||
|
||||
:rtype: list
|
||||
:returns: A list of
|
||||
:py:class:`InstanceState <boto.ec2.elb.instancestate.InstanceState>`
|
||||
instances, representing the instances
|
||||
attached to this load balancer.
|
||||
"""
|
||||
return self.connection.describe_instance_health(self.name, instances)
|
||||
|
||||
def create_listeners(self, listeners):
|
||||
return self.connection.create_load_balancer_listeners(self.name,
|
||||
listeners)
|
||||
|
||||
def create_listener(self, inPort, outPort=None, proto="tcp"):
|
||||
if outPort == None:
|
||||
outPort = inPort
|
||||
return self.create_listeners([(inPort, outPort, proto)])
|
||||
|
||||
def delete_listeners(self, listeners):
|
||||
return self.connection.delete_load_balancer_listeners(self.name,
|
||||
listeners)
|
||||
|
||||
def delete_listener(self, inPort):
|
||||
return self.delete_listeners([inPort])
|
||||
|
||||
def delete_policy(self, policy_name):
|
||||
"""
|
||||
Deletes a policy from the LoadBalancer. The specified policy must not
|
||||
be enabled for any listeners.
|
||||
"""
|
||||
return self.connection.delete_lb_policy(self.name, policy_name)
|
||||
|
||||
def set_policies_of_listener(self, lb_port, policies):
|
||||
return self.connection.set_lb_policies_of_listener(self.name,
|
||||
lb_port,
|
||||
policies)
|
||||
|
||||
def set_policies_of_backend_server(self, instance_port, policies):
|
||||
return self.connection.set_lb_policies_of_backend_server(self.name,
|
||||
instance_port,
|
||||
policies)
|
||||
|
||||
|
||||
def create_cookie_stickiness_policy(self, cookie_expiration_period,
|
||||
policy_name):
|
||||
return self.connection.create_lb_cookie_stickiness_policy(cookie_expiration_period, self.name, policy_name)
|
||||
|
||||
def create_app_cookie_stickiness_policy(self, name, policy_name):
|
||||
return self.connection.create_app_cookie_stickiness_policy(name,
|
||||
self.name,
|
||||
policy_name)
|
||||
|
||||
def set_listener_SSL_certificate(self, lb_port, ssl_certificate_id):
|
||||
return self.connection.set_lb_listener_SSL_certificate(self.name,
|
||||
lb_port,
|
||||
ssl_certificate_id)
|
||||
|
||||
def create_lb_policy(self, policy_name, policy_type, policy_attribute):
|
||||
return self.connection.create_lb_policy(self.name, policy_name, policy_type, policy_attribute)
|
||||
|
||||
def attach_subnets(self, subnets):
|
||||
"""
|
||||
Attaches load balancer to one or more subnets.
|
||||
Attaching subnets that are already registered with the
|
||||
Load Balancer has no effect.
|
||||
|
||||
:type subnets: string or List of strings
|
||||
:param subnets: The name of the subnet(s) to add.
|
||||
|
||||
"""
|
||||
if isinstance(subnets, str) or isinstance(subnets, unicode):
|
||||
subnets = [subnets]
|
||||
new_subnets = self.connection.attach_lb_to_subnets(self.name, subnets)
|
||||
self.subnets = new_subnets
|
||||
|
||||
def detach_subnets(self, subnets):
|
||||
"""
|
||||
Detaches load balancer from one or more subnets.
|
||||
|
||||
:type subnets: string or List of strings
|
||||
:param subnets: The name of the subnet(s) to detach.
|
||||
|
||||
"""
|
||||
if isinstance(subnets, str) or isinstance(subnets, unicode):
|
||||
subnets = [subnets]
|
||||
new_subnets = self.connection.detach_lb_from_subnets(self.name, subnets)
|
||||
self.subnets = new_subnets
|
||||
|
||||
def apply_security_groups(self, security_groups):
|
||||
"""
|
||||
Applies security groups to the load balancer.
|
||||
Applying security groups that are already registered with the
|
||||
Load Balancer has no effect.
|
||||
|
||||
:type security_groups: string or List of strings
|
||||
:param security_groups: The name of the security group(s) to add.
|
||||
|
||||
"""
|
||||
if isinstance(security_groups, str) or \
|
||||
isinstance(security_groups, unicode):
|
||||
security_groups = [security_groups]
|
||||
new_sgs = self.connection.apply_security_groups_to_lb(
|
||||
self.name, security_groups)
|
||||
self.security_groups = new_sgs
|
|
@ -1,109 +0,0 @@
|
|||
# Copyright (c) 2010 Reza Lotun http://reza.lotun.name
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
from boto.resultset import ResultSet
|
||||
|
||||
|
||||
class AppCookieStickinessPolicy(object):
|
||||
def __init__(self, connection=None):
|
||||
self.cookie_name = None
|
||||
self.policy_name = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'AppCookieStickiness(%s, %s)' % (self.policy_name,
|
||||
self.cookie_name)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'CookieName':
|
||||
self.cookie_name = value
|
||||
elif name == 'PolicyName':
|
||||
self.policy_name = value
|
||||
|
||||
|
||||
class LBCookieStickinessPolicy(object):
|
||||
def __init__(self, connection=None):
|
||||
self.policy_name = None
|
||||
self.cookie_expiration_period = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'LBCookieStickiness(%s, %s)' % (self.policy_name,
|
||||
self.cookie_expiration_period)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'CookieExpirationPeriod':
|
||||
self.cookie_expiration_period = value
|
||||
elif name == 'PolicyName':
|
||||
self.policy_name = value
|
||||
|
||||
|
||||
class OtherPolicy(object):
|
||||
def __init__(self, connection=None):
|
||||
self.policy_name = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'OtherPolicy(%s)' % (self.policy_name)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
self.policy_name = value
|
||||
|
||||
|
||||
class Policies(object):
|
||||
"""
|
||||
ELB Policies
|
||||
"""
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.app_cookie_stickiness_policies = None
|
||||
self.lb_cookie_stickiness_policies = None
|
||||
self.other_policies = None
|
||||
|
||||
def __repr__(self):
|
||||
app = 'AppCookieStickiness%s' % self.app_cookie_stickiness_policies
|
||||
lb = 'LBCookieStickiness%s' % self.lb_cookie_stickiness_policies
|
||||
other = 'Other%s' % self.other_policies
|
||||
return 'Policies(%s,%s,%s)' % (app, lb, other)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'AppCookieStickinessPolicies':
|
||||
rs = ResultSet([('member', AppCookieStickinessPolicy)])
|
||||
self.app_cookie_stickiness_policies = rs
|
||||
return rs
|
||||
elif name == 'LBCookieStickinessPolicies':
|
||||
rs = ResultSet([('member', LBCookieStickinessPolicy)])
|
||||
self.lb_cookie_stickiness_policies = rs
|
||||
return rs
|
||||
elif name == 'OtherPolicies':
|
||||
rs = ResultSet([('member', OtherPolicy)])
|
||||
self.other_policies = rs
|
||||
return rs
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
return
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
# Copyright (c) 2010 Reza Lotun http://reza.lotun.name
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
class SecurityGroup(object):
|
||||
def __init__(self, connection=None):
|
||||
self.name = None
|
||||
self.owner_alias = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'SecurityGroup(%s, %s)' % (self.name, self.owner_alias)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'GroupName':
|
||||
self.name = value
|
||||
elif name == 'OwnerAlias':
|
||||
self.owner_alias = value
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2010, Eucalyptus Systems, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
class Group:
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self.id = None
|
||||
self.name = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'groupId':
|
||||
self.id = value
|
||||
elif name == 'groupName':
|
||||
self.name = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
|
@ -1,421 +0,0 @@
|
|||
# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2010, Eucalyptus Systems, Inc.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
from boto.ec2.ec2object import EC2Object, TaggedEC2Object
|
||||
from boto.ec2.blockdevicemapping import BlockDeviceMapping
|
||||
|
||||
class ProductCodes(list):
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'productCode':
|
||||
self.append(value)
|
||||
|
||||
class BillingProducts(list):
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'billingProduct':
|
||||
self.append(value)
|
||||
|
||||
class Image(TaggedEC2Object):
|
||||
"""
|
||||
Represents an EC2 Image
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None):
|
||||
TaggedEC2Object.__init__(self, connection)
|
||||
self.id = None
|
||||
self.location = None
|
||||
self.state = None
|
||||
self.ownerId = None # for backwards compatibility
|
||||
self.owner_id = None
|
||||
self.owner_alias = None
|
||||
self.is_public = False
|
||||
self.architecture = None
|
||||
self.platform = None
|
||||
self.type = None
|
||||
self.kernel_id = None
|
||||
self.ramdisk_id = None
|
||||
self.name = None
|
||||
self.description = None
|
||||
self.product_codes = ProductCodes()
|
||||
self.billing_products = BillingProducts()
|
||||
self.block_device_mapping = None
|
||||
self.root_device_type = None
|
||||
self.root_device_name = None
|
||||
self.virtualization_type = None
|
||||
self.hypervisor = None
|
||||
self.instance_lifecycle = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'Image:%s' % self.id
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
retval = TaggedEC2Object.startElement(self, name, attrs, connection)
|
||||
if retval is not None:
|
||||
return retval
|
||||
if name == 'blockDeviceMapping':
|
||||
self.block_device_mapping = BlockDeviceMapping()
|
||||
return self.block_device_mapping
|
||||
elif name == 'productCodes':
|
||||
return self.product_codes
|
||||
elif name == 'billingProducts':
|
||||
return self.billing_products
|
||||
else:
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'imageId':
|
||||
self.id = value
|
||||
elif name == 'imageLocation':
|
||||
self.location = value
|
||||
elif name == 'imageState':
|
||||
self.state = value
|
||||
elif name == 'imageOwnerId':
|
||||
self.ownerId = value # for backwards compatibility
|
||||
self.owner_id = value
|
||||
elif name == 'isPublic':
|
||||
if value == 'false':
|
||||
self.is_public = False
|
||||
elif value == 'true':
|
||||
self.is_public = True
|
||||
else:
|
||||
raise Exception(
|
||||
'Unexpected value of isPublic %s for image %s'%(
|
||||
value,
|
||||
self.id
|
||||
)
|
||||
)
|
||||
elif name == 'architecture':
|
||||
self.architecture = value
|
||||
elif name == 'imageType':
|
||||
self.type = value
|
||||
elif name == 'kernelId':
|
||||
self.kernel_id = value
|
||||
elif name == 'ramdiskId':
|
||||
self.ramdisk_id = value
|
||||
elif name == 'imageOwnerAlias':
|
||||
self.owner_alias = value
|
||||
elif name == 'platform':
|
||||
self.platform = value
|
||||
elif name == 'name':
|
||||
self.name = value
|
||||
elif name == 'description':
|
||||
self.description = value
|
||||
elif name == 'rootDeviceType':
|
||||
self.root_device_type = value
|
||||
elif name == 'rootDeviceName':
|
||||
self.root_device_name = value
|
||||
elif name == 'virtualizationType':
|
||||
self.virtualization_type = value
|
||||
elif name == 'hypervisor':
|
||||
self.hypervisor = value
|
||||
elif name == 'instanceLifecycle':
|
||||
self.instance_lifecycle = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def _update(self, updated):
|
||||
self.__dict__.update(updated.__dict__)
|
||||
|
||||
def update(self, validate=False, dry_run=False):
|
||||
"""
|
||||
Update the image's state information by making a call to fetch
|
||||
the current image attributes from the service.
|
||||
|
||||
:type validate: bool
|
||||
:param validate: By default, if EC2 returns no data about the
|
||||
image the update method returns quietly. If
|
||||
the validate param is True, however, it will
|
||||
raise a ValueError exception if no data is
|
||||
returned from EC2.
|
||||
"""
|
||||
rs = self.connection.get_all_images([self.id], dry_run=dry_run)
|
||||
if len(rs) > 0:
|
||||
img = rs[0]
|
||||
if img.id == self.id:
|
||||
self._update(img)
|
||||
elif validate:
|
||||
raise ValueError('%s is not a valid Image ID' % self.id)
|
||||
return self.state
|
||||
|
||||
def run(self, min_count=1, max_count=1, key_name=None,
|
||||
security_groups=None, user_data=None,
|
||||
addressing_type=None, instance_type='m1.small', placement=None,
|
||||
kernel_id=None, ramdisk_id=None,
|
||||
monitoring_enabled=False, subnet_id=None,
|
||||
block_device_map=None,
|
||||
disable_api_termination=False,
|
||||
instance_initiated_shutdown_behavior=None,
|
||||
private_ip_address=None,
|
||||
placement_group=None, security_group_ids=None,
|
||||
additional_info=None, instance_profile_name=None,
|
||||
instance_profile_arn=None, tenancy=None, dry_run=False):
|
||||
|
||||
"""
|
||||
Runs this instance.
|
||||
|
||||
:type min_count: int
|
||||
:param min_count: The minimum number of instances to start
|
||||
|
||||
:type max_count: int
|
||||
:param max_count: The maximum number of instances to start
|
||||
|
||||
:type key_name: string
|
||||
:param key_name: The name of the key pair with which to
|
||||
launch instances.
|
||||
|
||||
:type security_groups: list of strings
|
||||
:param security_groups: The names of the security groups with which to
|
||||
associate instances.
|
||||
|
||||
:type user_data: string
|
||||
:param user_data: The Base64-encoded MIME user data to be made
|
||||
available to the instance(s) in this reservation.
|
||||
|
||||
:type instance_type: string
|
||||
:param instance_type: The type of instance to run:
|
||||
|
||||
* t1.micro
|
||||
* m1.small
|
||||
* m1.medium
|
||||
* m1.large
|
||||
* m1.xlarge
|
||||
* m3.xlarge
|
||||
* m3.2xlarge
|
||||
* c1.medium
|
||||
* c1.xlarge
|
||||
* m2.xlarge
|
||||
* m2.2xlarge
|
||||
* m2.4xlarge
|
||||
* cr1.8xlarge
|
||||
* hi1.4xlarge
|
||||
* hs1.8xlarge
|
||||
* cc1.4xlarge
|
||||
* cg1.4xlarge
|
||||
* cc2.8xlarge
|
||||
|
||||
:type placement: string
|
||||
:param placement: The Availability Zone to launch the instance into.
|
||||
|
||||
:type kernel_id: string
|
||||
:param kernel_id: The ID of the kernel with which to launch the
|
||||
instances.
|
||||
|
||||
:type ramdisk_id: string
|
||||
:param ramdisk_id: The ID of the RAM disk with which to launch the
|
||||
instances.
|
||||
|
||||
:type monitoring_enabled: bool
|
||||
:param monitoring_enabled: Enable CloudWatch monitoring on
|
||||
the instance.
|
||||
|
||||
:type subnet_id: string
|
||||
:param subnet_id: The subnet ID within which to launch the instances
|
||||
for VPC.
|
||||
|
||||
:type private_ip_address: string
|
||||
:param private_ip_address: If you're using VPC, you can
|
||||
optionally use this parameter to assign the instance a
|
||||
specific available IP address from the subnet (e.g.,
|
||||
10.0.0.25).
|
||||
|
||||
:type block_device_map: :class:`boto.ec2.blockdevicemapping.BlockDeviceMapping`
|
||||
:param block_device_map: A BlockDeviceMapping data structure
|
||||
describing the EBS volumes associated with the Image.
|
||||
|
||||
:type disable_api_termination: bool
|
||||
:param disable_api_termination: If True, the instances will be locked
|
||||
and will not be able to be terminated via the API.
|
||||
|
||||
:type instance_initiated_shutdown_behavior: string
|
||||
:param instance_initiated_shutdown_behavior: Specifies whether the
|
||||
instance stops or terminates on instance-initiated shutdown.
|
||||
Valid values are:
|
||||
|
||||
* stop
|
||||
* terminate
|
||||
|
||||
:type placement_group: string
|
||||
:param placement_group: If specified, this is the name of the placement
|
||||
group in which the instance(s) will be launched.
|
||||
|
||||
:type additional_info: string
|
||||
:param additional_info: Specifies additional information to make
|
||||
available to the instance(s).
|
||||
|
||||
:type security_group_ids: list of strings
|
||||
:param security_group_ids: The ID of the VPC security groups with
|
||||
which to associate instances.
|
||||
|
||||
:type instance_profile_name: string
|
||||
:param instance_profile_name: The name of
|
||||
the IAM Instance Profile (IIP) to associate with the instances.
|
||||
|
||||
:type instance_profile_arn: string
|
||||
:param instance_profile_arn: The Amazon resource name (ARN) of
|
||||
the IAM Instance Profile (IIP) to associate with the instances.
|
||||
|
||||
:type tenancy: string
|
||||
:param tenancy: The tenancy of the instance you want to
|
||||
launch. An instance with a tenancy of 'dedicated' runs on
|
||||
single-tenant hardware and can only be launched into a
|
||||
VPC. Valid values are:"default" or "dedicated".
|
||||
NOTE: To use dedicated tenancy you MUST specify a VPC
|
||||
subnet-ID as well.
|
||||
|
||||
:rtype: Reservation
|
||||
:return: The :class:`boto.ec2.instance.Reservation` associated with
|
||||
the request for machines
|
||||
|
||||
"""
|
||||
|
||||
return self.connection.run_instances(self.id, min_count, max_count,
|
||||
key_name, security_groups,
|
||||
user_data, addressing_type,
|
||||
instance_type, placement,
|
||||
kernel_id, ramdisk_id,
|
||||
monitoring_enabled, subnet_id,
|
||||
block_device_map, disable_api_termination,
|
||||
instance_initiated_shutdown_behavior,
|
||||
private_ip_address, placement_group,
|
||||
security_group_ids=security_group_ids,
|
||||
additional_info=additional_info,
|
||||
instance_profile_name=instance_profile_name,
|
||||
instance_profile_arn=instance_profile_arn,
|
||||
tenancy=tenancy, dry_run=dry_run)
|
||||
|
||||
def deregister(self, delete_snapshot=False, dry_run=False):
|
||||
return self.connection.deregister_image(
|
||||
self.id,
|
||||
delete_snapshot,
|
||||
dry_run=dry_run
|
||||
)
|
||||
|
||||
def get_launch_permissions(self, dry_run=False):
|
||||
img_attrs = self.connection.get_image_attribute(
|
||||
self.id,
|
||||
'launchPermission',
|
||||
dry_run=dry_run
|
||||
)
|
||||
return img_attrs.attrs
|
||||
|
||||
def set_launch_permissions(self, user_ids=None, group_names=None,
|
||||
dry_run=False):
|
||||
return self.connection.modify_image_attribute(self.id,
|
||||
'launchPermission',
|
||||
'add',
|
||||
user_ids,
|
||||
group_names,
|
||||
dry_run=dry_run)
|
||||
|
||||
def remove_launch_permissions(self, user_ids=None, group_names=None,
|
||||
dry_run=False):
|
||||
return self.connection.modify_image_attribute(self.id,
|
||||
'launchPermission',
|
||||
'remove',
|
||||
user_ids,
|
||||
group_names,
|
||||
dry_run=dry_run)
|
||||
|
||||
def reset_launch_attributes(self, dry_run=False):
|
||||
return self.connection.reset_image_attribute(
|
||||
self.id,
|
||||
'launchPermission',
|
||||
dry_run=dry_run
|
||||
)
|
||||
|
||||
def get_kernel(self, dry_run=False):
|
||||
img_attrs =self.connection.get_image_attribute(
|
||||
self.id,
|
||||
'kernel',
|
||||
dry_run=dry_run
|
||||
)
|
||||
return img_attrs.kernel
|
||||
|
||||
def get_ramdisk(self, dry_run=False):
|
||||
img_attrs = self.connection.get_image_attribute(
|
||||
self.id,
|
||||
'ramdisk',
|
||||
dry_run=dry_run
|
||||
)
|
||||
return img_attrs.ramdisk
|
||||
|
||||
class ImageAttribute:
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self.name = None
|
||||
self.kernel = None
|
||||
self.ramdisk = None
|
||||
self.attrs = {}
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'blockDeviceMapping':
|
||||
self.attrs['block_device_mapping'] = BlockDeviceMapping()
|
||||
return self.attrs['block_device_mapping']
|
||||
else:
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'launchPermission':
|
||||
self.name = 'launch_permission'
|
||||
elif name == 'group':
|
||||
if 'groups' in self.attrs:
|
||||
self.attrs['groups'].append(value)
|
||||
else:
|
||||
self.attrs['groups'] = [value]
|
||||
elif name == 'userId':
|
||||
if 'user_ids' in self.attrs:
|
||||
self.attrs['user_ids'].append(value)
|
||||
else:
|
||||
self.attrs['user_ids'] = [value]
|
||||
elif name == 'productCode':
|
||||
if 'product_codes' in self.attrs:
|
||||
self.attrs['product_codes'].append(value)
|
||||
else:
|
||||
self.attrs['product_codes'] = [value]
|
||||
elif name == 'imageId':
|
||||
self.image_id = value
|
||||
elif name == 'kernel':
|
||||
self.kernel = value
|
||||
elif name == 'ramdisk':
|
||||
self.ramdisk = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class CopyImage(object):
|
||||
def __init__(self, parent=None):
|
||||
self._parent = parent
|
||||
self.image_id = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'imageId':
|
||||
self.image_id = value
|
|
@ -1,681 +0,0 @@
|
|||
# Copyright (c) 2006-2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2010, Eucalyptus Systems, Inc.
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
"""
|
||||
Represents an EC2 Instance
|
||||
"""
|
||||
import boto
|
||||
from boto.ec2.ec2object import EC2Object, TaggedEC2Object
|
||||
from boto.resultset import ResultSet
|
||||
from boto.ec2.address import Address
|
||||
from boto.ec2.blockdevicemapping import BlockDeviceMapping
|
||||
from boto.ec2.image import ProductCodes
|
||||
from boto.ec2.networkinterface import NetworkInterface
|
||||
from boto.ec2.group import Group
|
||||
import base64
|
||||
|
||||
|
||||
class InstanceState(object):
|
||||
"""
|
||||
The state of the instance.
|
||||
|
||||
:ivar code: The low byte represents the state. The high byte is an
|
||||
opaque internal value and should be ignored. Valid values:
|
||||
|
||||
* 0 (pending)
|
||||
* 16 (running)
|
||||
* 32 (shutting-down)
|
||||
* 48 (terminated)
|
||||
* 64 (stopping)
|
||||
* 80 (stopped)
|
||||
|
||||
:ivar name: The name of the state of the instance. Valid values:
|
||||
|
||||
* "pending"
|
||||
* "running"
|
||||
* "shutting-down"
|
||||
* "terminated"
|
||||
* "stopping"
|
||||
* "stopped"
|
||||
"""
|
||||
def __init__(self, code=0, name=None):
|
||||
self.code = code
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%d)' % (self.name, self.code)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'code':
|
||||
self.code = int(value)
|
||||
elif name == 'name':
|
||||
self.name = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class InstancePlacement(object):
|
||||
"""
|
||||
The location where the instance launched.
|
||||
|
||||
:ivar zone: The Availability Zone of the instance.
|
||||
:ivar group_name: The name of the placement group the instance is
|
||||
in (for cluster compute instances).
|
||||
:ivar tenancy: The tenancy of the instance (if the instance is
|
||||
running within a VPC). An instance with a tenancy of dedicated
|
||||
runs on single-tenant hardware.
|
||||
"""
|
||||
def __init__(self, zone=None, group_name=None, tenancy=None):
|
||||
self.zone = zone
|
||||
self.group_name = group_name
|
||||
self.tenancy = tenancy
|
||||
|
||||
def __repr__(self):
|
||||
return self.zone
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'availabilityZone':
|
||||
self.zone = value
|
||||
elif name == 'groupName':
|
||||
self.group_name = value
|
||||
elif name == 'tenancy':
|
||||
self.tenancy = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class Reservation(EC2Object):
|
||||
"""
|
||||
Represents a Reservation response object.
|
||||
|
||||
:ivar id: The unique ID of the Reservation.
|
||||
:ivar owner_id: The unique ID of the owner of the Reservation.
|
||||
:ivar groups: A list of Group objects representing the security
|
||||
groups associated with launched instances.
|
||||
:ivar instances: A list of Instance objects launched in this
|
||||
Reservation.
|
||||
"""
|
||||
def __init__(self, connection=None):
|
||||
EC2Object.__init__(self, connection)
|
||||
self.id = None
|
||||
self.owner_id = None
|
||||
self.groups = []
|
||||
self.instances = []
|
||||
|
||||
def __repr__(self):
|
||||
return 'Reservation:%s' % self.id
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'instancesSet':
|
||||
self.instances = ResultSet([('item', Instance)])
|
||||
return self.instances
|
||||
elif name == 'groupSet':
|
||||
self.groups = ResultSet([('item', Group)])
|
||||
return self.groups
|
||||
else:
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'reservationId':
|
||||
self.id = value
|
||||
elif name == 'ownerId':
|
||||
self.owner_id = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def stop_all(self, dry_run=False):
|
||||
for instance in self.instances:
|
||||
instance.stop(dry_run=dry_run)
|
||||
|
||||
|
||||
class Instance(TaggedEC2Object):
|
||||
"""
|
||||
Represents an instance.
|
||||
|
||||
:ivar id: The unique ID of the Instance.
|
||||
:ivar groups: A list of Group objects representing the security
|
||||
groups associated with the instance.
|
||||
:ivar public_dns_name: The public dns name of the instance.
|
||||
:ivar private_dns_name: The private dns name of the instance.
|
||||
:ivar state: The string representation of the instance's current state.
|
||||
:ivar state_code: An integer representation of the instance's
|
||||
current state.
|
||||
:ivar previous_state: The string representation of the instance's
|
||||
previous state.
|
||||
:ivar previous_state_code: An integer representation of the
|
||||
instance's current state.
|
||||
:ivar key_name: The name of the SSH key associated with the instance.
|
||||
:ivar instance_type: The type of instance (e.g. m1.small).
|
||||
:ivar launch_time: The time the instance was launched.
|
||||
:ivar image_id: The ID of the AMI used to launch this instance.
|
||||
:ivar placement: The availability zone in which the instance is running.
|
||||
:ivar placement_group: The name of the placement group the instance
|
||||
is in (for cluster compute instances).
|
||||
:ivar placement_tenancy: The tenancy of the instance, if the instance
|
||||
is running within a VPC. An instance with a tenancy of dedicated
|
||||
runs on a single-tenant hardware.
|
||||
:ivar kernel: The kernel associated with the instance.
|
||||
:ivar ramdisk: The ramdisk associated with the instance.
|
||||
:ivar architecture: The architecture of the image (i386|x86_64).
|
||||
:ivar hypervisor: The hypervisor used.
|
||||
:ivar virtualization_type: The type of virtualization used.
|
||||
:ivar product_codes: A list of product codes associated with this instance.
|
||||
:ivar ami_launch_index: This instances position within it's launch group.
|
||||
:ivar monitored: A boolean indicating whether monitoring is enabled or not.
|
||||
:ivar monitoring_state: A string value that contains the actual value
|
||||
of the monitoring element returned by EC2.
|
||||
:ivar spot_instance_request_id: The ID of the spot instance request
|
||||
if this is a spot instance.
|
||||
:ivar subnet_id: The VPC Subnet ID, if running in VPC.
|
||||
:ivar vpc_id: The VPC ID, if running in VPC.
|
||||
:ivar private_ip_address: The private IP address of the instance.
|
||||
:ivar ip_address: The public IP address of the instance.
|
||||
:ivar platform: Platform of the instance (e.g. Windows)
|
||||
:ivar root_device_name: The name of the root device.
|
||||
:ivar root_device_type: The root device type (ebs|instance-store).
|
||||
:ivar block_device_mapping: The Block Device Mapping for the instance.
|
||||
:ivar state_reason: The reason for the most recent state transition.
|
||||
:ivar groups: List of security Groups associated with the instance.
|
||||
:ivar interfaces: List of Elastic Network Interfaces associated with
|
||||
this instance.
|
||||
:ivar ebs_optimized: Whether instance is using optimized EBS volumes
|
||||
or not.
|
||||
:ivar instance_profile: A Python dict containing the instance
|
||||
profile id and arn associated with this instance.
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None):
|
||||
TaggedEC2Object.__init__(self, connection)
|
||||
self.id = None
|
||||
self.dns_name = None
|
||||
self.public_dns_name = None
|
||||
self.private_dns_name = None
|
||||
self.key_name = None
|
||||
self.instance_type = None
|
||||
self.launch_time = None
|
||||
self.image_id = None
|
||||
self.kernel = None
|
||||
self.ramdisk = None
|
||||
self.product_codes = ProductCodes()
|
||||
self.ami_launch_index = None
|
||||
self.monitored = False
|
||||
self.monitoring_state = None
|
||||
self.spot_instance_request_id = None
|
||||
self.subnet_id = None
|
||||
self.vpc_id = None
|
||||
self.private_ip_address = None
|
||||
self.ip_address = None
|
||||
self.requester_id = None
|
||||
self._in_monitoring_element = False
|
||||
self.persistent = False
|
||||
self.root_device_name = None
|
||||
self.root_device_type = None
|
||||
self.block_device_mapping = None
|
||||
self.state_reason = None
|
||||
self.group_name = None
|
||||
self.client_token = None
|
||||
self.eventsSet = None
|
||||
self.groups = []
|
||||
self.platform = None
|
||||
self.interfaces = []
|
||||
self.hypervisor = None
|
||||
self.virtualization_type = None
|
||||
self.architecture = None
|
||||
self.instance_profile = None
|
||||
self._previous_state = None
|
||||
self._state = InstanceState()
|
||||
self._placement = InstancePlacement()
|
||||
|
||||
def __repr__(self):
|
||||
return 'Instance:%s' % self.id
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self._state.name
|
||||
|
||||
@property
|
||||
def state_code(self):
|
||||
return self._state.code
|
||||
|
||||
@property
|
||||
def previous_state(self):
|
||||
if self._previous_state:
|
||||
return self._previous_state.name
|
||||
return None
|
||||
|
||||
@property
|
||||
def previous_state_code(self):
|
||||
if self._previous_state:
|
||||
return self._previous_state.code
|
||||
return 0
|
||||
|
||||
@property
|
||||
def placement(self):
|
||||
return self._placement.zone
|
||||
|
||||
@property
|
||||
def placement_group(self):
|
||||
return self._placement.group_name
|
||||
|
||||
@property
|
||||
def placement_tenancy(self):
|
||||
return self._placement.tenancy
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
retval = TaggedEC2Object.startElement(self, name, attrs, connection)
|
||||
if retval is not None:
|
||||
return retval
|
||||
if name == 'monitoring':
|
||||
self._in_monitoring_element = True
|
||||
elif name == 'blockDeviceMapping':
|
||||
self.block_device_mapping = BlockDeviceMapping()
|
||||
return self.block_device_mapping
|
||||
elif name == 'productCodes':
|
||||
return self.product_codes
|
||||
elif name == 'stateReason':
|
||||
self.state_reason = SubParse('stateReason')
|
||||
return self.state_reason
|
||||
elif name == 'groupSet':
|
||||
self.groups = ResultSet([('item', Group)])
|
||||
return self.groups
|
||||
elif name == "eventsSet":
|
||||
self.eventsSet = SubParse('eventsSet')
|
||||
return self.eventsSet
|
||||
elif name == 'networkInterfaceSet':
|
||||
self.interfaces = ResultSet([('item', NetworkInterface)])
|
||||
return self.interfaces
|
||||
elif name == 'iamInstanceProfile':
|
||||
self.instance_profile = SubParse('iamInstanceProfile')
|
||||
return self.instance_profile
|
||||
elif name == 'currentState':
|
||||
return self._state
|
||||
elif name == 'previousState':
|
||||
self._previous_state = InstanceState()
|
||||
return self._previous_state
|
||||
elif name == 'instanceState':
|
||||
return self._state
|
||||
elif name == 'placement':
|
||||
return self._placement
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'instanceId':
|
||||
self.id = value
|
||||
elif name == 'imageId':
|
||||
self.image_id = value
|
||||
elif name == 'dnsName' or name == 'publicDnsName':
|
||||
self.dns_name = value # backwards compatibility
|
||||
self.public_dns_name = value
|
||||
elif name == 'privateDnsName':
|
||||
self.private_dns_name = value
|
||||
elif name == 'keyName':
|
||||
self.key_name = value
|
||||
elif name == 'amiLaunchIndex':
|
||||
self.ami_launch_index = value
|
||||
elif name == 'previousState':
|
||||
self.previous_state = value
|
||||
elif name == 'instanceType':
|
||||
self.instance_type = value
|
||||
elif name == 'rootDeviceName':
|
||||
self.root_device_name = value
|
||||
elif name == 'rootDeviceType':
|
||||
self.root_device_type = value
|
||||
elif name == 'launchTime':
|
||||
self.launch_time = value
|
||||
elif name == 'platform':
|
||||
self.platform = value
|
||||
elif name == 'kernelId':
|
||||
self.kernel = value
|
||||
elif name == 'ramdiskId':
|
||||
self.ramdisk = value
|
||||
elif name == 'state':
|
||||
if self._in_monitoring_element:
|
||||
self.monitoring_state = value
|
||||
if value == 'enabled':
|
||||
self.monitored = True
|
||||
self._in_monitoring_element = False
|
||||
elif name == 'spotInstanceRequestId':
|
||||
self.spot_instance_request_id = value
|
||||
elif name == 'subnetId':
|
||||
self.subnet_id = value
|
||||
elif name == 'vpcId':
|
||||
self.vpc_id = value
|
||||
elif name == 'privateIpAddress':
|
||||
self.private_ip_address = value
|
||||
elif name == 'ipAddress':
|
||||
self.ip_address = value
|
||||
elif name == 'requesterId':
|
||||
self.requester_id = value
|
||||
elif name == 'persistent':
|
||||
if value == 'true':
|
||||
self.persistent = True
|
||||
else:
|
||||
self.persistent = False
|
||||
elif name == 'groupName':
|
||||
if self._in_monitoring_element:
|
||||
self.group_name = value
|
||||
elif name == 'clientToken':
|
||||
self.client_token = value
|
||||
elif name == "eventsSet":
|
||||
self.events = value
|
||||
elif name == 'hypervisor':
|
||||
self.hypervisor = value
|
||||
elif name == 'virtualizationType':
|
||||
self.virtualization_type = value
|
||||
elif name == 'architecture':
|
||||
self.architecture = value
|
||||
elif name == 'ebsOptimized':
|
||||
self.ebs_optimized = (value == 'true')
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def _update(self, updated):
|
||||
self.__dict__.update(updated.__dict__)
|
||||
|
||||
def update(self, validate=False, dry_run=False):
|
||||
"""
|
||||
Update the instance's state information by making a call to fetch
|
||||
the current instance attributes from the service.
|
||||
|
||||
:type validate: bool
|
||||
:param validate: By default, if EC2 returns no data about the
|
||||
instance the update method returns quietly. If
|
||||
the validate param is True, however, it will
|
||||
raise a ValueError exception if no data is
|
||||
returned from EC2.
|
||||
"""
|
||||
rs = self.connection.get_all_reservations([self.id], dry_run=dry_run)
|
||||
if len(rs) > 0:
|
||||
r = rs[0]
|
||||
for i in r.instances:
|
||||
if i.id == self.id:
|
||||
self._update(i)
|
||||
elif validate:
|
||||
raise ValueError('%s is not a valid Instance ID' % self.id)
|
||||
return self.state
|
||||
|
||||
def terminate(self, dry_run=False):
|
||||
"""
|
||||
Terminate the instance
|
||||
"""
|
||||
rs = self.connection.terminate_instances([self.id], dry_run=dry_run)
|
||||
if len(rs) > 0:
|
||||
self._update(rs[0])
|
||||
|
||||
def stop(self, force=False, dry_run=False):
|
||||
"""
|
||||
Stop the instance
|
||||
|
||||
:type force: bool
|
||||
:param force: Forces the instance to stop
|
||||
|
||||
:rtype: list
|
||||
:return: A list of the instances stopped
|
||||
"""
|
||||
rs = self.connection.stop_instances([self.id], force, dry_run=dry_run)
|
||||
if len(rs) > 0:
|
||||
self._update(rs[0])
|
||||
|
||||
def start(self, dry_run=False):
|
||||
"""
|
||||
Start the instance.
|
||||
"""
|
||||
rs = self.connection.start_instances([self.id], dry_run=dry_run)
|
||||
if len(rs) > 0:
|
||||
self._update(rs[0])
|
||||
|
||||
def reboot(self, dry_run=False):
|
||||
return self.connection.reboot_instances([self.id], dry_run=dry_run)
|
||||
|
||||
def get_console_output(self, dry_run=False):
|
||||
"""
|
||||
Retrieves the console output for the instance.
|
||||
|
||||
:rtype: :class:`boto.ec2.instance.ConsoleOutput`
|
||||
:return: The console output as a ConsoleOutput object
|
||||
"""
|
||||
return self.connection.get_console_output(self.id, dry_run=dry_run)
|
||||
|
||||
def confirm_product(self, product_code, dry_run=False):
|
||||
return self.connection.confirm_product_instance(
|
||||
self.id,
|
||||
product_code,
|
||||
dry_run=dry_run
|
||||
)
|
||||
|
||||
def use_ip(self, ip_address, dry_run=False):
|
||||
"""
|
||||
Associates an Elastic IP to the instance.
|
||||
|
||||
:type ip_address: Either an instance of
|
||||
:class:`boto.ec2.address.Address` or a string.
|
||||
:param ip_address: The IP address to associate
|
||||
with the instance.
|
||||
|
||||
:rtype: bool
|
||||
:return: True if successful
|
||||
"""
|
||||
|
||||
if isinstance(ip_address, Address):
|
||||
ip_address = ip_address.public_ip
|
||||
return self.connection.associate_address(
|
||||
self.id,
|
||||
ip_address,
|
||||
dry_run=dry_run
|
||||
)
|
||||
|
||||
def monitor(self, dry_run=False):
|
||||
return self.connection.monitor_instance(self.id, dry_run=dry_run)
|
||||
|
||||
def unmonitor(self, dry_run=False):
|
||||
return self.connection.unmonitor_instance(self.id, dry_run=dry_run)
|
||||
|
||||
def get_attribute(self, attribute, dry_run=False):
|
||||
"""
|
||||
Gets an attribute from this instance.
|
||||
|
||||
:type attribute: string
|
||||
:param attribute: The attribute you need information about
|
||||
Valid choices are:
|
||||
|
||||
* instanceType
|
||||
* kernel
|
||||
* ramdisk
|
||||
* userData
|
||||
* disableApiTermination
|
||||
* instanceInitiatedShutdownBehavior
|
||||
* rootDeviceName
|
||||
* blockDeviceMapping
|
||||
* productCodes
|
||||
* sourceDestCheck
|
||||
* groupSet
|
||||
* ebsOptimized
|
||||
|
||||
:rtype: :class:`boto.ec2.image.InstanceAttribute`
|
||||
:return: An InstanceAttribute object representing the value of the
|
||||
attribute requested
|
||||
"""
|
||||
return self.connection.get_instance_attribute(
|
||||
self.id,
|
||||
attribute,
|
||||
dry_run=dry_run
|
||||
)
|
||||
|
||||
def modify_attribute(self, attribute, value, dry_run=False):
|
||||
"""
|
||||
Changes an attribute of this instance
|
||||
|
||||
:type attribute: string
|
||||
:param attribute: The attribute you wish to change.
|
||||
|
||||
* instanceType - A valid instance type (m1.small)
|
||||
* kernel - Kernel ID (None)
|
||||
* ramdisk - Ramdisk ID (None)
|
||||
* userData - Base64 encoded String (None)
|
||||
* disableApiTermination - Boolean (true)
|
||||
* instanceInitiatedShutdownBehavior - stop|terminate
|
||||
* sourceDestCheck - Boolean (true)
|
||||
* groupSet - Set of Security Groups or IDs
|
||||
* ebsOptimized - Boolean (false)
|
||||
|
||||
:type value: string
|
||||
:param value: The new value for the attribute
|
||||
|
||||
:rtype: bool
|
||||
:return: Whether the operation succeeded or not
|
||||
"""
|
||||
return self.connection.modify_instance_attribute(
|
||||
self.id,
|
||||
attribute,
|
||||
value,
|
||||
dry_run=dry_run
|
||||
)
|
||||
|
||||
def reset_attribute(self, attribute, dry_run=False):
|
||||
"""
|
||||
Resets an attribute of this instance to its default value.
|
||||
|
||||
:type attribute: string
|
||||
:param attribute: The attribute to reset. Valid values are:
|
||||
kernel|ramdisk
|
||||
|
||||
:rtype: bool
|
||||
:return: Whether the operation succeeded or not
|
||||
"""
|
||||
return self.connection.reset_instance_attribute(
|
||||
self.id,
|
||||
attribute,
|
||||
dry_run=dry_run
|
||||
)
|
||||
|
||||
def create_image(self, name, description=None, no_reboot=False,
|
||||
dry_run=False):
|
||||
"""
|
||||
Will create an AMI from the instance in the running or stopped
|
||||
state.
|
||||
|
||||
:type name: string
|
||||
:param name: The name of the new image
|
||||
|
||||
:type description: string
|
||||
:param description: An optional human-readable string describing
|
||||
the contents and purpose of the AMI.
|
||||
|
||||
:type no_reboot: bool
|
||||
:param no_reboot: An optional flag indicating that the bundling process
|
||||
should not attempt to shutdown the instance before
|
||||
bundling. If this flag is True, the responsibility
|
||||
of maintaining file system integrity is left to the
|
||||
owner of the instance.
|
||||
|
||||
:rtype: string
|
||||
:return: The new image id
|
||||
"""
|
||||
return self.connection.create_image(
|
||||
self.id,
|
||||
name,
|
||||
description,
|
||||
no_reboot,
|
||||
dry_run=dry_run
|
||||
)
|
||||
|
||||
|
||||
class ConsoleOutput:
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self.parent = parent
|
||||
self.instance_id = None
|
||||
self.timestamp = None
|
||||
self.output = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'instanceId':
|
||||
self.instance_id = value
|
||||
elif name == 'timestamp':
|
||||
self.timestamp = value
|
||||
elif name == 'output':
|
||||
self.output = base64.b64decode(value)
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class InstanceAttribute(dict):
|
||||
|
||||
ValidValues = ['instanceType', 'kernel', 'ramdisk', 'userData',
|
||||
'disableApiTermination',
|
||||
'instanceInitiatedShutdownBehavior',
|
||||
'rootDeviceName', 'blockDeviceMapping', 'sourceDestCheck',
|
||||
'groupSet']
|
||||
|
||||
def __init__(self, parent=None):
|
||||
dict.__init__(self)
|
||||
self.instance_id = None
|
||||
self.request_id = None
|
||||
self._current_value = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'blockDeviceMapping':
|
||||
self[name] = BlockDeviceMapping()
|
||||
return self[name]
|
||||
elif name == 'groupSet':
|
||||
self[name] = ResultSet([('item', Group)])
|
||||
return self[name]
|
||||
else:
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'instanceId':
|
||||
self.instance_id = value
|
||||
elif name == 'requestId':
|
||||
self.request_id = value
|
||||
elif name == 'value':
|
||||
if value == 'true':
|
||||
value = True
|
||||
elif value == 'false':
|
||||
value = False
|
||||
self._current_value = value
|
||||
elif name in self.ValidValues:
|
||||
self[name] = self._current_value
|
||||
|
||||
|
||||
class SubParse(dict):
|
||||
|
||||
def __init__(self, section, parent=None):
|
||||
dict.__init__(self)
|
||||
self.section = section
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name != self.section:
|
||||
self[name] = value
|
|
@ -1,51 +0,0 @@
|
|||
# Copyright (c) 2006-2008 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
class InstanceInfo(object):
|
||||
"""
|
||||
Represents an EC2 Instance status response from CloudWatch
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None, id=None, state=None):
|
||||
"""
|
||||
:ivar str id: The instance's EC2 ID.
|
||||
:ivar str state: Specifies the current status of the instance.
|
||||
"""
|
||||
self.connection = connection
|
||||
self.id = id
|
||||
self.state = state
|
||||
|
||||
def __repr__(self):
|
||||
return 'InstanceInfo:%s' % self.id
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'instanceId' or name == 'InstanceId':
|
||||
self.id = value
|
||||
elif name == 'state':
|
||||
self.state = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
|
||||
class Details(dict):
|
||||
"""
|
||||
A dict object that contains name/value pairs which provide
|
||||
more detailed information about the status of the system
|
||||
or the instance.
|
||||
"""
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'name':
|
||||
self._name = value
|
||||
elif name == 'status':
|
||||
self[self._name] = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class Event(object):
|
||||
"""
|
||||
A status event for an instance.
|
||||
|
||||
:ivar code: A string indicating the event type.
|
||||
:ivar description: A string describing the reason for the event.
|
||||
:ivar not_before: A datestring describing the earliest time for
|
||||
the event.
|
||||
:ivar not_after: A datestring describing the latest time for
|
||||
the event.
|
||||
"""
|
||||
|
||||
def __init__(self, code=None, description=None,
|
||||
not_before=None, not_after=None):
|
||||
self.code = code
|
||||
self.description = description
|
||||
self.not_before = not_before
|
||||
self.not_after = not_after
|
||||
|
||||
def __repr__(self):
|
||||
return 'Event:%s' % self.code
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'code':
|
||||
self.code = value
|
||||
elif name == 'description':
|
||||
self.description = value
|
||||
elif name == 'notBefore':
|
||||
self.not_before = value
|
||||
elif name == 'notAfter':
|
||||
self.not_after = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class Status(object):
|
||||
"""
|
||||
A generic Status object used for system status and instance status.
|
||||
|
||||
:ivar status: A string indicating overall status.
|
||||
:ivar details: A dict containing name-value pairs which provide
|
||||
more details about the current status.
|
||||
"""
|
||||
|
||||
def __init__(self, status=None, details=None):
|
||||
self.status = status
|
||||
if not details:
|
||||
details = Details()
|
||||
self.details = details
|
||||
|
||||
def __repr__(self):
|
||||
return 'Status:%s' % self.status
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'details':
|
||||
return self.details
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'status':
|
||||
self.status = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class EventSet(list):
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'item':
|
||||
event = Event()
|
||||
self.append(event)
|
||||
return event
|
||||
else:
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class InstanceStatus(object):
|
||||
"""
|
||||
Represents an EC2 Instance status as reported by
|
||||
DescribeInstanceStatus request.
|
||||
|
||||
:ivar id: The instance identifier.
|
||||
:ivar zone: The availability zone of the instance.
|
||||
:ivar events: A list of events relevant to the instance.
|
||||
:ivar state_code: An integer representing the current state
|
||||
of the instance.
|
||||
:ivar state_name: A string describing the current state
|
||||
of the instance.
|
||||
:ivar system_status: A Status object that reports impaired
|
||||
functionality that stems from issues related to the systems
|
||||
that support an instance, such as such as hardware failures
|
||||
and network connectivity problems.
|
||||
:ivar instance_status: A Status object that reports impaired
|
||||
functionality that arises from problems internal to the instance.
|
||||
"""
|
||||
|
||||
def __init__(self, id=None, zone=None, events=None,
|
||||
state_code=None, state_name=None):
|
||||
self.id = id
|
||||
self.zone = zone
|
||||
self.events = events
|
||||
self.state_code = state_code
|
||||
self.state_name = state_name
|
||||
self.system_status = Status()
|
||||
self.instance_status = Status()
|
||||
|
||||
def __repr__(self):
|
||||
return 'InstanceStatus:%s' % self.id
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'eventsSet':
|
||||
self.events = EventSet()
|
||||
return self.events
|
||||
elif name == 'systemStatus':
|
||||
return self.system_status
|
||||
elif name == 'instanceStatus':
|
||||
return self.instance_status
|
||||
else:
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'instanceId':
|
||||
self.id = value
|
||||
elif name == 'availabilityZone':
|
||||
self.zone = value
|
||||
elif name == 'code':
|
||||
self.state_code = int(value)
|
||||
elif name == 'name':
|
||||
self.state_name = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class InstanceStatusSet(list):
|
||||
"""
|
||||
A list object that contains the results of a call to
|
||||
DescribeInstanceStatus request. Each element of the
|
||||
list will be an InstanceStatus object.
|
||||
|
||||
:ivar next_token: If the response was truncated by
|
||||
the EC2 service, the next_token attribute of the
|
||||
object will contain the string that needs to be
|
||||
passed in to the next request to retrieve the next
|
||||
set of results.
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None):
|
||||
list.__init__(self)
|
||||
self.connection = connection
|
||||
self.next_token = None
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'item':
|
||||
status = InstanceStatus()
|
||||
self.append(status)
|
||||
return status
|
||||
else:
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'nextToken':
|
||||
self.next_token = value
|
||||
setattr(self, name, value)
|
|
@ -1,113 +0,0 @@
|
|||
# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
"""
|
||||
Represents an EC2 Keypair
|
||||
"""
|
||||
|
||||
import os
|
||||
from boto.ec2.ec2object import EC2Object
|
||||
from boto.exception import BotoClientError
|
||||
|
||||
class KeyPair(EC2Object):
|
||||
|
||||
def __init__(self, connection=None):
|
||||
EC2Object.__init__(self, connection)
|
||||
self.name = None
|
||||
self.fingerprint = None
|
||||
self.material = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'KeyPair:%s' % self.name
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'keyName':
|
||||
self.name = value
|
||||
elif name == 'keyFingerprint':
|
||||
self.fingerprint = value
|
||||
elif name == 'keyMaterial':
|
||||
self.material = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def delete(self, dry_run=False):
|
||||
"""
|
||||
Delete the KeyPair.
|
||||
|
||||
:rtype: bool
|
||||
:return: True if successful, otherwise False.
|
||||
"""
|
||||
return self.connection.delete_key_pair(self.name, dry_run=dry_run)
|
||||
|
||||
def save(self, directory_path):
|
||||
"""
|
||||
Save the material (the unencrypted PEM encoded RSA private key)
|
||||
of a newly created KeyPair to a local file.
|
||||
|
||||
:type directory_path: string
|
||||
:param directory_path: The fully qualified path to the directory
|
||||
in which the keypair will be saved. The
|
||||
keypair file will be named using the name
|
||||
of the keypair as the base name and .pem
|
||||
for the file extension. If a file of that
|
||||
name already exists in the directory, an
|
||||
exception will be raised and the old file
|
||||
will not be overwritten.
|
||||
|
||||
:rtype: bool
|
||||
:return: True if successful.
|
||||
"""
|
||||
if self.material:
|
||||
directory_path = os.path.expanduser(directory_path)
|
||||
file_path = os.path.join(directory_path, '%s.pem' % self.name)
|
||||
if os.path.exists(file_path):
|
||||
raise BotoClientError('%s already exists, it will not be overwritten' % file_path)
|
||||
fp = open(file_path, 'wb')
|
||||
fp.write(self.material)
|
||||
fp.close()
|
||||
os.chmod(file_path, 0600)
|
||||
return True
|
||||
else:
|
||||
raise BotoClientError('KeyPair contains no material')
|
||||
|
||||
def copy_to_region(self, region, dry_run=False):
|
||||
"""
|
||||
Create a new key pair of the same new in another region.
|
||||
Note that the new key pair will use a different ssh
|
||||
cert than the this key pair. After doing the copy,
|
||||
you will need to save the material associated with the
|
||||
new key pair (use the save method) to a local file.
|
||||
|
||||
:type region: :class:`boto.ec2.regioninfo.RegionInfo`
|
||||
:param region: The region to which this security group will be copied.
|
||||
|
||||
:rtype: :class:`boto.ec2.keypair.KeyPair`
|
||||
:return: The new key pair
|
||||
"""
|
||||
if region.name == self.region:
|
||||
raise BotoClientError('Unable to copy to the same Region')
|
||||
conn_params = self.connection.get_params()
|
||||
rconn = region.connect(**conn_params)
|
||||
kp = rconn.create_key_pair(self.name, dry_run=dry_run)
|
||||
return kp
|
||||
|
||||
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
# Copyright (c) 2006-2012 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
"""
|
||||
Represents a launch specification for Spot instances.
|
||||
"""
|
||||
|
||||
from boto.ec2.ec2object import EC2Object
|
||||
from boto.resultset import ResultSet
|
||||
from boto.ec2.blockdevicemapping import BlockDeviceMapping
|
||||
from boto.ec2.group import Group
|
||||
from boto.ec2.instance import SubParse
|
||||
|
||||
|
||||
class GroupList(list):
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'groupId':
|
||||
self.append(value)
|
||||
|
||||
|
||||
class LaunchSpecification(EC2Object):
|
||||
|
||||
def __init__(self, connection=None):
|
||||
EC2Object.__init__(self, connection)
|
||||
self.key_name = None
|
||||
self.instance_type = None
|
||||
self.image_id = None
|
||||
self.groups = []
|
||||
self.placement = None
|
||||
self.kernel = None
|
||||
self.ramdisk = None
|
||||
self.monitored = False
|
||||
self.subnet_id = None
|
||||
self._in_monitoring_element = False
|
||||
self.block_device_mapping = None
|
||||
self.instance_profile = None
|
||||
self.ebs_optimized = False
|
||||
|
||||
def __repr__(self):
|
||||
return 'LaunchSpecification(%s)' % self.image_id
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'groupSet':
|
||||
self.groups = ResultSet([('item', Group)])
|
||||
return self.groups
|
||||
elif name == 'monitoring':
|
||||
self._in_monitoring_element = True
|
||||
elif name == 'blockDeviceMapping':
|
||||
self.block_device_mapping = BlockDeviceMapping()
|
||||
return self.block_device_mapping
|
||||
elif name == 'iamInstanceProfile':
|
||||
self.instance_profile = SubParse('iamInstanceProfile')
|
||||
return self.instance_profile
|
||||
else:
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'imageId':
|
||||
self.image_id = value
|
||||
elif name == 'keyName':
|
||||
self.key_name = value
|
||||
elif name == 'instanceType':
|
||||
self.instance_type = value
|
||||
elif name == 'availabilityZone':
|
||||
self.placement = value
|
||||
elif name == 'placement':
|
||||
pass
|
||||
elif name == 'kernelId':
|
||||
self.kernel = value
|
||||
elif name == 'ramdiskId':
|
||||
self.ramdisk = value
|
||||
elif name == 'subnetId':
|
||||
self.subnet_id = value
|
||||
elif name == 'state':
|
||||
if self._in_monitoring_element:
|
||||
if value == 'enabled':
|
||||
self.monitored = True
|
||||
self._in_monitoring_element = False
|
||||
elif name == 'ebsOptimized':
|
||||
self.ebs_optimized = (value == 'true')
|
||||
else:
|
||||
setattr(self, name, value)
|
|
@ -1,287 +0,0 @@
|
|||
# Copyright (c) 2011 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
"""
|
||||
Represents an EC2 Elastic Network Interface
|
||||
"""
|
||||
from boto.exception import BotoClientError
|
||||
from boto.ec2.ec2object import TaggedEC2Object
|
||||
from boto.resultset import ResultSet
|
||||
from boto.ec2.group import Group
|
||||
|
||||
|
||||
class Attachment(object):
|
||||
"""
|
||||
:ivar id: The ID of the attachment.
|
||||
:ivar instance_id: The ID of the instance.
|
||||
:ivar device_index: The index of this device.
|
||||
:ivar status: The status of the device.
|
||||
:ivar attach_time: The time the device was attached.
|
||||
:ivar delete_on_termination: Whether the device will be deleted
|
||||
when the instance is terminated.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.id = None
|
||||
self.instance_id = None
|
||||
self.instance_owner_id = None
|
||||
self.device_index = 0
|
||||
self.status = None
|
||||
self.attach_time = None
|
||||
self.delete_on_termination = False
|
||||
|
||||
def __repr__(self):
|
||||
return 'Attachment:%s' % self.id
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'attachmentId':
|
||||
self.id = value
|
||||
elif name == 'instanceId':
|
||||
self.instance_id = value
|
||||
elif name == 'deviceIndex':
|
||||
self.device_index = int(value)
|
||||
elif name == 'instanceOwnerId':
|
||||
self.instance_owner_id = value
|
||||
elif name == 'status':
|
||||
self.status = value
|
||||
elif name == 'attachTime':
|
||||
self.attach_time = value
|
||||
elif name == 'deleteOnTermination':
|
||||
if value.lower() == 'true':
|
||||
self.delete_on_termination = True
|
||||
else:
|
||||
self.delete_on_termination = False
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class NetworkInterface(TaggedEC2Object):
|
||||
"""
|
||||
An Elastic Network Interface.
|
||||
|
||||
:ivar id: The ID of the ENI.
|
||||
:ivar subnet_id: The ID of the VPC subnet.
|
||||
:ivar vpc_id: The ID of the VPC.
|
||||
:ivar description: The description.
|
||||
:ivar owner_id: The ID of the owner of the ENI.
|
||||
:ivar requester_managed:
|
||||
:ivar status: The interface's status (available|in-use).
|
||||
:ivar mac_address: The MAC address of the interface.
|
||||
:ivar private_ip_address: The IP address of the interface within
|
||||
the subnet.
|
||||
:ivar source_dest_check: Flag to indicate whether to validate
|
||||
network traffic to or from this network interface.
|
||||
:ivar groups: List of security groups associated with the interface.
|
||||
:ivar attachment: The attachment object.
|
||||
:ivar private_ip_addresses: A list of PrivateIPAddress objects.
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None):
|
||||
TaggedEC2Object.__init__(self, connection)
|
||||
self.id = None
|
||||
self.subnet_id = None
|
||||
self.vpc_id = None
|
||||
self.availability_zone = None
|
||||
self.description = None
|
||||
self.owner_id = None
|
||||
self.requester_managed = False
|
||||
self.status = None
|
||||
self.mac_address = None
|
||||
self.private_ip_address = None
|
||||
self.source_dest_check = None
|
||||
self.groups = []
|
||||
self.attachment = None
|
||||
self.private_ip_addresses = []
|
||||
|
||||
def __repr__(self):
|
||||
return 'NetworkInterface:%s' % self.id
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
retval = TaggedEC2Object.startElement(self, name, attrs, connection)
|
||||
if retval is not None:
|
||||
return retval
|
||||
if name == 'groupSet':
|
||||
self.groups = ResultSet([('item', Group)])
|
||||
return self.groups
|
||||
elif name == 'attachment':
|
||||
self.attachment = Attachment()
|
||||
return self.attachment
|
||||
elif name == 'privateIpAddressesSet':
|
||||
self.private_ip_addresses = ResultSet([('item', PrivateIPAddress)])
|
||||
return self.private_ip_addresses
|
||||
else:
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'networkInterfaceId':
|
||||
self.id = value
|
||||
elif name == 'subnetId':
|
||||
self.subnet_id = value
|
||||
elif name == 'vpcId':
|
||||
self.vpc_id = value
|
||||
elif name == 'availabilityZone':
|
||||
self.availability_zone = value
|
||||
elif name == 'description':
|
||||
self.description = value
|
||||
elif name == 'ownerId':
|
||||
self.owner_id = value
|
||||
elif name == 'requesterManaged':
|
||||
if value.lower() == 'true':
|
||||
self.requester_managed = True
|
||||
else:
|
||||
self.requester_managed = False
|
||||
elif name == 'status':
|
||||
self.status = value
|
||||
elif name == 'macAddress':
|
||||
self.mac_address = value
|
||||
elif name == 'privateIpAddress':
|
||||
self.private_ip_address = value
|
||||
elif name == 'sourceDestCheck':
|
||||
if value.lower() == 'true':
|
||||
self.source_dest_check = True
|
||||
else:
|
||||
self.source_dest_check = False
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def delete(self, dry_run=False):
|
||||
return self.connection.delete_network_interface(
|
||||
self.id,
|
||||
dry_run=dry_run
|
||||
)
|
||||
|
||||
|
||||
class PrivateIPAddress(object):
|
||||
def __init__(self, connection=None, private_ip_address=None,
|
||||
primary=None):
|
||||
self.connection = connection
|
||||
self.private_ip_address = private_ip_address
|
||||
self.primary = primary
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
pass
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'privateIpAddress':
|
||||
self.private_ip_address = value
|
||||
elif name == 'primary':
|
||||
self.primary = True if value.lower() == 'true' else False
|
||||
|
||||
def __repr__(self):
|
||||
return "PrivateIPAddress(%s, primary=%s)" % (self.private_ip_address,
|
||||
self.primary)
|
||||
|
||||
|
||||
class NetworkInterfaceCollection(list):
|
||||
def __init__(self, *interfaces):
|
||||
self.extend(interfaces)
|
||||
|
||||
def build_list_params(self, params, prefix=''):
|
||||
for i, spec in enumerate(self):
|
||||
full_prefix = '%sNetworkInterface.%s.' % (prefix, i)
|
||||
if spec.network_interface_id is not None:
|
||||
params[full_prefix + 'NetworkInterfaceId'] = \
|
||||
str(spec.network_interface_id)
|
||||
if spec.device_index is not None:
|
||||
params[full_prefix + 'DeviceIndex'] = \
|
||||
str(spec.device_index)
|
||||
else:
|
||||
params[full_prefix + 'DeviceIndex'] = 0
|
||||
if spec.subnet_id is not None:
|
||||
params[full_prefix + 'SubnetId'] = str(spec.subnet_id)
|
||||
if spec.description is not None:
|
||||
params[full_prefix + 'Description'] = str(spec.description)
|
||||
if spec.delete_on_termination is not None:
|
||||
params[full_prefix + 'DeleteOnTermination'] = \
|
||||
'true' if spec.delete_on_termination else 'false'
|
||||
if spec.secondary_private_ip_address_count is not None:
|
||||
params[full_prefix + 'SecondaryPrivateIpAddressCount'] = \
|
||||
str(spec.secondary_private_ip_address_count)
|
||||
if spec.private_ip_address is not None:
|
||||
params[full_prefix + 'PrivateIpAddress'] = \
|
||||
str(spec.private_ip_address)
|
||||
if spec.groups is not None:
|
||||
for j, group_id in enumerate(spec.groups):
|
||||
query_param_key = '%sSecurityGroupId.%s' % (full_prefix, j)
|
||||
params[query_param_key] = str(group_id)
|
||||
if spec.private_ip_addresses is not None:
|
||||
for k, ip_addr in enumerate(spec.private_ip_addresses):
|
||||
query_param_key_prefix = (
|
||||
'%sPrivateIpAddresses.%s' % (full_prefix, k))
|
||||
params[query_param_key_prefix + '.PrivateIpAddress'] = \
|
||||
str(ip_addr.private_ip_address)
|
||||
if ip_addr.primary is not None:
|
||||
params[query_param_key_prefix + '.Primary'] = \
|
||||
'true' if ip_addr.primary else 'false'
|
||||
|
||||
# Associating Public IPs have special logic around them:
|
||||
#
|
||||
# * Only assignable on an device_index of ``0``
|
||||
# * Only on one interface
|
||||
# * Only if there are no other interfaces being created
|
||||
# * Only if it's a new interface (which we can't really guard
|
||||
# against)
|
||||
#
|
||||
# More details on http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-RunInstances.html
|
||||
if spec.associate_public_ip_address is not None:
|
||||
if not params[full_prefix + 'DeviceIndex'] in (0, '0'):
|
||||
raise BotoClientError(
|
||||
"Only the interface with device index of 0 can " + \
|
||||
"be provided when using " + \
|
||||
"'associate_public_ip_address'."
|
||||
)
|
||||
|
||||
if len(self) > 1:
|
||||
raise BotoClientError(
|
||||
"Only one interface can be provided when using " + \
|
||||
"'associate_public_ip_address'."
|
||||
)
|
||||
|
||||
key = full_prefix + 'AssociatePublicIpAddress'
|
||||
|
||||
if spec.associate_public_ip_address:
|
||||
params[key] = 'true'
|
||||
else:
|
||||
params[key] = 'false'
|
||||
|
||||
|
||||
class NetworkInterfaceSpecification(object):
|
||||
def __init__(self, network_interface_id=None, device_index=None,
|
||||
subnet_id=None, description=None, private_ip_address=None,
|
||||
groups=None, delete_on_termination=None,
|
||||
private_ip_addresses=None,
|
||||
secondary_private_ip_address_count=None,
|
||||
associate_public_ip_address=None):
|
||||
self.network_interface_id = network_interface_id
|
||||
self.device_index = device_index
|
||||
self.subnet_id = subnet_id
|
||||
self.description = description
|
||||
self.private_ip_address = private_ip_address
|
||||
self.groups = groups
|
||||
self.delete_on_termination = delete_on_termination
|
||||
self.private_ip_addresses = private_ip_addresses
|
||||
self.secondary_private_ip_address_count = \
|
||||
secondary_private_ip_address_count
|
||||
self.associate_public_ip_address = associate_public_ip_address
|
|
@ -1,54 +0,0 @@
|
|||
# Copyright (c) 2010 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
"""
|
||||
Represents an EC2 Placement Group
|
||||
"""
|
||||
from boto.ec2.ec2object import EC2Object
|
||||
from boto.exception import BotoClientError
|
||||
|
||||
class PlacementGroup(EC2Object):
|
||||
|
||||
def __init__(self, connection=None, name=None, strategy=None, state=None):
|
||||
EC2Object.__init__(self, connection)
|
||||
self.name = name
|
||||
self.strategy = strategy
|
||||
self.state = state
|
||||
|
||||
def __repr__(self):
|
||||
return 'PlacementGroup:%s' % self.name
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'groupName':
|
||||
self.name = value
|
||||
elif name == 'strategy':
|
||||
self.strategy = value
|
||||
elif name == 'state':
|
||||
self.state = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
def delete(self, dry_run=False):
|
||||
return self.connection.delete_placement_group(
|
||||
self.name,
|
||||
dry_run=dry_run
|
||||
)
|
||||
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
|
||||
# Copyright (c) 2010, Eucalyptus Systems, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
from boto.regioninfo import RegionInfo
|
||||
|
||||
class EC2RegionInfo(RegionInfo):
|
||||
"""
|
||||
Represents an EC2 Region
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None, name=None, endpoint=None):
|
||||
from boto.ec2.connection import EC2Connection
|
||||
RegionInfo.__init__(self, connection, name, endpoint,
|
||||
EC2Connection)
|
|
@ -1,349 +0,0 @@
|
|||
# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||
# copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||
# lowing conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included
|
||||
# in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
from boto.resultset import ResultSet
|
||||
from boto.ec2.ec2object import EC2Object
|
||||
from boto.utils import parse_ts
|
||||
|
||||
|
||||
class ReservedInstancesOffering(EC2Object):
|
||||
|
||||
def __init__(self, connection=None, id=None, instance_type=None,
|
||||
availability_zone=None, duration=None, fixed_price=None,
|
||||
usage_price=None, description=None, instance_tenancy=None,
|
||||
currency_code=None, offering_type=None,
|
||||
recurring_charges=None, pricing_details=None):
|
||||
EC2Object.__init__(self, connection)
|
||||
self.id = id
|
||||
self.instance_type = instance_type
|
||||
self.availability_zone = availability_zone
|
||||
self.duration = duration
|
||||
self.fixed_price = fixed_price
|
||||
self.usage_price = usage_price
|
||||
self.description = description
|
||||
self.instance_tenancy = instance_tenancy
|
||||
self.currency_code = currency_code
|
||||
self.offering_type = offering_type
|
||||
self.recurring_charges = recurring_charges
|
||||
self.pricing_details = pricing_details
|
||||
|
||||
def __repr__(self):
|
||||
return 'ReservedInstanceOffering:%s' % self.id
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'recurringCharges':
|
||||
self.recurring_charges = ResultSet([('item', RecurringCharge)])
|
||||
return self.recurring_charges
|
||||
elif name == 'pricingDetailsSet':
|
||||
self.pricing_details = ResultSet([('item', PricingDetail)])
|
||||
return self.pricing_details
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'reservedInstancesOfferingId':
|
||||
self.id = value
|
||||
elif name == 'instanceType':
|
||||
self.instance_type = value
|
||||
elif name == 'availabilityZone':
|
||||
self.availability_zone = value
|
||||
elif name == 'duration':
|
||||
self.duration = int(value)
|
||||
elif name == 'fixedPrice':
|
||||
self.fixed_price = value
|
||||
elif name == 'usagePrice':
|
||||
self.usage_price = value
|
||||
elif name == 'productDescription':
|
||||
self.description = value
|
||||
elif name == 'instanceTenancy':
|
||||
self.instance_tenancy = value
|
||||
elif name == 'currencyCode':
|
||||
self.currency_code = value
|
||||
elif name == 'offeringType':
|
||||
self.offering_type = value
|
||||
elif name == 'marketplace':
|
||||
self.marketplace = True if value == 'true' else False
|
||||
|
||||
def describe(self):
|
||||
print 'ID=%s' % self.id
|
||||
print '\tInstance Type=%s' % self.instance_type
|
||||
print '\tZone=%s' % self.availability_zone
|
||||
print '\tDuration=%s' % self.duration
|
||||
print '\tFixed Price=%s' % self.fixed_price
|
||||
print '\tUsage Price=%s' % self.usage_price
|
||||
print '\tDescription=%s' % self.description
|
||||
|
||||
def purchase(self, instance_count=1, dry_run=False):
|
||||
return self.connection.purchase_reserved_instance_offering(
|
||||
self.id,
|
||||
instance_count,
|
||||
dry_run=dry_run
|
||||
)
|
||||
|
||||
|
||||
class RecurringCharge(object):
|
||||
def __init__(self, connection=None, frequency=None, amount=None):
|
||||
self.frequency = frequency
|
||||
self.amount = amount
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class PricingDetail(object):
|
||||
def __init__(self, connection=None, price=None, count=None):
|
||||
self.price = price
|
||||
self.count = count
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class ReservedInstance(ReservedInstancesOffering):
|
||||
|
||||
def __init__(self, connection=None, id=None, instance_type=None,
|
||||
availability_zone=None, duration=None, fixed_price=None,
|
||||
usage_price=None, description=None,
|
||||
instance_count=None, state=None):
|
||||
ReservedInstancesOffering.__init__(self, connection, id, instance_type,
|
||||
availability_zone, duration, fixed_price,
|
||||
usage_price, description)
|
||||
self.instance_count = instance_count
|
||||
self.state = state
|
||||
self.start = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'ReservedInstance:%s' % self.id
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'reservedInstancesId':
|
||||
self.id = value
|
||||
if name == 'instanceCount':
|
||||
self.instance_count = int(value)
|
||||
elif name == 'state':
|
||||
self.state = value
|
||||
elif name == 'start':
|
||||
self.start = value
|
||||
else:
|
||||
ReservedInstancesOffering.endElement(self, name, value, connection)
|
||||
|
||||
|
||||
class ReservedInstanceListing(EC2Object):
|
||||
def __init__(self, connection=None, listing_id=None, id=None,
|
||||
create_date=None, update_date=None,
|
||||
status=None, status_message=None, client_token=None):
|
||||
self.connection = connection
|
||||
self.listing_id = listing_id
|
||||
self.id = id
|
||||
self.create_date = create_date
|
||||
self.update_date = update_date
|
||||
self.status = status
|
||||
self.status_message = status_message
|
||||
self.client_token = client_token
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'instanceCounts':
|
||||
self.instance_counts = ResultSet([('item', InstanceCount)])
|
||||
return self.instance_counts
|
||||
elif name == 'priceSchedules':
|
||||
self.price_schedules = ResultSet([('item', PriceSchedule)])
|
||||
return self.price_schedules
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'reservedInstancesListingId':
|
||||
self.listing_id = value
|
||||
elif name == 'reservedInstancesId':
|
||||
self.id = value
|
||||
elif name == 'createDate':
|
||||
self.create_date = value
|
||||
elif name == 'updateDate':
|
||||
self.update_date = value
|
||||
elif name == 'status':
|
||||
self.status = value
|
||||
elif name == 'statusMessage':
|
||||
self.status_message = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class InstanceCount(object):
|
||||
def __init__(self, connection=None, state=None, instance_count=None):
|
||||
self.state = state
|
||||
self.instance_count = instance_count
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'state':
|
||||
self.state = value
|
||||
elif name == 'instanceCount':
|
||||
self.instance_count = int(value)
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class PriceSchedule(object):
|
||||
def __init__(self, connection=None, term=None, price=None,
|
||||
currency_code=None, active=None):
|
||||
self.connection = connection
|
||||
self.term = term
|
||||
self.price = price
|
||||
self.currency_code = currency_code
|
||||
self.active = active
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'term':
|
||||
self.term = int(value)
|
||||
elif name == 'price':
|
||||
self.price = value
|
||||
elif name == 'currencyCode':
|
||||
self.currency_code = value
|
||||
elif name == 'active':
|
||||
self.active = True if value == 'true' else False
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class ReservedInstancesConfiguration(object):
|
||||
def __init__(self, connection=None, availability_zone=None, platform=None,
|
||||
instance_count=None, instance_type=None):
|
||||
self.connection = connection
|
||||
self.availability_zone = availability_zone
|
||||
self.platform = platform
|
||||
self.instance_count = instance_count
|
||||
self.instance_type = instance_type
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'availabilityZone':
|
||||
self.availability_zone = value
|
||||
elif name == 'platform':
|
||||
self.platform = value
|
||||
elif name == 'instanceCount':
|
||||
self.instance_count = int(value)
|
||||
elif name == 'instanceType':
|
||||
self.instance_type = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class ModifyReservedInstancesResult(object):
|
||||
def __init__(self, connection=None, modification_id=None):
|
||||
self.connection = connection
|
||||
self.modification_id = modification_id
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'reservedInstancesModificationId':
|
||||
self.modification_id = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class ModificationResult(object):
|
||||
def __init__(self, connection=None, modification_id=None,
|
||||
availability_zone=None, platform=None, instance_count=None,
|
||||
instance_type=None):
|
||||
self.connection = connection
|
||||
self.modification_id = modification_id
|
||||
self.availability_zone = availability_zone
|
||||
self.platform = platform
|
||||
self.instance_count = instance_count
|
||||
self.instance_type = instance_type
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'reservedInstancesModificationId':
|
||||
self.modification_id = value
|
||||
elif name == 'availabilityZone':
|
||||
self.availability_zone = value
|
||||
elif name == 'platform':
|
||||
self.platform = value
|
||||
elif name == 'instanceCount':
|
||||
self.instance_count = int(value)
|
||||
elif name == 'instanceType':
|
||||
self.instance_type = value
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
|
||||
class ReservedInstancesModification(object):
|
||||
def __init__(self, connection=None, modification_id=None,
|
||||
reserved_instances=None, modification_results=None,
|
||||
create_date=None, update_date=None, effective_date=None,
|
||||
status=None, status_message=None, client_token=None):
|
||||
self.connection = connection
|
||||
self.modification_id = modification_id
|
||||
self.reserved_instances = reserved_instances
|
||||
self.modification_results = modification_results
|
||||
self.create_date = create_date
|
||||
self.update_date = update_date
|
||||
self.effective_date = effective_date
|
||||
self.status = status
|
||||
self.status_message = status_message
|
||||
self.client_token = client_token
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
if name == 'reservedInstancesSet':
|
||||
self.reserved_instances = ResultSet([
|
||||
('item', ReservedInstance)
|
||||
])
|
||||
return self.reserved_instances
|
||||
elif name == 'modificationResultSet':
|
||||
self.modification_results = ResultSet([
|
||||
('item', ModificationResult)
|
||||
])
|
||||
return self.modification_results
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'reservedInstancesModificationId':
|
||||
self.modification_id = value
|
||||
elif name == 'createDate':
|
||||
self.create_date = parse_ts(value)
|
||||
elif name == 'updateDate':
|
||||
self.update_date = parse_ts(value)
|
||||
elif name == 'effectiveDate':
|
||||
self.effective_date = parse_ts(value)
|
||||
elif name == 'status':
|
||||
self.status = value
|
||||
elif name == 'statusMessage':
|
||||
self.status_message = value
|
||||
elif name == 'clientToken':
|
||||
self.client_token = value
|
||||
else:
|
||||
setattr(self, name, value)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue