heat/heat/cfn_client/boto_client.py

308 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
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):
disable_rollback = False
if 'DisableRollback' in kwargs:
if str(kwargs['DisableRollback']).lower() == 'true':
disable_rollback = True
if 'TemplateUrl' in kwargs:
return super(BotoClient, self).create_stack(
kwargs['StackName'],
template_url=kwargs['TemplateUrl'],
parameters=kwargs['Parameters'],
disable_rollback=disable_rollback)
elif 'TemplateBody' in kwargs:
return super(BotoClient, self).create_stack(
kwargs['StackName'],
template_body=kwargs['TemplateBody'],
parameters=kwargs['Parameters'],
disable_rollback=disable_rollback)
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
# set is_secure=0 in the config to disable https
cloudformation = BotoClient(aws_access_key_id=aws_access_key,
aws_secret_access_key=aws_secret_key,
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