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 <shardy@redhat.com>
This commit is contained in:
parent
619239527f
commit
5aa80047b6
94
bin/heat
94
bin/heat
@ -42,23 +42,16 @@ scriptname = os.path.basename(sys.argv[0])
|
|||||||
|
|
||||||
gettext.install('heat', unicode=1)
|
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 import version
|
||||||
from heat.common import config
|
from heat.common import config
|
||||||
from heat.common import exception
|
from heat.common import exception
|
||||||
from heat import utils
|
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')
|
@utils.catch_error('validate')
|
||||||
def template_validate(options, arguments):
|
def template_validate(options, arguments):
|
||||||
'''
|
'''
|
||||||
@ -81,11 +74,10 @@ def template_validate(options, arguments):
|
|||||||
logging.error('Please specify a template file or url')
|
logging.error('Please specify a template file or url')
|
||||||
return utils.FAILURE
|
return utils.FAILURE
|
||||||
|
|
||||||
parameters.update(format_parameters(options))
|
c = get_client(options)
|
||||||
|
parameters.update(c.format_parameters(options))
|
||||||
client = get_client(options)
|
result = c.validate_template(**parameters)
|
||||||
result = client.validate_template(**parameters)
|
print c.format_template(result)
|
||||||
print result
|
|
||||||
|
|
||||||
|
|
||||||
@utils.catch_error('estimatetemplatecost')
|
@utils.catch_error('estimatetemplatecost')
|
||||||
@ -98,8 +90,6 @@ def estimate_template_cost(options, arguments):
|
|||||||
logging.error("as the first argument")
|
logging.error("as the first argument")
|
||||||
return utils.FAILURE
|
return utils.FAILURE
|
||||||
|
|
||||||
parameters.update(format_parameters(options))
|
|
||||||
|
|
||||||
if options.template_file:
|
if options.template_file:
|
||||||
parameters['TemplateBody'] = open(options.template_file).read()
|
parameters['TemplateBody'] = open(options.template_file).read()
|
||||||
elif options.template_url:
|
elif options.template_url:
|
||||||
@ -109,6 +99,7 @@ def estimate_template_cost(options, arguments):
|
|||||||
return utils.FAILURE
|
return utils.FAILURE
|
||||||
|
|
||||||
c = get_client(options)
|
c = get_client(options)
|
||||||
|
parameters.update(c.format_parameters(options))
|
||||||
result = c.estimate_template_cost(**parameters)
|
result = c.estimate_template_cost(**parameters)
|
||||||
print result
|
print result
|
||||||
|
|
||||||
@ -156,8 +147,6 @@ def stack_create(options, arguments):
|
|||||||
logging.error("as the first argument")
|
logging.error("as the first argument")
|
||||||
return utils.FAILURE
|
return utils.FAILURE
|
||||||
|
|
||||||
parameters.update(format_parameters(options))
|
|
||||||
|
|
||||||
parameters['TimeoutInMinutes'] = options.timeout
|
parameters['TimeoutInMinutes'] = options.timeout
|
||||||
|
|
||||||
if options.template_file:
|
if options.template_file:
|
||||||
@ -169,6 +158,7 @@ def stack_create(options, arguments):
|
|||||||
return utils.FAILURE
|
return utils.FAILURE
|
||||||
|
|
||||||
c = get_client(options)
|
c = get_client(options)
|
||||||
|
parameters.update(c.format_parameters(options))
|
||||||
result = c.create_stack(**parameters)
|
result = c.create_stack(**parameters)
|
||||||
print result
|
print result
|
||||||
|
|
||||||
@ -206,9 +196,8 @@ def stack_update(options, arguments):
|
|||||||
elif options.template_url:
|
elif options.template_url:
|
||||||
parameters['TemplateUrl'] = options.template_url
|
parameters['TemplateUrl'] = options.template_url
|
||||||
|
|
||||||
parameters.update(format_parameters(options))
|
|
||||||
|
|
||||||
c = get_client(options)
|
c = get_client(options)
|
||||||
|
parameters.update(c.format_parameters(options))
|
||||||
result = c.update_stack(**parameters)
|
result = c.update_stack(**parameters)
|
||||||
print result
|
print result
|
||||||
|
|
||||||
@ -250,7 +239,7 @@ def stack_describe(options, arguments):
|
|||||||
|
|
||||||
c = get_client(options)
|
c = get_client(options)
|
||||||
result = c.describe_stacks(**parameters)
|
result = c.describe_stacks(**parameters)
|
||||||
print result
|
print c.format_stack(result)
|
||||||
|
|
||||||
|
|
||||||
@utils.catch_error('event-list')
|
@utils.catch_error('event-list')
|
||||||
@ -268,7 +257,7 @@ def stack_events_list(options, arguments):
|
|||||||
|
|
||||||
c = get_client(options)
|
c = get_client(options)
|
||||||
result = c.list_stack_events(**parameters)
|
result = c.list_stack_events(**parameters)
|
||||||
print result
|
print c.format_stack_event(result)
|
||||||
|
|
||||||
|
|
||||||
@utils.catch_error('resource')
|
@utils.catch_error('resource')
|
||||||
@ -288,7 +277,7 @@ def stack_resource_show(options, arguments):
|
|||||||
'LogicalResourceId': resource_name,
|
'LogicalResourceId': resource_name,
|
||||||
}
|
}
|
||||||
result = c.describe_stack_resource(**parameters)
|
result = c.describe_stack_resource(**parameters)
|
||||||
print result
|
print c.format_stack_resource(result)
|
||||||
|
|
||||||
|
|
||||||
@utils.catch_error('resource-list')
|
@utils.catch_error('resource-list')
|
||||||
@ -307,35 +296,7 @@ def stack_resources_list(options, arguments):
|
|||||||
'StackName': stack_name,
|
'StackName': stack_name,
|
||||||
}
|
}
|
||||||
result = c.list_stack_resources(**parameters)
|
result = c.list_stack_resources(**parameters)
|
||||||
print result
|
print c.format_stack_resource_summary(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
|
|
||||||
|
|
||||||
|
|
||||||
@utils.catch_error('resource-list-details')
|
@utils.catch_error('resource-list-details')
|
||||||
@ -357,26 +318,23 @@ def stack_resources_list_details(options, arguments):
|
|||||||
try:
|
try:
|
||||||
name_or_pid = arguments.pop(0)
|
name_or_pid = arguments.pop(0)
|
||||||
except IndexError:
|
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
|
print usage
|
||||||
return
|
return
|
||||||
|
|
||||||
logical_resource_id = arguments.pop(0) if arguments else None
|
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..
|
c = get_client(options)
|
||||||
lookup_keys = ['StackName', 'PhysicalResourceId']
|
result = c.describe_stack_resources(**parameters)
|
||||||
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
|
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
print result
|
print c.format_stack_resource(result)
|
||||||
else:
|
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
|
print usage
|
||||||
|
|
||||||
|
|
||||||
@ -389,7 +347,7 @@ def stack_list(options, arguments):
|
|||||||
'''
|
'''
|
||||||
c = get_client(options)
|
c = get_client(options)
|
||||||
result = c.list_stacks()
|
result = c.list_stacks()
|
||||||
print result
|
print c.format_stack_summary(result)
|
||||||
|
|
||||||
|
|
||||||
def get_client(options):
|
def get_client(options):
|
||||||
@ -640,7 +598,7 @@ Commands:
|
|||||||
NotImplementedError,
|
NotImplementedError,
|
||||||
exception.ClientConfigurationError), ex:
|
exception.ClientConfigurationError), ex:
|
||||||
oparser.print_usage()
|
oparser.print_usage()
|
||||||
logging.error("ERROR: " % ex)
|
logging.error("ERROR: %s" % ex)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
731
bin/heat-boto
731
bin/heat-boto
@ -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=<template file>|--template-url=<template URL>] \\
|
|
||||||
[options]
|
|
||||||
|
|
||||||
--template-file: Specify a local template file.
|
|
||||||
--template-url: Specify a URL pointing to a stack description template.
|
|
||||||
'''
|
|
||||||
parameters = {}
|
|
||||||
if options.template_file:
|
|
||||||
parameters['TemplateBody'] = open(options.template_file).read()
|
|
||||||
elif options.template_url:
|
|
||||||
parameters['TemplateUrl'] = options.template_url
|
|
||||||
else:
|
|
||||||
logging.error('Please specify a template file or url')
|
|
||||||
return utils.FAILURE
|
|
||||||
|
|
||||||
if options.parameters:
|
|
||||||
count = 1
|
|
||||||
for p in options.parameters.split(';'):
|
|
||||||
(n, v) = p.split('=')
|
|
||||||
parameters['Parameters.member.%d.ParameterKey' % count] = n
|
|
||||||
parameters['Parameters.member.%d.ParameterValue' % count] = v
|
|
||||||
count = count + 1
|
|
||||||
|
|
||||||
client = get_client(options)
|
|
||||||
result = client.validate_template(**parameters)
|
|
||||||
print result
|
|
||||||
|
|
||||||
|
|
||||||
@utils.catch_error('estimatetemplatecost')
|
|
||||||
def estimate_template_cost(options, arguments):
|
|
||||||
parameters = {}
|
|
||||||
try:
|
|
||||||
parameters['StackName'] = arguments.pop(0)
|
|
||||||
except IndexError:
|
|
||||||
logging.error("Please specify the stack name you wish to estimate")
|
|
||||||
logging.error("as the first argument")
|
|
||||||
return utils.FAILURE
|
|
||||||
|
|
||||||
if options.parameters:
|
|
||||||
count = 1
|
|
||||||
for p in options.parameters.split(';'):
|
|
||||||
(n, v) = p.split('=')
|
|
||||||
parameters['Parameters.member.%d.ParameterKey' % count] = n
|
|
||||||
parameters['Parameters.member.%d.ParameterValue' % count] = v
|
|
||||||
count = count + 1
|
|
||||||
|
|
||||||
if options.template_file:
|
|
||||||
parameters['TemplateBody'] = open(options.template_file).read()
|
|
||||||
elif options.template_url:
|
|
||||||
parameters['TemplateUrl'] = options.template_url
|
|
||||||
else:
|
|
||||||
logging.error('Please specify a template file or url')
|
|
||||||
return utils.FAILURE
|
|
||||||
|
|
||||||
c = get_client(options)
|
|
||||||
result = c.estimate_template_cost(**parameters)
|
|
||||||
print result
|
|
||||||
|
|
||||||
|
|
||||||
@utils.catch_error('gettemplate')
|
|
||||||
def get_template(options, arguments):
|
|
||||||
'''
|
|
||||||
Gets an existing stack template.
|
|
||||||
'''
|
|
||||||
if len(arguments):
|
|
||||||
stack_name = arguments.pop(0)
|
|
||||||
else:
|
|
||||||
logging.error("Please specify the stack you wish to get")
|
|
||||||
logging.error("as the first argument")
|
|
||||||
return utils.FAILURE
|
|
||||||
|
|
||||||
c = get_client(options)
|
|
||||||
result = c.get_template(stack_name)
|
|
||||||
print json.dumps(result)
|
|
||||||
|
|
||||||
|
|
||||||
@utils.catch_error('create')
|
|
||||||
def stack_create(options, arguments):
|
|
||||||
'''
|
|
||||||
Create a new stack from a template.
|
|
||||||
|
|
||||||
Usage: heat create <stack name> \\
|
|
||||||
[--template-file=<template file>|--template-url=<template URL>] \\
|
|
||||||
[options]
|
|
||||||
|
|
||||||
Stack Name: The user specified name of the stack you wish to create.
|
|
||||||
|
|
||||||
--template-file: Specify a local template file containing a valid
|
|
||||||
stack description template.
|
|
||||||
--template-url: Specify a URL pointing to a valid stack description
|
|
||||||
template.
|
|
||||||
'''
|
|
||||||
|
|
||||||
if len(arguments):
|
|
||||||
stack_name = arguments.pop(0)
|
|
||||||
else:
|
|
||||||
logging.error("Please specify the stack name you wish to create")
|
|
||||||
logging.error("as the first argument")
|
|
||||||
return utils.FAILURE
|
|
||||||
|
|
||||||
params = []
|
|
||||||
if options.parameters:
|
|
||||||
for p in options.parameters.split(';'):
|
|
||||||
(n, v) = p.split('=')
|
|
||||||
# Boto expects parameters as a list-of-tuple
|
|
||||||
params.append((n, v))
|
|
||||||
|
|
||||||
result = None
|
|
||||||
c = get_client(options)
|
|
||||||
if options.template_file:
|
|
||||||
t = open(options.template_file).read()
|
|
||||||
result = c.create_stack(stack_name, template_body=t, parameters=params)
|
|
||||||
elif options.template_url:
|
|
||||||
result = c.create_stack(stack_name, template_url=options.template_url)
|
|
||||||
else:
|
|
||||||
logging.error('Please specify a template file or url')
|
|
||||||
return utils.FAILURE
|
|
||||||
|
|
||||||
print result
|
|
||||||
|
|
||||||
|
|
||||||
@utils.catch_error('update')
|
|
||||||
def stack_update(options, arguments):
|
|
||||||
'''
|
|
||||||
Update an existing stack.
|
|
||||||
|
|
||||||
Usage: heat update <stack name> \\
|
|
||||||
[--template-file=<template file>|--template-url=<template URL>] \\
|
|
||||||
[options]
|
|
||||||
|
|
||||||
Stack Name: The name of the stack you wish to modify.
|
|
||||||
|
|
||||||
--template-file: Specify a local template file containing a valid
|
|
||||||
stack description template.
|
|
||||||
--template-url: Specify a URL pointing to a valid stack description
|
|
||||||
template.
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--parameters: A list of key/value pairs separated by ';'s used
|
|
||||||
to specify allowed values in the template file.
|
|
||||||
'''
|
|
||||||
parameters = {}
|
|
||||||
if len(arguments):
|
|
||||||
stack_name = arguments.pop(0)
|
|
||||||
else:
|
|
||||||
logging.error("Please specify the stack name you wish to update")
|
|
||||||
logging.error("as the first argument")
|
|
||||||
return utils.FAILURE
|
|
||||||
|
|
||||||
result = None
|
|
||||||
c = get_client(options)
|
|
||||||
if options.template_file:
|
|
||||||
t = open(options.template_file).read()
|
|
||||||
result = c.update_stack(stack_name, template_body=t)
|
|
||||||
elif options.template_url:
|
|
||||||
t = options.template_url
|
|
||||||
result = c.update_stack(stack_name, template_url=t)
|
|
||||||
|
|
||||||
if options.parameters:
|
|
||||||
count = 1
|
|
||||||
for p in options.parameters.split(';'):
|
|
||||||
(n, v) = p.split('=')
|
|
||||||
parameters['Parameters.member.%d.ParameterKey' % count] = n
|
|
||||||
parameters['Parameters.member.%d.ParameterValue' % count] = v
|
|
||||||
count = count + 1
|
|
||||||
|
|
||||||
print result
|
|
||||||
|
|
||||||
|
|
||||||
@utils.catch_error('delete')
|
|
||||||
def stack_delete(options, arguments):
|
|
||||||
'''
|
|
||||||
Delete an existing stack. This shuts down all VMs associated with
|
|
||||||
the stack and (perhaps wrongly) also removes all events associated
|
|
||||||
with the given stack.
|
|
||||||
|
|
||||||
Usage: heat delete <stack name>
|
|
||||||
'''
|
|
||||||
if len(arguments):
|
|
||||||
stack_name = arguments.pop(0)
|
|
||||||
else:
|
|
||||||
logging.error("Please specify the stack name you wish to delete")
|
|
||||||
logging.error("as the first argument")
|
|
||||||
return utils.FAILURE
|
|
||||||
|
|
||||||
c = get_client(options)
|
|
||||||
result = c.delete_stack(stack_name)
|
|
||||||
print result
|
|
||||||
|
|
||||||
|
|
||||||
@utils.catch_error('describe')
|
|
||||||
def stack_describe(options, arguments):
|
|
||||||
'''
|
|
||||||
Describes an existing stack.
|
|
||||||
|
|
||||||
Usage: heat describe <stack name>
|
|
||||||
'''
|
|
||||||
if len(arguments):
|
|
||||||
stack_name = arguments.pop(0)
|
|
||||||
else:
|
|
||||||
logging.info("No stack name passed, getting results for ALL stacks")
|
|
||||||
stack_name = None
|
|
||||||
|
|
||||||
c = get_client(options)
|
|
||||||
result = c.describe_stacks(stack_name)
|
|
||||||
for s in result:
|
|
||||||
print_stack(s)
|
|
||||||
|
|
||||||
|
|
||||||
@utils.catch_error('event-list')
|
|
||||||
def stack_events_list(options, arguments):
|
|
||||||
'''
|
|
||||||
List events associated with the given stack.
|
|
||||||
|
|
||||||
Usage: heat event-list <stack name>
|
|
||||||
'''
|
|
||||||
if len(arguments):
|
|
||||||
stack_name = arguments.pop(0)
|
|
||||||
else:
|
|
||||||
logging.info("No stack name passed, getting events for ALL stacks")
|
|
||||||
stack_name = None
|
|
||||||
|
|
||||||
c = get_client(options)
|
|
||||||
result = c.describe_stack_events(stack_name)
|
|
||||||
for e in result:
|
|
||||||
print_stack_event(e)
|
|
||||||
|
|
||||||
|
|
||||||
@utils.catch_error('resource')
|
|
||||||
def stack_resource_show(options, arguments):
|
|
||||||
'''
|
|
||||||
Display details of the specified resource.
|
|
||||||
'''
|
|
||||||
c = get_client(options)
|
|
||||||
try:
|
|
||||||
stack_name, resource_name = arguments
|
|
||||||
except ValueError:
|
|
||||||
print 'Enter stack name and logical resource id'
|
|
||||||
return
|
|
||||||
|
|
||||||
result = c.describe_stack_resources(stack_name, resource_name)
|
|
||||||
for r in result:
|
|
||||||
print_stack_resource(r)
|
|
||||||
|
|
||||||
|
|
||||||
@utils.catch_error('resource-list')
|
|
||||||
def stack_resources_list(options, arguments):
|
|
||||||
'''
|
|
||||||
Display summary of all resources in the specified stack.
|
|
||||||
'''
|
|
||||||
c = get_client(options)
|
|
||||||
try:
|
|
||||||
stack_name = arguments.pop(0)
|
|
||||||
except IndexError:
|
|
||||||
print 'Enter stack name'
|
|
||||||
return
|
|
||||||
|
|
||||||
result = c.list_stack_resources(stack_name)
|
|
||||||
for r in result:
|
|
||||||
print_stack_resource_summary(r)
|
|
||||||
|
|
||||||
|
|
||||||
@utils.catch_error('resource-list-details')
|
|
||||||
def stack_resources_list_details(options, arguments):
|
|
||||||
'''
|
|
||||||
Display details of resources in the specified stack.
|
|
||||||
|
|
||||||
- If stack name is specified, all associated resources are returned
|
|
||||||
- If physical resource ID is specified, all associated resources of the
|
|
||||||
stack the resource belongs to are returned
|
|
||||||
- You must specify stack name *or* physical resource ID
|
|
||||||
- You may optionally specify a Logical resource ID to filter the result
|
|
||||||
'''
|
|
||||||
usage = ('''Usage:
|
|
||||||
%s resource-list-details stack_name [logical_resource_id]
|
|
||||||
%s resource-list-details physical_resource_id [logical_resource_id]''' %
|
|
||||||
(scriptname, scriptname))
|
|
||||||
|
|
||||||
try:
|
|
||||||
name_or_pid = arguments.pop(0)
|
|
||||||
except IndexError:
|
|
||||||
logging.error("Must pass a stack_name or physical_resource_id")
|
|
||||||
print usage
|
|
||||||
return
|
|
||||||
|
|
||||||
logical_resource_id = arguments.pop(0) if arguments else None
|
|
||||||
|
|
||||||
c = get_client(options)
|
|
||||||
|
|
||||||
# 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 name_or_pid as the physical resource ID, however
|
|
||||||
# boto spews errors when raising an exception so we can't do that
|
|
||||||
list_stacks = c.list_stacks()
|
|
||||||
stack_names = [s.stack_name for s in list_stacks]
|
|
||||||
if name_or_pid in stack_names:
|
|
||||||
logging.debug("Looking up resources for StackName:%s" % name_or_pid)
|
|
||||||
result = c.describe_stack_resources(stack_name_or_id=name_or_pid,
|
|
||||||
logical_resource_id=logical_resource_id)
|
|
||||||
else:
|
|
||||||
logging.debug("Looking up resources for PhysicalResourceId:%s" %
|
|
||||||
name_or_pid)
|
|
||||||
result = c.describe_stack_resources(stack_name_or_id=None,
|
|
||||||
logical_resource_id=logical_resource_id,
|
|
||||||
physical_resource_id=name_or_pid)
|
|
||||||
|
|
||||||
if result:
|
|
||||||
for r in result:
|
|
||||||
print_stack_resource(r)
|
|
||||||
else:
|
|
||||||
logging.error("Invalid stack_name, physical_resource_id " +
|
|
||||||
"or logical_resource_id")
|
|
||||||
print usage
|
|
||||||
|
|
||||||
|
|
||||||
@utils.catch_error('list')
|
|
||||||
def stack_list(options, arguments):
|
|
||||||
'''
|
|
||||||
List all running stacks.
|
|
||||||
|
|
||||||
Usage: heat list
|
|
||||||
'''
|
|
||||||
c = get_client(options)
|
|
||||||
result = c.list_stacks()
|
|
||||||
for s in result:
|
|
||||||
print_stack_summary(s)
|
|
||||||
|
|
||||||
|
|
||||||
def get_client(options):
|
|
||||||
"""
|
|
||||||
Returns a new boto client object to a heat server
|
|
||||||
"""
|
|
||||||
|
|
||||||
# FIXME : reopen boto pull-request so that --host can override the
|
|
||||||
# host specified in the hostname by passing it via the constructor
|
|
||||||
# I implemented this previously, but they preferred the config-file
|
|
||||||
# solution..
|
|
||||||
# Note we pass None/None for the keys so boto reads /etc/boto.cfg
|
|
||||||
cloudformation = boto.connect_cloudformation(aws_access_key_id=None,
|
|
||||||
aws_secret_access_key=None, debug=options.debug, is_secure=False,
|
|
||||||
port=options.port, path="/v1")
|
|
||||||
if cloudformation:
|
|
||||||
logging.debug("Got CF connection object OK")
|
|
||||||
else:
|
|
||||||
logging.error("Error establishing connection!")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
return cloudformation
|
|
||||||
|
|
||||||
|
|
||||||
def create_options(parser):
|
|
||||||
"""
|
|
||||||
Sets up the CLI and config-file options that may be
|
|
||||||
parsed and program commands.
|
|
||||||
|
|
||||||
:param parser: The option parser
|
|
||||||
"""
|
|
||||||
parser.add_option('-v', '--verbose', default=False, action="store_true",
|
|
||||||
help="Print more verbose output")
|
|
||||||
parser.add_option('-d', '--debug', default=False, action="store_true",
|
|
||||||
help="Print more verbose output")
|
|
||||||
parser.add_option('-y', '--yes', default=False, action="store_true",
|
|
||||||
help="Don't prompt for user input; assume the answer to "
|
|
||||||
"every question is 'yes'.")
|
|
||||||
parser.add_option('-H', '--host', metavar="ADDRESS", default="0.0.0.0",
|
|
||||||
help="Address of heat API host. "
|
|
||||||
"Default: %default")
|
|
||||||
parser.add_option('-p', '--port', dest="port", metavar="PORT",
|
|
||||||
type=int, default=config.DEFAULT_PORT,
|
|
||||||
help="Port the heat API host listens on. "
|
|
||||||
"Default: %default")
|
|
||||||
parser.add_option('-U', '--url', metavar="URL", default=None,
|
|
||||||
help="URL of heat service. This option can be used "
|
|
||||||
"to specify the hostname, port and protocol "
|
|
||||||
"(http/https) of the heat server, for example "
|
|
||||||
"-U https://localhost:" + str(config.DEFAULT_PORT) +
|
|
||||||
"/v1 Default: No<F3>ne")
|
|
||||||
parser.add_option('-k', '--insecure', dest="insecure",
|
|
||||||
default=False, action="store_true",
|
|
||||||
help="Explicitly allow heat to perform \"insecure\" "
|
|
||||||
"SSL (https) requests. The server's certificate will "
|
|
||||||
"not be verified against any certificate authorities. "
|
|
||||||
"This option should be used with caution.")
|
|
||||||
parser.add_option('-A', '--auth_token', dest="auth_token",
|
|
||||||
metavar="TOKEN", default=None,
|
|
||||||
help="Authentication token to use to identify the "
|
|
||||||
"client to the heat server")
|
|
||||||
parser.add_option('-I', '--username', dest="username",
|
|
||||||
metavar="USER", default=None,
|
|
||||||
help="User name used to acquire an authentication token")
|
|
||||||
parser.add_option('-K', '--password', dest="password",
|
|
||||||
metavar="PASSWORD", default=None,
|
|
||||||
help="Password used to acquire an authentication token")
|
|
||||||
parser.add_option('-T', '--tenant', dest="tenant",
|
|
||||||
metavar="TENANT", default=None,
|
|
||||||
help="Tenant name used for Keystone authentication")
|
|
||||||
parser.add_option('-R', '--region', dest="region",
|
|
||||||
metavar="REGION", default=None,
|
|
||||||
help="Region name. When using keystone authentication "
|
|
||||||
"version 2.0 or later this identifies the region "
|
|
||||||
"name to use when selecting the service endpoint. A "
|
|
||||||
"region name must be provided if more than one "
|
|
||||||
"region endpoint is available")
|
|
||||||
parser.add_option('-N', '--auth_url', dest="auth_url",
|
|
||||||
metavar="AUTH_URL", default=None,
|
|
||||||
help="Authentication URL")
|
|
||||||
parser.add_option('-S', '--auth_strategy', dest="auth_strategy",
|
|
||||||
metavar="STRATEGY", default=None,
|
|
||||||
help="Authentication strategy (keystone or noauth)")
|
|
||||||
|
|
||||||
parser.add_option('-u', '--template-url', metavar="template_url",
|
|
||||||
default=None, help="URL of template. Default: None")
|
|
||||||
parser.add_option('-f', '--template-file', metavar="template_file",
|
|
||||||
default=None, help="Path to the template. Default: None")
|
|
||||||
parser.add_option('-t', '--timeout', metavar="timeout",
|
|
||||||
default='60',
|
|
||||||
help='Stack creation timeout in minutes. Default: 60')
|
|
||||||
|
|
||||||
parser.add_option('-P', '--parameters', metavar="parameters", default=None,
|
|
||||||
help="Parameter values used to create the stack.")
|
|
||||||
|
|
||||||
|
|
||||||
def credentials_from_env():
|
|
||||||
return dict(username=os.getenv('OS_USERNAME'),
|
|
||||||
password=os.getenv('OS_PASSWORD'),
|
|
||||||
tenant=os.getenv('OS_TENANT_NAME'),
|
|
||||||
auth_url=os.getenv('OS_AUTH_URL'),
|
|
||||||
auth_strategy=os.getenv('OS_AUTH_STRATEGY'))
|
|
||||||
|
|
||||||
|
|
||||||
def parse_options(parser, cli_args):
|
|
||||||
"""
|
|
||||||
Returns the parsed CLI options, command to run and its arguments, merged
|
|
||||||
with any same-named options found in a configuration file
|
|
||||||
|
|
||||||
:param parser: The option parser
|
|
||||||
"""
|
|
||||||
if not cli_args:
|
|
||||||
cli_args.append('-h') # Show options in usage output...
|
|
||||||
|
|
||||||
(options, args) = parser.parse_args(cli_args)
|
|
||||||
env_opts = credentials_from_env()
|
|
||||||
for option, env_val in env_opts.items():
|
|
||||||
if not getattr(options, option):
|
|
||||||
setattr(options, option, env_val)
|
|
||||||
|
|
||||||
if options.url is not None:
|
|
||||||
u = urlparse(options.url)
|
|
||||||
options.port = u.port
|
|
||||||
options.host = u.hostname
|
|
||||||
|
|
||||||
if not options.auth_strategy:
|
|
||||||
options.auth_strategy = 'noauth'
|
|
||||||
|
|
||||||
options.use_ssl = (options.url is not None and u.scheme == 'https')
|
|
||||||
|
|
||||||
# HACK(sirp): Make the parser available to the print_help method
|
|
||||||
# print_help is a command, so it only accepts (options, args); we could
|
|
||||||
# one-off have it take (parser, options, args), however, for now, I think
|
|
||||||
# this little hack will suffice
|
|
||||||
options.__parser = parser
|
|
||||||
|
|
||||||
if not args:
|
|
||||||
parser.print_usage()
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
command_name = args.pop(0)
|
|
||||||
command = lookup_command(parser, command_name)
|
|
||||||
|
|
||||||
if options.debug:
|
|
||||||
logging.basicConfig(format='%(levelname)s:%(message)s',
|
|
||||||
level=logging.DEBUG)
|
|
||||||
logging.debug("Debug level logging enabled")
|
|
||||||
elif options.verbose:
|
|
||||||
logging.basicConfig(format='%(levelname)s:%(message)s',
|
|
||||||
level=logging.INFO)
|
|
||||||
else:
|
|
||||||
logging.basicConfig(format='%(levelname)s:%(message)s',
|
|
||||||
level=logging.WARNING)
|
|
||||||
|
|
||||||
return (options, command, args)
|
|
||||||
|
|
||||||
|
|
||||||
def print_help(options, args):
|
|
||||||
"""
|
|
||||||
Print help specific to a command
|
|
||||||
"""
|
|
||||||
parser = options.__parser
|
|
||||||
|
|
||||||
if not args:
|
|
||||||
parser.print_usage()
|
|
||||||
|
|
||||||
subst = {'prog': os.path.basename(sys.argv[0])}
|
|
||||||
docs = [lookup_command(parser, cmd).__doc__ % subst for cmd in args]
|
|
||||||
print '\n\n'.join(docs)
|
|
||||||
|
|
||||||
|
|
||||||
def lookup_command(parser, command_name):
|
|
||||||
base_commands = {'help': print_help}
|
|
||||||
|
|
||||||
stack_commands = {
|
|
||||||
'create': stack_create,
|
|
||||||
'update': stack_update,
|
|
||||||
'delete': stack_delete,
|
|
||||||
'list': stack_list,
|
|
||||||
'event-list': stack_events_list,
|
|
||||||
'resource': stack_resource_show,
|
|
||||||
'resource-list': stack_resources_list,
|
|
||||||
'resource-list-details': stack_resources_list_details,
|
|
||||||
'validate': template_validate,
|
|
||||||
'gettemplate': get_template,
|
|
||||||
'estimate-template-cost': estimate_template_cost,
|
|
||||||
'describe': stack_describe}
|
|
||||||
|
|
||||||
commands = {}
|
|
||||||
for command_set in (base_commands, stack_commands):
|
|
||||||
commands.update(command_set)
|
|
||||||
|
|
||||||
try:
|
|
||||||
command = commands[command_name]
|
|
||||||
except KeyError:
|
|
||||||
parser.print_usage()
|
|
||||||
sys.exit("Unknown command: %s" % command_name)
|
|
||||||
|
|
||||||
return command
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
'''
|
|
||||||
'''
|
|
||||||
usage = """
|
|
||||||
%prog <command> [options] [args]
|
|
||||||
|
|
||||||
Commands:
|
|
||||||
|
|
||||||
help <command> Output help for one of the commands below
|
|
||||||
|
|
||||||
create Create the stack
|
|
||||||
|
|
||||||
delete Delete the stack
|
|
||||||
|
|
||||||
describe Describe the stack
|
|
||||||
|
|
||||||
update Update the stack
|
|
||||||
|
|
||||||
list List the user's stacks
|
|
||||||
|
|
||||||
gettemplate Get the template
|
|
||||||
|
|
||||||
estimate-template-cost Returns the estimated monthly cost of a template
|
|
||||||
|
|
||||||
validate Validate a template
|
|
||||||
|
|
||||||
event-list List events for a stack
|
|
||||||
|
|
||||||
resource Describe the resource
|
|
||||||
|
|
||||||
resource-list Show list of resources belonging to a stack
|
|
||||||
|
|
||||||
resource-list-details Detailed view of resources belonging to a stack
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
oparser = optparse.OptionParser(version='%%prog %s'
|
|
||||||
% version.version_string(),
|
|
||||||
usage=usage.strip())
|
|
||||||
create_options(oparser)
|
|
||||||
(opts, cmd, args) = parse_options(oparser, sys.argv[1:])
|
|
||||||
|
|
||||||
try:
|
|
||||||
start_time = time.time()
|
|
||||||
result = cmd(opts, args)
|
|
||||||
end_time = time.time()
|
|
||||||
logging.debug("Completed in %-0.4f sec." % (end_time - start_time))
|
|
||||||
sys.exit(result)
|
|
||||||
except (RuntimeError,
|
|
||||||
NotImplementedError,
|
|
||||||
exception.ClientConfigurationError), ex:
|
|
||||||
oparser.print_usage()
|
|
||||||
logging.error("ERROR: " % ex)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
1
bin/heat-boto
Symbolic link
1
bin/heat-boto
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
heat
|
273
heat/boto_client.py
Normal file
273
heat/boto_client.py
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
# 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):
|
||||||
|
|
||||||
|
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_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):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Returns a new boto client object to a heat server
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Note we pass None/None for the keys so boto reads /etc/boto.cfg
|
||||||
|
# Also note is_secure is defaulted to False as HTTPS connections
|
||||||
|
# don't seem to work atm, FIXME
|
||||||
|
cloudformation = BotoClient(aws_access_key_id=None,
|
||||||
|
aws_secret_access_key=None, is_secure=False,
|
||||||
|
port=port, path="/v1")
|
||||||
|
if cloudformation:
|
||||||
|
logger.debug("Got CF connection object OK")
|
||||||
|
else:
|
||||||
|
logger.error("Error establishing connection!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
return cloudformation
|
@ -72,7 +72,21 @@ class V1Client(base_client.BaseClient):
|
|||||||
return self.stack_request("DescribeStackResource", "GET", **kwargs)
|
return self.stack_request("DescribeStackResource", "GET", **kwargs)
|
||||||
|
|
||||||
def describe_stack_resources(self, **kwargs):
|
def describe_stack_resources(self, **kwargs):
|
||||||
return self.stack_request("DescribeStackResources", "GET", **kwargs)
|
for lookup_key in ['StackName', 'PhysicalResourceId']:
|
||||||
|
lookup_value = kwargs['NameOrPid']
|
||||||
|
parameters = {
|
||||||
|
lookup_key: lookup_value,
|
||||||
|
'LogicalResourceId': kwargs['LogicalResourceId']}
|
||||||
|
try:
|
||||||
|
result = self.stack_request("DescribeStackResources", "GET",
|
||||||
|
**parameters)
|
||||||
|
except:
|
||||||
|
logger.debug("Failed to lookup resource details with key %s:%s"
|
||||||
|
% (lookup_key, lookup_value))
|
||||||
|
else:
|
||||||
|
logger.debug("Got lookup resource details with key %s:%s" %
|
||||||
|
(lookup_key, lookup_value))
|
||||||
|
return result
|
||||||
|
|
||||||
def list_stack_resources(self, **kwargs):
|
def list_stack_resources(self, **kwargs):
|
||||||
return self.stack_request("ListStackResources", "GET", **kwargs)
|
return self.stack_request("ListStackResources", "GET", **kwargs)
|
||||||
@ -86,6 +100,40 @@ class V1Client(base_client.BaseClient):
|
|||||||
def estimate_template_cost(self, **kwargs):
|
def estimate_template_cost(self, **kwargs):
|
||||||
return self.stack_request("EstimateTemplateCost", "GET", **kwargs)
|
return self.stack_request("EstimateTemplateCost", "GET", **kwargs)
|
||||||
|
|
||||||
|
# Dummy print functions for alignment with the boto-based client
|
||||||
|
# which has to extract class fields for printing, we could also
|
||||||
|
# align output format here by decoding the XML/JSON
|
||||||
|
def format_stack_event(self, event):
|
||||||
|
return str(event)
|
||||||
|
|
||||||
|
def format_stack(self, stack):
|
||||||
|
return str(stack)
|
||||||
|
|
||||||
|
def format_stack_resource(self, res):
|
||||||
|
return str(res)
|
||||||
|
|
||||||
|
def format_stack_resource_summary(self, res):
|
||||||
|
return str(res)
|
||||||
|
|
||||||
|
def format_stack_summary(self, summary):
|
||||||
|
return str(summary)
|
||||||
|
|
||||||
|
def format_template(self, template):
|
||||||
|
return str(template)
|
||||||
|
|
||||||
|
def format_parameters(self, options):
|
||||||
|
'''
|
||||||
|
Reformat parameters into dict of format expected by the API
|
||||||
|
'''
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
HeatClient = V1Client
|
HeatClient = V1Client
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user