From 5aa80047b65488fa02f23818ca9132e523cb8693 Mon Sep 17 00:00:00 2001 From: Steven Hardy Date: Wed, 15 Aug 2012 14:09:54 +0100 Subject: [PATCH] heat cli : Rework to separate cli tool from client-API wrappers Rework to remove duplication between heat and heat-boto, and to provide better separation between the CLI tool logic and the underlying client API (should allow easier porting to new ReST API) Ref #175 (partially fixes) Fixes #192 Change-Id: Ib1f821667c40c78770a345204af923163daeffae Signed-off-by: Steven Hardy --- bin/heat | 94 ++---- bin/heat-boto | 732 +------------------------------------------- heat/boto_client.py | 273 +++++++++++++++++ heat/client.py | 50 ++- 4 files changed, 349 insertions(+), 800 deletions(-) mode change 100755 => 120000 bin/heat-boto create mode 100644 heat/boto_client.py diff --git a/bin/heat b/bin/heat index 3fb0ec83f7..fbfb305130 100755 --- a/bin/heat +++ b/bin/heat @@ -42,23 +42,16 @@ scriptname = os.path.basename(sys.argv[0]) gettext.install('heat', unicode=1) -from heat import client as heat_client +if scriptname == 'heat-boto': + from heat import boto_client as heat_client +else: + from heat import client as heat_client from heat import version from heat.common import config from heat.common import exception from heat import utils -def format_parameters(options): - parameters = {} - if options.parameters: - for count, p in enumerate(options.parameters.split(';'), 1): - (n, v) = p.split('=') - parameters['Parameters.member.%d.ParameterKey' % count] = n - parameters['Parameters.member.%d.ParameterValue' % count] = v - return parameters - - @utils.catch_error('validate') def template_validate(options, arguments): ''' @@ -81,11 +74,10 @@ def template_validate(options, arguments): logging.error('Please specify a template file or url') return utils.FAILURE - parameters.update(format_parameters(options)) - - client = get_client(options) - result = client.validate_template(**parameters) - print result + c = get_client(options) + parameters.update(c.format_parameters(options)) + result = c.validate_template(**parameters) + print c.format_template(result) @utils.catch_error('estimatetemplatecost') @@ -98,8 +90,6 @@ def estimate_template_cost(options, arguments): logging.error("as the first argument") return utils.FAILURE - parameters.update(format_parameters(options)) - if options.template_file: parameters['TemplateBody'] = open(options.template_file).read() elif options.template_url: @@ -109,6 +99,7 @@ def estimate_template_cost(options, arguments): return utils.FAILURE c = get_client(options) + parameters.update(c.format_parameters(options)) result = c.estimate_template_cost(**parameters) print result @@ -156,8 +147,6 @@ def stack_create(options, arguments): logging.error("as the first argument") return utils.FAILURE - parameters.update(format_parameters(options)) - parameters['TimeoutInMinutes'] = options.timeout if options.template_file: @@ -169,6 +158,7 @@ def stack_create(options, arguments): return utils.FAILURE c = get_client(options) + parameters.update(c.format_parameters(options)) result = c.create_stack(**parameters) print result @@ -206,9 +196,8 @@ def stack_update(options, arguments): elif options.template_url: parameters['TemplateUrl'] = options.template_url - parameters.update(format_parameters(options)) - c = get_client(options) + parameters.update(c.format_parameters(options)) result = c.update_stack(**parameters) print result @@ -250,7 +239,7 @@ def stack_describe(options, arguments): c = get_client(options) result = c.describe_stacks(**parameters) - print result + print c.format_stack(result) @utils.catch_error('event-list') @@ -268,7 +257,7 @@ def stack_events_list(options, arguments): c = get_client(options) result = c.list_stack_events(**parameters) - print result + print c.format_stack_event(result) @utils.catch_error('resource') @@ -288,7 +277,7 @@ def stack_resource_show(options, arguments): 'LogicalResourceId': resource_name, } result = c.describe_stack_resource(**parameters) - print result + print c.format_stack_resource(result) @utils.catch_error('resource-list') @@ -307,35 +296,7 @@ def stack_resources_list(options, arguments): 'StackName': stack_name, } result = c.list_stack_resources(**parameters) - print result - - -def _resources_list_details(options, lookup_key='StackName', - lookup_value=None, log_resid=None): - ''' - Helper function to reduce duplication in stack_resources_list_details - Looks up resource details based on StackName or PhysicalResourceId - ''' - c = get_client(options) - parameters = {} - - if lookup_key in ['StackName', 'PhysicalResourceId']: - parameters[lookup_key] = lookup_value - else: - logging.error("Unexpected key %s" % lookup_key) - return - - if log_resid: - parameters['LogicalResourceId'] = log_resid - - try: - result = c.describe_stack_resources(**parameters) - except: - logging.debug("Failed to lookup resource details with key %s:%s" % - (lookup_key, lookup_value)) - return - - return result + print c.format_stack_resource_summary(result) @utils.catch_error('resource-list-details') @@ -357,26 +318,23 @@ def stack_resources_list_details(options, arguments): try: name_or_pid = arguments.pop(0) except IndexError: - logging.error("No valid stack_name or physical_resource_id") + logging.error("Must pass a stack_name or physical_resource_id") print usage return logical_resource_id = arguments.pop(0) if arguments else None + parameters = { + 'NameOrPid': name_or_pid, + 'LogicalResourceId': logical_resource_id, } - # Try StackName first as it seems the most likely.. - lookup_keys = ['StackName', 'PhysicalResourceId'] - for key in lookup_keys: - logging.debug("Looking up resources for %s:%s" % (key, name_or_pid)) - result = _resources_list_details(options, lookup_key=key, - lookup_value=name_or_pid, - log_resid=logical_resource_id) - if result: - break + c = get_client(options) + result = c.describe_stack_resources(**parameters) if result: - print result + print c.format_stack_resource(result) else: - logging.error("No valid stack_name or physical_resource_id") + logging.error("Invalid stack_name, physical_resource_id " + + "or logical_resource_id") print usage @@ -389,7 +347,7 @@ def stack_list(options, arguments): ''' c = get_client(options) result = c.list_stacks() - print result + print c.format_stack_summary(result) def get_client(options): @@ -640,7 +598,7 @@ Commands: NotImplementedError, exception.ClientConfigurationError), ex: oparser.print_usage() - logging.error("ERROR: " % ex) + logging.error("ERROR: %s" % ex) sys.exit(1) diff --git a/bin/heat-boto b/bin/heat-boto deleted file mode 100755 index 91f15188de..0000000000 --- a/bin/heat-boto +++ /dev/null @@ -1,731 +0,0 @@ -#!/usr/bin/env 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. - -""" -This is the administration program for heat. It is simply a command-line -interface for adding, modifying, and retrieving information about the stacks -belonging to a user. It is a convenience application that talks to the heat -API server. -""" - -import gettext -import optparse -import os -import os.path -import sys -import time -import json -import logging - -from urlparse import urlparse - -# If ../heat/__init__.py exists, add ../ to Python search path, so that -# it will override what happens to be installed in /usr/(local/)lib/python... -possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), - os.pardir, - os.pardir)) -if os.path.exists(os.path.join(possible_topdir, 'heat', '__init__.py')): - sys.path.insert(0, possible_topdir) - -scriptname = os.path.basename(sys.argv[0]) - -gettext.install('heat', unicode=1) - -import boto -from heat import version -from heat.common import config -from heat.common import exception -from heat import utils - - -# FIXME : would be better if each of the boto response classes -# implemented __str__ or a print method, so we could just print -# them, avoiding all these print functions, boto patch TODO - - -def print_stack_event(event): - ''' - Print contents of a boto.cloudformation.stack.StackEvent object - ''' - print "EventId : %s" % event.event_id - print "LogicalResourceId : %s" % event.logical_resource_id - print "PhysicalResourceId : %s" % event.physical_resource_id - print "ResourceProperties : %s" % event.resource_properties - print "ResourceStatus : %s" % event.resource_status - print "ResourceStatusReason : %s" % event.resource_status_reason - print "ResourceType : %s" % event.resource_type - print "StackId : %s" % event.stack_id - print "StackName : %s" % event.stack_name - print "Timestamp : %s" % event.timestamp - print "--" - - -def print_stack(s): - ''' - Print contents of a boto.cloudformation.stack.Stack object - ''' - print "Capabilities : %s" % s.capabilities - print "CreationTime : %s" % s.creation_time - print "Description : %s" % s.description - print "DisableRollback : %s" % s.disable_rollback - # FIXME : boto doesn't populate this field, but AWS defines it. - # bit unclear because the docs say LastUpdatedTime, where all - # other response structures define LastUpdatedTimestamp - # need confirmation of real AWS format, probably a documentation bug.. -# print "LastUpdatedTime : %s" % s.last_updated_time - print "NotificationARNs : %s" % s.notification_arns - print "Outputs : %s" % s.outputs - print "Parameters : %s" % s.parameters - print "StackId : %s" % s.stack_id - print "StackName : %s" % s.stack_name - print "StackStatus : %s" % s.stack_status - print "StackStatusReason : %s" % s.stack_status_reason - print "TimeoutInMinutes : %s" % s.timeout_in_minutes - print "--" - - -def print_stack_resource(res): - ''' - Print contents of a boto.cloudformation.stack.StackResource object - ''' - print "LogicalResourceId : %s" % res.logical_resource_id - print "PhysicalResourceId : %s" % res.physical_resource_id - print "ResourceStatus : %s" % res.resource_status - print "ResourceStatusReason : %s" % res.resource_status_reason - print "ResourceType : %s" % res.resource_type - print "StackId : %s" % res.stack_id - print "StackName : %s" % res.stack_name - print "Timestamp : %s" % res.timestamp - print "--" - - -def print_stack_resource_summary(res): - ''' - Print contents of a boto.cloudformation.stack.StackResourceSummary object - ''' - print "LastUpdatedTimestamp : %s" % res.last_updated_timestamp - print "LogicalResourceId : %s" % res.logical_resource_id - print "PhysicalResourceId : %s" % res.physical_resource_id - print "ResourceStatus : %s" % res.resource_status - print "ResourceStatusReason : %s" % res.resource_status_reason - print "ResourceType : %s" % res.resource_type - print "--" - - -def print_stack_summary(s): - ''' - Print contents of a boto.cloudformation.stack.StackSummary object - ''' - print "StackId : %s" % s.stack_id - print "StackName : %s" % s.stack_name - print "CreationTime : %s" % s.creation_time - print "StackStatus : %s" % s.stack_status - print "TemplateDescription : %s" % s.template_description - print "--" - - -@utils.catch_error('validate') -def template_validate(options, arguments): - ''' - Validate a template. This command parses a template and verifies that - it is in the correct format. - - Usage: heat validate \\ - [--template-file=