ff352a0d10
Allow ec2 credentials to optionally be passed in to boto rather than purely relying on config-files Change-Id: Ib821f2bca9bc3f61af521955a9ed52f0059e70e4 Signed-off-by: Steven Hardy <shardy@redhat.com>
298 lines
12 KiB
Python
298 lines
12 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""
|
|
Client implementation based on the boto AWS client library
|
|
"""
|
|
|
|
from heat.openstack.common import log as logging
|
|
logger = logging.getLogger(__name__)
|
|
|
|
from boto.cloudformation import CloudFormationConnection
|
|
import json
|
|
|
|
|
|
class BotoClient(CloudFormationConnection):
|
|
'''
|
|
Wrapper class for boto CloudFormationConnection class
|
|
'''
|
|
|
|
def list_stacks(self, **kwargs):
|
|
return super(BotoClient, self).list_stacks()
|
|
|
|
def describe_stacks(self, **kwargs):
|
|
try:
|
|
stack_name = kwargs['StackName']
|
|
except KeyError:
|
|
stack_name = None
|
|
return super(BotoClient, self).describe_stacks(stack_name)
|
|
|
|
def create_stack(self, **kwargs):
|
|
if 'TemplateUrl' in kwargs:
|
|
return super(BotoClient, self).create_stack(kwargs['StackName'],
|
|
template_url=kwargs['TemplateUrl'],
|
|
parameters=kwargs['Parameters'])
|
|
elif 'TemplateBody' in kwargs:
|
|
return super(BotoClient, self).create_stack(kwargs['StackName'],
|
|
template_body=kwargs['TemplateBody'],
|
|
parameters=kwargs['Parameters'])
|
|
else:
|
|
logger.error("Must specify TemplateUrl or TemplateBody!")
|
|
|
|
def update_stack(self, **kwargs):
|
|
if 'TemplateUrl' in kwargs:
|
|
return super(BotoClient, self).update_stack(kwargs['StackName'],
|
|
template_url=kwargs['TemplateUrl'],
|
|
parameters=kwargs['Parameters'])
|
|
elif 'TemplateBody' in kwargs:
|
|
return super(BotoClient, self).update_stack(kwargs['StackName'],
|
|
template_body=kwargs['TemplateBody'],
|
|
parameters=kwargs['Parameters'])
|
|
else:
|
|
logger.error("Must specify TemplateUrl or TemplateBody!")
|
|
|
|
def delete_stack(self, **kwargs):
|
|
return super(BotoClient, self).delete_stack(kwargs['StackName'])
|
|
|
|
def list_stack_events(self, **kwargs):
|
|
return super(BotoClient, self).describe_stack_events(
|
|
kwargs['StackName'])
|
|
|
|
def describe_stack_resource(self, **kwargs):
|
|
return super(BotoClient, self).describe_stack_resource(
|
|
kwargs['StackName'], kwargs['LogicalResourceId'])
|
|
|
|
def describe_stack_resources(self, **kwargs):
|
|
# Check if this is a StackName, if not assume it's a physical res ID
|
|
# Note this is slower for the common case, which is probably StackName
|
|
# than just doing a try/catch over the StackName case then retrying
|
|
# on failure with kwargs['NameOrPid'] as the physical resource ID,
|
|
# however boto spews errors when raising an exception so we can't
|
|
list_stacks = self.list_stacks()
|
|
stack_names = [s.stack_name for s in list_stacks]
|
|
if kwargs['NameOrPid'] in stack_names:
|
|
logger.debug("Looking up resources for StackName:%s" %
|
|
kwargs['NameOrPid'])
|
|
return super(BotoClient, self).describe_stack_resources(
|
|
stack_name_or_id=kwargs['NameOrPid'],
|
|
logical_resource_id=kwargs['LogicalResourceId'])
|
|
else:
|
|
logger.debug("Looking up resources for PhysicalResourceId:%s" %
|
|
kwargs['NameOrPid'])
|
|
return super(BotoClient, self).describe_stack_resources(
|
|
stack_name_or_id=None,
|
|
logical_resource_id=kwargs['LogicalResourceId'],
|
|
physical_resource_id=kwargs['NameOrPid'])
|
|
|
|
def list_stack_resources(self, **kwargs):
|
|
return super(BotoClient, self).list_stack_resources(
|
|
kwargs['StackName'])
|
|
|
|
def validate_template(self, **kwargs):
|
|
if 'TemplateUrl' in kwargs:
|
|
return super(BotoClient, self).validate_template(
|
|
template_url=kwargs['TemplateUrl'])
|
|
elif 'TemplateBody' in kwargs:
|
|
return super(BotoClient, self).validate_template(
|
|
template_body=kwargs['TemplateBody'])
|
|
else:
|
|
logger.error("Must specify TemplateUrl or TemplateBody!")
|
|
|
|
def get_template(self, **kwargs):
|
|
return super(BotoClient, self).get_template(kwargs['StackName'])
|
|
|
|
def estimate_template_cost(self, **kwargs):
|
|
if 'TemplateUrl' in kwargs:
|
|
return super(BotoClient, self).estimate_template_cost(
|
|
kwargs['StackName'],
|
|
template_url=kwargs['TemplateUrl'],
|
|
parameters=kwargs['Parameters'])
|
|
elif 'TemplateBody' in kwargs:
|
|
return super(BotoClient, self).estimate_template_cost(
|
|
kwargs['StackName'],
|
|
template_body=kwargs['TemplateBody'],
|
|
parameters=kwargs['Parameters'])
|
|
else:
|
|
logger.error("Must specify TemplateUrl or TemplateBody!")
|
|
|
|
def format_stack_event(self, events):
|
|
'''
|
|
Return string formatted representation of
|
|
boto.cloudformation.stack.StackEvent objects
|
|
'''
|
|
ret = []
|
|
for event in events:
|
|
ret.append("EventId : %s" % event.event_id)
|
|
ret.append("LogicalResourceId : %s" % event.logical_resource_id)
|
|
ret.append("PhysicalResourceId : %s" % event.physical_resource_id)
|
|
ret.append("ResourceProperties : %s" % event.resource_properties)
|
|
ret.append("ResourceStatus : %s" % event.resource_status)
|
|
ret.append("ResourceStatusReason : %s" %
|
|
event.resource_status_reason)
|
|
ret.append("ResourceType : %s" % event.resource_type)
|
|
ret.append("StackId : %s" % event.stack_id)
|
|
ret.append("StackName : %s" % event.stack_name)
|
|
ret.append("Timestamp : %s" % event.timestamp)
|
|
ret.append("--")
|
|
return '\n'.join(ret)
|
|
|
|
def format_stack(self, stacks):
|
|
'''
|
|
Return string formatted representation of
|
|
boto.cloudformation.stack.Stack objects
|
|
'''
|
|
ret = []
|
|
for s in stacks:
|
|
ret.append("Capabilities : %s" % s.capabilities)
|
|
ret.append("CreationTime : %s" % s.creation_time)
|
|
ret.append("Description : %s" % s.description)
|
|
ret.append("DisableRollback : %s" % s.disable_rollback)
|
|
ret.append("NotificationARNs : %s" % s.notification_arns)
|
|
ret.append("Outputs : %s" % s.outputs)
|
|
ret.append("Parameters : %s" % s.parameters)
|
|
ret.append("StackId : %s" % s.stack_id)
|
|
ret.append("StackName : %s" % s.stack_name)
|
|
ret.append("StackStatus : %s" % s.stack_status)
|
|
ret.append("StackStatusReason : %s" % s.stack_status_reason)
|
|
ret.append("TimeoutInMinutes : %s" % s.timeout_in_minutes)
|
|
ret.append("--")
|
|
return '\n'.join(ret)
|
|
|
|
def format_stack_resource(self, resources):
|
|
'''
|
|
Return string formatted representation of
|
|
boto.cloudformation.stack.StackResource objects
|
|
'''
|
|
ret = []
|
|
for res in resources:
|
|
ret.append("LogicalResourceId : %s" % res.logical_resource_id)
|
|
ret.append("PhysicalResourceId : %s" % res.physical_resource_id)
|
|
ret.append("ResourceStatus : %s" % res.resource_status)
|
|
ret.append("ResourceStatusReason : %s" %
|
|
res.resource_status_reason)
|
|
ret.append("ResourceType : %s" % res.resource_type)
|
|
ret.append("StackId : %s" % res.stack_id)
|
|
ret.append("StackName : %s" % res.stack_name)
|
|
ret.append("Timestamp : %s" % res.timestamp)
|
|
ret.append("--")
|
|
return '\n'.join(ret)
|
|
|
|
def format_stack_resource_summary(self, resources):
|
|
'''
|
|
Return string formatted representation of
|
|
boto.cloudformation.stack.StackResourceSummary objects
|
|
'''
|
|
ret = []
|
|
for res in resources:
|
|
ret.append("LastUpdatedTimestamp : %s" %
|
|
res.last_updated_timestamp)
|
|
ret.append("LogicalResourceId : %s" % res.logical_resource_id)
|
|
ret.append("PhysicalResourceId : %s" % res.physical_resource_id)
|
|
ret.append("ResourceStatus : %s" % res.resource_status)
|
|
ret.append("ResourceStatusReason : %s" %
|
|
res.resource_status_reason)
|
|
ret.append("ResourceType : %s" % res.resource_type)
|
|
ret.append("--")
|
|
return '\n'.join(ret)
|
|
|
|
def format_stack_resource_detail(self, res):
|
|
'''
|
|
Print response from describe_stack_resource call
|
|
|
|
Note pending upstream patch will make this response a
|
|
boto.cloudformation.stack.StackResourceDetail object
|
|
which aligns better with all the existing calls
|
|
see https://github.com/boto/boto/pull/857
|
|
|
|
For now, we format the dict response as a workaround
|
|
'''
|
|
resource_detail = res['DescribeStackResourceResponse'][
|
|
'DescribeStackResourceResult']['StackResourceDetail']
|
|
ret = []
|
|
for key in resource_detail:
|
|
ret.append("%s : %s" % (key, resource_detail[key]))
|
|
return '\n'.join(ret)
|
|
|
|
def format_stack_summary(self, summaries):
|
|
'''
|
|
Return string formatted representation of
|
|
boto.cloudformation.stack.StackSummary objects
|
|
'''
|
|
ret = []
|
|
for s in summaries:
|
|
ret.append("StackId : %s" % s.stack_id)
|
|
ret.append("StackName : %s" % s.stack_name)
|
|
ret.append("CreationTime : %s" % s.creation_time)
|
|
ret.append("StackStatus : %s" % s.stack_status)
|
|
ret.append("TemplateDescription : %s" % s.template_description)
|
|
ret.append("--")
|
|
return '\n'.join(ret)
|
|
|
|
def format_template(self, template):
|
|
'''
|
|
String formatted representation of
|
|
boto.cloudformation.template.Template object
|
|
'''
|
|
ret = []
|
|
ret.append("Description : %s" % template.description)
|
|
for p in template.template_parameters:
|
|
ret.append("Parameter : ")
|
|
ret.append(" NoEcho : %s" % p.no_echo)
|
|
ret.append(" Description : %s" % p.description)
|
|
ret.append(" ParameterKey : %s" % p.parameter_key)
|
|
ret.append("--")
|
|
return '\n'.join(ret)
|
|
|
|
def format_parameters(self, options):
|
|
'''
|
|
Returns a dict containing list-of-tuple format
|
|
as expected by boto for request parameters
|
|
'''
|
|
parameters = {}
|
|
params = []
|
|
if options.parameters:
|
|
for p in options.parameters.split(';'):
|
|
(n, v) = p.split('=')
|
|
params.append((n, v))
|
|
parameters['Parameters'] = params
|
|
return parameters
|
|
|
|
|
|
def get_client(host, port=None, username=None,
|
|
password=None, tenant=None,
|
|
auth_url=None, auth_strategy=None,
|
|
auth_token=None, region=None,
|
|
is_silent_upload=False, insecure=True,
|
|
aws_access_key=None, aws_secret_key=None):
|
|
|
|
"""
|
|
Returns a new boto Cloudformation client connection to a heat server
|
|
"""
|
|
|
|
# Note we pass None/None for the keys by default
|
|
# This means boto reads /etc/boto.cfg, or ~/.boto
|
|
# Also note is_secure is defaulted to False as HTTPS connections
|
|
# don't seem to work atm, FIXME
|
|
cloudformation = BotoClient(aws_access_key_id=aws_access_key,
|
|
aws_secret_access_key=aws_secret_key, is_secure=False,
|
|
port=port, path="/v1")
|
|
if cloudformation:
|
|
logger.debug("Got CF connection object OK")
|
|
else:
|
|
logger.error("Error establishing Cloudformation connection!")
|
|
sys.exit(1)
|
|
|
|
return cloudformation
|