Removed boto and ec2 related metadata

This commit is contained in:
Tim Kuhlman 2014-04-25 15:06:10 -06:00
parent e4bb04ef38
commit 339d9f5904
314 changed files with 4 additions and 88856 deletions

View File

@ -1 +0,0 @@
__author__ = 'gary'

View File

@ -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:

View File

@ -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)

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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')

View File

@ -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]

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.
#

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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',
}

View File

@ -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

View File

@ -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
)

View File

@ -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

View File

@ -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')

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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])

View File

@ -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

View File

@ -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)

View File

@ -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'

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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]

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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
)

View File

@ -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)

View File

@ -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