+++ /dev/null
-heat-cfn
\ No newline at end of file
+++ /dev/null
-#!/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 optparse
-import os
-import os.path
-import sys
-import time
-import logging
-
-import httplib
-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])
-
-from heat.openstack.common import gettextutils
-
-gettextutils.install('heat', lazy=True)
-
-if scriptname == 'heat-boto':
- from heat.cfn_client import boto_client as heat_client
-else:
- from heat.cfn_client import client as heat_client
-from heat.version import version_info as version
-from heat.common import config
-from heat.common import exception
-from heat.cfn_client import utils
-from keystoneclient.v2_0 import client
-
-
-def get_swift_template(options):
- '''
- Retrieve a template from the swift object store, using
- the provided URL. We request a keystone token to authenticate
- '''
- template_body = None
- if options.auth_strategy == 'keystone':
- # we use the keystone credentials to get a token
- # to pass in the request header
- keystone = client.Client(username=options.username,
- password=options.password,
- tenant_name=options.tenant,
- auth_url=options.auth_url)
- logging.info("Getting template from swift URL: %s" %
- options.template_object)
- url = urlparse(options.template_object)
- if url.scheme == 'https':
- conn = httplib.HTTPSConnection(url.netloc)
- else:
- conn = httplib.HTTPConnection(url.netloc)
- headers = {'X-Auth-Token': keystone.auth_token}
- conn.request("GET", url.path, headers=headers)
- r1 = conn.getresponse()
- logging.info('status %d' % r1.status)
- if r1.status == 200:
- template_body = r1.read()
- conn.close()
- else:
- logging.error("template-object option requires keystone")
-
- return template_body
-
-
-def get_template_param(options):
- '''
- Helper function to extract the template in whatever
- format has been specified by the cli options
- '''
- param = {}
- if options.template_file:
- param['TemplateBody'] = open(options.template_file).read()
- elif options.template_url:
- param['TemplateUrl'] = options.template_url
- elif options.template_object:
- template_body = get_swift_template(options)
- if template_body:
- param['TemplateBody'] = template_body
- else:
- logging.error("Error reading swift template")
-
- return param
-
-
-@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-cfn 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 = {}
- templ_param = get_template_param(options)
- if templ_param:
- parameters.update(templ_param)
- else:
- logging.error('Please specify a template file or url')
- return utils.FAILURE
-
- c = get_client(options)
- parameters.update(c.format_parameters(options))
- result = c.validate_template(**parameters)
- print c.format_template(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
-
- templ_param = get_template_param(options)
- if templ_param:
- parameters.update(templ_param)
- else:
- logging.error('Please specify a template file or url')
- return utils.FAILURE
-
- c = get_client(options)
- parameters.update(c.format_parameters(options))
- result = c.estimate_template_cost(**parameters)
- print result
-
-
-@utils.catch_error('gettemplate')
-def get_template(options, arguments):
- '''
- Gets an existing stack template.
- '''
- parameters = {}
- try:
- parameters['StackName'] = arguments.pop(0)
- except IndexError:
- 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(**parameters)
- print result
-
-
-@utils.catch_error('create')
-def stack_create(options, arguments):
- '''
- Create a new stack from a template.
-
- Usage: heat-cfn 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.
- '''
-
- parameters = {}
- try:
- parameters['StackName'] = arguments.pop(0)
- except IndexError:
- logging.error("Please specify the stack name you wish to create")
- logging.error("as the first argument")
- return utils.FAILURE
-
- parameters['TimeoutInMinutes'] = options.timeout
-
- if options.enable_rollback:
- parameters['DisableRollback'] = 'False'
-
- templ_param = get_template_param(options)
- if templ_param:
- parameters.update(templ_param)
- else:
- logging.error('Please specify a template file or url')
- return utils.FAILURE
-
- c = get_client(options)
- parameters.update(c.format_parameters(options))
- result = c.create_stack(**parameters)
- print result
-
-
-@utils.catch_error('update')
-def stack_update(options, arguments):
- '''
- Update an existing stack.
-
- Usage: heat-cfn 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 = {}
- try:
- parameters['StackName'] = arguments.pop(0)
- except IndexError:
- logging.error("Please specify the stack name you wish to update")
- logging.error("as the first argument")
- return utils.FAILURE
-
- templ_param = get_template_param(options)
- if templ_param:
- parameters.update(templ_param)
- else:
- logging.error('Please specify a template file or url')
- return utils.FAILURE
-
- c = get_client(options)
- parameters.update(c.format_parameters(options))
- result = c.update_stack(**parameters)
- 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-cfn delete <stack name>
- '''
- parameters = {}
- try:
- parameters['StackName'] = arguments.pop(0)
- except IndexError:
- 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(**parameters)
- print result
-
-
-@utils.catch_error('describe')
-def stack_describe(options, arguments):
- '''
- Describes an existing stack.
-
- Usage: heat-cfn describe <stack name>
- '''
- parameters = {}
- try:
- parameters['StackName'] = arguments.pop(0)
- except IndexError:
- logging.info("No stack name passed, getting results for ALL stacks")
-
- c = get_client(options)
- result = c.describe_stacks(**parameters)
- print c.format_stack(result)
-
-
-@utils.catch_error('event-list')
-def stack_events_list(options, arguments):
- '''
- List events associated with the given stack.
-
- Usage: heat-cfn event-list <stack name>
- '''
- parameters = {}
- try:
- parameters['StackName'] = arguments.pop(0)
- except IndexError:
- pass
-
- c = get_client(options)
- result = c.list_stack_events(**parameters)
- print c.format_stack_event(result)
-
-
-@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
-
- parameters = {
- 'StackName': stack_name,
- 'LogicalResourceId': resource_name,
- }
- result = c.describe_stack_resource(**parameters)
- print c.format_stack_resource_detail(result)
-
-
-@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
-
- parameters = {
- 'StackName': stack_name,
- }
- result = c.list_stack_resources(**parameters)
- print c.format_stack_resource_summary(result)
-
-
-@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
- parameters = {
- 'NameOrPid': name_or_pid,
- 'LogicalResourceId': logical_resource_id, }
-
- c = get_client(options)
- result = c.describe_stack_resources(**parameters)
-
- if result:
- print c.format_stack_resource(result)
- 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-cfn list
- '''
- c = get_client(options)
- result = c.list_stacks()
- print c.format_stack_summary(result)
-
-
-def get_client(options):
- """
- Returns a new client object to a heat server
- specified by the --host and --port options
- supplied to the CLI
- Note options.host is ignored for heat-boto, host must be
- set in your boto config via the cfn_region_endpoint option
- """
- return heat_client.get_client(host=options.host,
- port=options.port,
- username=options.username,
- password=options.password,
- tenant=options.tenant,
- auth_url=options.auth_url,
- auth_strategy=options.auth_strategy,
- auth_token=options.auth_token,
- region=options.region,
- insecure=options.insecure)
-
-
-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=None,
- 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, defaults to env[OS_USERNAME]")
- parser.add_option('-K', '--password', dest="password",
- metavar="PASSWORD", default=None,
- help="Password used to acquire an authentication "
- "token, defaults to env[OS_PASSWORD]")
- parser.add_option('-T', '--tenant', dest="tenant",
- metavar="TENANT", default=None,
- help="Tenant name used for Keystone authentication, "
- "defaults to env[OS_TENANT_NAME]")
- 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, "
- "defaults to env[OS_AUTH_URL]")
- parser.add_option('-S', '--auth_strategy', dest="auth_strategy",
- metavar="STRATEGY", default=None,
- help="Authentication strategy (keystone or noauth)")
-
- parser.add_option('-o', '--template-object',
- metavar="template_object", default=None,
- help="URL to retrieve template object (e.g from swift)")
- 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.")
-
- parser.add_option('-r', '--enable-rollback', dest="enable_rollback",
- default=False, action="store_true",
- help="Enable rollback on failure")
-
-
-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 scriptname == 'heat-boto':
- if options.host is not None:
- logging.error("Use boto.cfg or ~/.boto cfn_region_endpoint")
- raise ValueError("--host option not supported by heat-boto")
- if options.url is not None:
- logging.error("Use boto.cfg or ~/.boto cfn_region_endpoint")
- raise ValueError("--url option not supported by heat-boto")
-
- if not (options.username and options.password) and not options.auth_token:
- logging.error("Must specify credentials, " +
- "either username/password or token")
- logging.error("See %s --help for options" % scriptname)
- sys.exit(1)
-
- 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,
- 'events_list': stack_events_list, # DEPRECATED
- '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
-
-"""
-
- version_string = version.version_string()
- oparser = optparse.OptionParser(version=version_string,
- usage=usage.strip())
- create_options(oparser)
- try:
- (opts, cmd, args) = parse_options(oparser, sys.argv[1:])
- except ValueError as ex:
- logging.error("Error parsing options : %s" % str(ex))
- sys.exit(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) as ex:
- oparser.print_usage()
- logging.error("ERROR: %s" % ex)
- sys.exit(1)
-
-
-if __name__ == '__main__':
- main()
+++ /dev/null
-#!/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-api-cloudwatch.
-It is simply a command-line interface for adding, modifying, and retrieving
-information about the cloudwatch alarms and metrics belonging to a user.
-It is a convenience application that talks to the heat Cloudwatch API server.
-"""
-
-import optparse
-import os
-import os.path
-import sys
-import time
-import logging
-
-# 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])
-
-from heat.openstack.common import gettextutils
-
-gettextutils.install('heat')
-
-from heat.cfn_client import boto_client_cloudwatch as heat_client
-from heat.version import version_info as version
-from heat.common import exception
-from heat.cfn_client import utils
-
-DEFAULT_PORT = 8003
-
-
-@utils.catch_error('alarm-describe')
-def alarm_describe(options, arguments):
- '''
- Describe detail for specified alarm, or all alarms
- if no AlarmName is specified
- '''
- parameters = {}
- try:
- parameters['AlarmName'] = arguments.pop(0)
- except IndexError:
- logging.info("No AlarmName passed, getting results for ALL alarms")
-
- c = heat_client.get_client(options.port)
- result = c.describe_alarm(**parameters)
- print c.format_metric_alarm(result)
-
-
-@utils.catch_error('alarm-set-state')
-def alarm_set_state(options, arguments):
- '''
- Temporarily set state for specified alarm
- '''
- usage = ('''Usage:
- %s alarm-set-state AlarmName StateValue [StateReason]''' %
- (scriptname))
-
- parameters = {}
- try:
- parameters['AlarmName'] = arguments.pop(0)
- parameters['StateValue'] = arguments.pop(0)
- except IndexError:
- logging.error("Must specify AlarmName and StateValue")
- print usage
- print "StateValue must be one of %s, %s or %s" % (
- heat_client.BotoCWClient.ALARM_STATES)
- return utils.FAILURE
- try:
- parameters['StateReason'] = arguments.pop(0)
- except IndexError:
- parameters['StateReason'] = ""
-
- # We don't handle attaching data to state via this cli tool (yet)
- parameters['StateReasonData'] = None
-
- c = heat_client.get_client(options.port)
- result = c.set_alarm_state(**parameters)
- print result
-
-
-@utils.catch_error('metric-list')
-def metric_list(options, arguments):
- '''
- List all metric data for a given metric (or all metrics if none specified)
- '''
- parameters = {}
- try:
- parameters['MetricName'] = arguments.pop(0)
- except IndexError:
- logging.info("No MetricName passed, getting results for ALL alarms")
-
- c = heat_client.get_client(options.port)
- result = c.list_metrics(**parameters)
- print c.format_metric(result)
-
-
-@utils.catch_error('metric-put-data')
-def metric_put_data(options, arguments):
- '''
- Create a datapoint for a specified metric
- '''
- usage = ('''Usage:
-%s metric-put-data AlarmName Namespace MetricName Units MetricValue
-e.g
-%s metric-put-data HttpFailureAlarm system/linux ServiceFailure Count 1
-''' % (scriptname, scriptname))
-
- # NOTE : we currently only support metric datapoints associated with a
- # specific AlarmName, due to the current engine/db cloudwatch
- # implementation, we should probably revisit this so we can support
- # more generic metric data collection
- parameters = {}
- try:
- parameters['AlarmName'] = arguments.pop(0)
- parameters['Namespace'] = arguments.pop(0)
- parameters['MetricName'] = arguments.pop(0)
- parameters['MetricUnit'] = arguments.pop(0)
- parameters['MetricValue'] = arguments.pop(0)
- except IndexError:
- logging.error("Please specify the alarm, metric, unit and value")
- print usage
- return utils.FAILURE
-
- c = heat_client.get_client(options.port)
- result = c.put_metric_data(**parameters)
- print result
-
-
-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('-p', '--port', dest="port", type=int,
- default=DEFAULT_PORT,
- help="Port the heat API host listens on. "
- "Default: %default")
-
-
-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)
-
- # 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}
-
- watch_commands = {
- 'describe': alarm_describe,
- 'set-state': alarm_set_state,
- 'metric-list': metric_list,
- 'metric-put-data': metric_put_data}
-
- commands = {}
- for command_set in (base_commands, watch_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
-
- describe Describe a specified alarm (or all alarms)
-
- set-state Temporarily set the state of an alarm
-
- metric-list List data-points for specified metric
-
- metric-put-data Publish data-point for specified metric
-
-"""
- version_string = version.version_string()
- oparser = optparse.OptionParser(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) as ex:
- oparser.print_usage()
- logging.error("ERROR: %s" % ex)
- sys.exit(1)
-
-
-if __name__ == '__main__':
- main()
('man/heat-api-cloudwatch', 'heat-api-cloudwatch',
u'CloudWatch alike API service to the heat project',
[u'Heat Developers'], 1),
- ('man/heat-boto', 'heat-boto',
- u'Command line utility to run heat actions over the CloudFormation API',
- [u'Heat Developers'], 1),
- ('man/heat-cfn', 'heat-cfn',
- u'Command line utility to run heat actions over the CloudFormation API',
- [u'Heat Developers'], 1),
('man/heat-db-setup', 'heat-db-setup',
u'Command line utility to setup the Heat database',
[u'Heat Developers'], 1),
('man/heat-keystone-setup', 'heat-keystone-setup',
u'Script which sets up keystone for usage by Heat',
[u'Heat Developers'], 1),
- ('man/heat-watch', 'heat-watch',
- u'Command line utility to run heat watch actions over the CloudWatch API',
- [u'Heat Developers'], 1),
]
# If true, show URL addresses after external links.
+++ /dev/null
-=========
-heat-boto
-=========
-
-.. program:: heat-boto
-
-SYNOPSIS
-========
-
-``heat-boto [OPTIONS] COMMAND [COMMAND_OPTIONS]``
-
-DESCRIPTION
-===========
-heat-boto is a command-line utility for heat. It is a variant of the heat-cfn
-tool which uses the boto client library (instead of the heat CFN client
-library)
-
-The tool provides an interface for adding, modifying, and retrieving
-information about the stacks belonging to a user. It is a convenience
-application that talks to the heat CloudFormation API.
-
-
-CONFIGURATION
-=============
-
-heat-watch uses the boto client library, and expects some configuration files
-to exist in your environment, see our wiki for an example configuration file:
-
-https://wiki.openstack.org/wiki/Heat/Using-Boto
-
-
-COMMANDS
-========
-
-``create``
-
- Create stack as defined in template file
-
-``delete``
-
- Delete specified stack
-
-``describe``
-
- Provide detailed information about the specified stack, or if no arguments are given all stacks
-
-``estimate-template-cost``
-
- Currently not implemented
-
-``event-list``
-
- List events related to specified stacks, or if no arguments are given all stacks
-
-``gettemplate``
-
- Get the template for a running stack
-
-``help``
-
- Provide help/usage information
-
-``list``
-
- List summary information for all stacks
-
-``resource``
-
- List information about a specific resource
-
-``resource-list``
-
- List all resources for a specified stack
-
-``resource-list-details``
-
- List details of all resources for a specified stack or physical resource ID, optionally filtered by a logical resource ID
-
-``update``
-
- Update a running stack with a modified template or template parameters - currently not implemented
-
-``validate``
-
- Validate a template file syntax
-
-
-OPTIONS
-=======
-
-Note some options are marked as having no effect due to the common implementation with heat-cfn.
-These are options which work with heat-cfn, but not with heat-boto, in most cases the information
-should be specified via your boto configuration file instead.
-
-.. cmdoption:: -S, --auth_strategy
-
- This option has no effect, credentials should be specified in your boto config
-
-.. cmdoption:: -A, --auth_token
-
- This option has no effect, credentials should be specified in your boto config
-
-.. cmdoption:: -N, --auth_url
-
- This option has no effect, credentials should be specified in your boto config
-
-.. cmdoption:: -d, --debug
-
- Enable verbose debug level output
-
-.. cmdoption:: -H, --host
-
- Note, this option does not work for heat-boto due to limitations of the boto library
- You should specify cfn_region_endpoint option in your boto config.
-
-.. cmdoption:: -k, --insecure
-
- This option has no effect, is_secure should be specified in your boto config
-
-.. cmdoption:: -P, --parameters
-
- Stack input parameters
-
-.. cmdoption:: -K, --password
-
- This option has no effect, credentials should be specified in your boto config
-
-.. cmdoption:: -p, --port
-
- Specify the port to connect to for the heat API service
-
-.. cmdoption:: -R, --region
-
- This option has no effect, credentials should be specified in your boto config
-
-.. cmdoption:: -f, --template-file
-
- Path to file containing the stack template
-
-.. cmdoption:: -u, --template-url
-
- URL to stack template
-
-.. cmdoption:: -T, --tenant
-
- This option has no effect, credentials should be specified in your boto config
-
-.. cmdoption:: -t, --timeout
-
- Stack creation timeout (default is 60 minutes)
-
-.. cmdoption:: -U, --url
-
- This option has no effect, cfn_region_endpoint should be specified in your boto config
-
-.. cmdoption:: -I, --username
-
- This option has no effect, credentials should be specified in your boto config
-
-.. cmdoption:: -v, --verbose
-
- Enable verbose output
-
-.. cmdoption:: -y, --yes
-
- Do not prompt for confirmation, assume yes
-
-
-EXAMPLES
-========
- heat-boto -d create wordpress \\
- --template-file=templates/WordPress_Single_Instance.template\\
- --parameters="InstanceType=m1.xlarge;DBUsername=${USER};\\
- DBPassword=verybadpass;KeyName=${USER}_key"
-
- heat-boto list
-
- heat-boto describe wordpress
-
- heat-boto resource-list wordpress
-
- heat-boto resource-list-details wordpress
-
- heat-boto resource-list-details wordpress WikiDatabase
-
- heat-boto resource wordpress WikiDatabase
-
- heat-boto event-list
-
- heat-boto delete wordpress
-
-BUGS
-====
-Heat bugs are managed through Launchpad <https://launchpad.net/heat>
+++ /dev/null
-========
-heat-cfn
-========
-
-.. program:: heat-cfn
-
-SYNOPSIS
-========
-
-``heat-cfn [OPTIONS] COMMAND [COMMAND_OPTIONS]``
-
-DESCRIPTION
-===========
-heat-cfn is a command-line utility for heat. It is simply an
-interface for adding, modifying, and retrieving information about the stacks
-belonging to a user. It is a convenience application that talks to the heat
-CloudFormation API compatable server.
-
-
-CONFIGURATION
-=============
-
-heat-cfn uses keystone authentication, and expects some variables to be
-set in your environment, without these heat will not be able to establish
-an authenticated connection with the heat API server.
-
-Example:
-
-export ADMIN_TOKEN=<keystone admin token>
-
-export OS_USERNAME=admin
-
-export OS_PASSWORD=verybadpass
-
-export OS_TENANT_NAME=admin
-
-export OS_AUTH_URL=http://127.0.0.1:5000/v2.0/
-
-export OS_AUTH_STRATEGY=keystone
-
-
-
-COMMANDS
-========
-
-``create``
-
- Create stack as defined in template file
-
-``delete``
-
- Delete specified stack
-
-``describe``
-
- Provide detailed information about the specified stack, or if no arguments are given all stacks
-
-``estimate-template-cost``
-
- Currently not implemented
-
-``event-list``
-
- List events related to specified stacks, or if no arguments are given all stacks
-
-``gettemplate``
-
- Get the template for a running stack
-
-``help``
-
- Provide help/usage information
-
-``list``
-
- List summary information for all stacks
-
-``resource``
-
- List information about a specific resource
-
-``resource-list``
-
- List all resources for a specified stack
-
-``resource-list-details``
-
- List details of all resources for a specified stack or physical resource ID, optionally filtered by a logical resource ID
-
-``update``
-
- Update a running stack with a modified template or template parameters - currently not implemented
-
-``validate``
-
- Validate a template file syntax
-
-
-OPTIONS
-=======
-
-.. cmdoption:: -S, --auth_strategy
-
- Authentication strategy
-
-.. cmdoption:: -A, --auth_token
-
- Authentication token to use to identify the client to the heat server
-
-.. cmdoption:: -N, --auth_url
-
- Authentication URL for keystone authentication
-
-.. cmdoption:: -d, --debug
-
- Enable verbose debug level output
-
-.. cmdoption:: -H, --host
-
- Specify the hostname running the heat API service
-
-.. cmdoption:: -k, --insecure
-
- Use plain HTTP instead of HTTPS
-
-.. cmdoption:: -P, --parameters
-
- Stack input parameters
-
-.. cmdoption:: -K, --password
-
- Password used to acquire an authentication token
-
-.. cmdoption:: -p, --port
-
- Specify the port to connect to for the heat API service
-
-.. cmdoption:: -R, --region
-
- Region name. When using keystone authentication "version 2.0 or later this identifies the region
-
-.. cmdoption:: -f, --template-file
-
- Path to file containing the stack template
-
-.. cmdoption:: -u, --template-url
-
- URL to stack template
-
-.. cmdoption:: -T, --tenant
-
- Tenant name used for Keystone authentication
-
-.. cmdoption:: -t, --timeout
-
- Stack creation timeout (default is 60 minutes)
-
-.. cmdoption:: -U, --url
-
- URL of heat service
-
-.. cmdoption:: -I, --username
-
- User name used to acquire an authentication token
-
-.. cmdoption:: -v, --verbose
-
- Enable verbose output
-
-.. cmdoption:: -y, --yes
-
- Do not prompt for confirmation, assume yes
-
-
-EXAMPLES
-========
- heat-cfn -d create wordpress --template-
- file=templates/WordPress_Single_Instance.template
- --parameters="InstanceType=m1.xlarge;DBUsername=${USER};DBPassword=verybadpass;KeyName=${USER}_key"
-
- heat-cfn list
-
- heat-cfn describe wordpress
-
- heat-cfn resource-list wordpress
-
- heat-cfn resource-list-details wordpress
-
- heat-cfn resource-list-details wordpress WikiDatabase
-
- heat-cfn resource wordpress WikiDatabase
-
- heat-cfn event-list
-
- heat-cfn delete wordpress
-
-BUGS
-====
-Heat bugs are managed through Launchpad <https://launchpad.net/heat>
\ No newline at end of file
+++ /dev/null
-==========
-heat-watch
-==========
-
-.. program:: heat-watch
-
-
-SYNOPSIS
-========
-
-``heat-watch [OPTIONS] COMMAND [COMMAND_OPTIONS]``
-
-
-DESCRIPTION
-===========
-heat-watch is a command-line utility for heat-api-cloudwatch.
-It allows manipulation of the watch alarms and metric data via the heat
-cloudwatch API, so this service must be running and accessibe on the host
-specified in your boto config (cloudwatch_region_endpoint)
-
-
-CONFIGURATION
-=============
-
-heat-watch uses the boto client library, and expects some configuration files
-to exist in your environment, see our wiki for an example configuration file:
-
-https://wiki.openstack.org/wiki/Heat/Using-Boto
-
-
-COMMANDS
-========
-
-``describe``
-
- Provide detailed information about the specified watch rule, or if no arguments are given all watch rules
-
-``set-state``
-
- Temporarily set the state of a watch rule
-
-``metric-list``
-
- List data-points for a specified metric
-
-``metric-put-data``
-
- Publish data-point for specified metric
-
- Note the metric must be associated with a CloudWatch Alarm (specified in a heat stack template), publishing arbitrary metric data is not supported.
-
-``help``
-
- Provide help/usage information on each command
-
-
-OPTIONS
-=======
-
-.. cmdoption:: --version
-
- show program version number and exit
-
-.. cmdoption:: -h, --help
-
- show this help message and exit
-
-.. cmdoption:: -v, --verbose
-
- Print more verbose output
-
-.. cmdoption:: -d, --debug
-
- Print debug output
-
-.. cmdoption:: -p, --port
-
- Specify port the heat CW API host listens on. Default: 8003
-
-
-EXAMPLES
-========
-
- heat-watch describe
-
- heat-watch metric-list
-
- heat-watch metric-put-data HttpFailureAlarm system/linux ServiceFailure Count 1
-
- heat-watch set-state HttpFailureAlarm ALARM
-
-
-BUGS
-====
-Heat bugs are managed through Launchpad <https://launchpad.net/heat>
.. toctree::
:maxdepth: 2
- heat-cfn
- heat-boto
- heat-watch
heat-db-setup
heat-keystone-setup
+++ /dev/null
-_heat()
-{
- local cur prev opts
- COMPREPLY=()
- cur="${COMP_WORDS[COMP_CWORD]}"
- prev="${COMP_WORDS[COMP_CWORD-1]}"
-
- if [[ ${cur} == -* ]]; then
- opts=$(heat-cfn --help | grep -A100 "^Options" | sed -r "s/^[[:space:]]*-[[:alpha:]]([[:space:]][[:alpha:]_]*,|,)[[:space:]]//" | cut -d "=" -f1 | grep "^--" | awk '{print $1}')
- COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
- return 0
- fi
-
- if [[ ${#COMP_WORDS[@]} -gt 2 ]]; then
- return 0
- else
- cmds=$(heat-cfn help | awk '{print $1}' | egrep -v "^(Usage|Commands|$)")
- COMPREPLY=( $(compgen -W "${cmds}" -- ${cur}) )
- return 0
- fi
-}
-complete -F _heat heat-cfn
+++ /dev/null
-#[Credentials]
-# AWS credentials, from keystone ec2-credentials-list
-# Note this section should only be uncommented for per-user
-# boto config files, copy this file to ~/.boto
-# Alternatively the credentials can be passed into the boto
-# client at constructor-time in your code
-#aws_access_key_id = YOUR_KEY
-#aws_secret_access_key = YOUR_SECKEY
-
-[Boto]
-# Make boto output verbose debugging information
-debug = 0
-
-# Disable https connections
-is_secure = 0
-
-# Override the default AWS endpoint to connect to heat on localhost
-cfn_region_name = heat
-cfn_region_endpoint = 127.0.0.1
-
-cloudwatch_region_name = heat
-cloudwatch_region_endpoint = 127.0.0.1
-
-# Set the client retries to 1, or errors connecting to heat repeat
-# which is not useful when debugging API issues
-num_retries = 1
+++ /dev/null
-# 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
-"""
-
-import sys
-
-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):
- args = {'disable_rollback': True}
- if str(kwargs.get('DisableRollback', '')).lower() == 'false':
- args['disable_rollback'] = False
-
- if 'TimeoutInMinutes' in kwargs:
- try:
- timeout = int(kwargs['TimeoutInMinutes'])
- except ValueError:
- logger.error("Invalid timeout %s" % kwargs['TimeoutInMinutes'])
- return
- else:
- args['timeout_in_minutes'] = timeout
-
- if 'TemplateUrl' in kwargs:
- return super(BotoClient, self).create_stack(
- kwargs['StackName'],
- template_url=kwargs['TemplateUrl'],
- parameters=kwargs['Parameters'],
- **args)
- elif 'TemplateBody' in kwargs:
- return super(BotoClient, self).create_stack(
- kwargs['StackName'],
- template_body=kwargs['TemplateBody'],
- parameters=kwargs['Parameters'],
- **args)
- 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
+++ /dev/null
-# 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
-"""
-
-import sys
-
-from heat.openstack.common import log as logging
-logger = logging.getLogger(__name__)
-
-from boto.ec2.cloudwatch import CloudWatchConnection
-
-
-class BotoCWClient(CloudWatchConnection):
- '''
- Wrapper class for boto CloudWatchConnection class
- '''
- # TODO(unknown) : These should probably go in the CW API and be imported
- DEFAULT_NAMESPACE = "heat/unknown"
- METRIC_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)
- METRIC_COMPARISONS = (">=", ">", "<", "<=")
- ALARM_STATES = ("OK", "ALARM", "INSUFFICIENT_DATA")
- METRIC_STATISTICS = ("Average", "Sum", "SampleCount", "Maximum", "Minimum")
-
- # Note, several of these boto calls take a list of alarm names, so
- # we could easily handle multiple alarms per-action, but in the
- # interests of keeping the client simple, we just handle one 'AlarmName'
-
- def describe_alarm(self, **kwargs):
- # If no AlarmName specified, we pass None, which returns
- # results for ALL alarms
- try:
- name = kwargs['AlarmName']
- except KeyError:
- name = None
- return super(BotoCWClient, self).describe_alarms(
- alarm_names=[name])
-
- def list_metrics(self, **kwargs):
- # list_metrics returns non-null index in next_token if there
- # are more than 500 metric results, in which case we have to
- # re-read with the token to get the next batch of results
- #
- # Also note that we can do more advanced filtering by dimension
- # and/or namespace, but for simplicity we only filter by
- # MetricName for the time being
- try:
- name = kwargs['MetricName']
- except KeyError:
- name = None
-
- results = []
- token = None
- while True:
- results.append(super(BotoCWClient, self).list_metrics(
- next_token=token,
- dimensions=None,
- metric_name=name,
- namespace=None))
- if not token:
- break
-
- return results
-
- def put_metric_data(self, **kwargs):
- '''
- Publish metric data points to CloudWatch
- '''
- try:
- metric_name = kwargs['MetricName']
- metric_unit = kwargs['MetricUnit']
- metric_value = kwargs['MetricValue']
- metric_namespace = kwargs['Namespace']
- except KeyError:
- logger.error("Must pass MetricName, MetricUnit, " +
- "Namespace, MetricValue!")
- return
-
- try:
- metric_unit = kwargs['MetricUnit']
- except KeyError:
- metric_unit = None
-
- # If we're passed AlarmName, we attach it to the metric
- # as a dimension
- try:
- metric_dims = [{'AlarmName': kwargs['AlarmName']}]
- except KeyError:
- metric_dims = []
-
- if metric_unit not in self.METRIC_UNITS:
- logger.error("MetricUnit not an allowed value")
- logger.error("MetricUnit must be one of %s" % self.METRIC_UNITS)
- return
-
- return super(BotoCWClient, self).put_metric_data(
- namespace=metric_namespace,
- name=metric_name,
- value=metric_value,
- timestamp=None, # This means use "now" in the engine
- unit=metric_unit,
- dimensions=metric_dims,
- statistics=None)
-
- def set_alarm_state(self, **kwargs):
- return super(BotoCWClient, self).set_alarm_state(
- alarm_name=kwargs['AlarmName'],
- state_reason=kwargs['StateReason'],
- state_value=kwargs['StateValue'],
- state_reason_data=kwargs['StateReasonData'])
-
- def format_metric_alarm(self, alarms):
- '''
- Return string formatted representation of
- boto.ec2.cloudwatch.alarm.MetricAlarm objects
- '''
- ret = []
- for s in alarms:
- ret.append("AlarmName : %s" % s.name)
- ret.append("AlarmDescription : %s" % s.description)
- ret.append("ActionsEnabled : %s" % s.actions_enabled)
- ret.append("AlarmActions : %s" % s.alarm_actions)
- ret.append("AlarmArn : %s" % s.alarm_arn)
- ret.append("AlarmConfigurationUpdatedTimestamp : %s" %
- s.last_updated)
- ret.append("ComparisonOperator : %s" % s.comparison)
- ret.append("Dimensions : %s" % s.dimensions)
- ret.append("EvaluationPeriods : %s" % s.evaluation_periods)
- ret.append("InsufficientDataActions : %s" %
- s.insufficient_data_actions)
- ret.append("MetricName : %s" % s.metric)
- ret.append("Namespace : %s" % s.namespace)
- ret.append("OKActions : %s" % s.ok_actions)
- ret.append("Period : %s" % s.period)
- ret.append("StateReason : %s" % s.state_reason)
- ret.append("StateUpdatedTimestamp : %s" %
- s.last_updated)
- ret.append("StateValue : %s" % s.state_value)
- ret.append("Statistic : %s" % s.statistic)
- ret.append("Threshold : %s" % s.threshold)
- ret.append("Unit : %s" % s.unit)
- ret.append("--")
- return '\n'.join(ret)
-
- def format_metric(self, metrics):
- '''
- Return string formatted representation of
- boto.ec2.cloudwatch.metric.Metric objects
- '''
- # Boto appears to return metrics as a list-inside-a-list
- # probably a bug in boto, but work around here
- if len(metrics) == 1:
- metlist = metrics[0]
- elif len(metrics) == 0:
- metlist = []
- else:
- # Shouldn't get here, unless boto gets fixed..
- logger.error("Unexpected metric list-of-list length (boto fixed?)")
- return "ERROR\n--"
-
- ret = []
- for m in metlist:
- ret.append("MetricName : %s" % m.name)
- ret.append("Namespace : %s" % m.namespace)
- ret.append("Dimensions : %s" % m.dimensions)
- ret.append("--")
- return '\n'.join(ret)
-
-
-def get_client(port=None, aws_access_key=None, aws_secret_key=None):
- """
- Returns a new boto CloudWatch client connection to a heat server
- Note : Configuration goes in /etc/boto.cfg, not via arguments
- """
-
- # 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
- cloudwatch = BotoCWClient(aws_access_key_id=aws_access_key,
- aws_secret_access_key=aws_secret_key,
- is_secure=False,
- port=port,
- path="/v1")
- if cloudwatch:
- logger.debug("Got CW connection object OK")
- else:
- logger.error("Error establishing CloudWatch connection!")
- sys.exit(1)
-
- return cloudwatch
+++ /dev/null
-# 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 classes for callers of a heat system
-"""
-
-from lxml import etree
-from heat.common import client as base_client
-from heat.common import exception
-
-from heat.openstack.common import log as logging
-
-logger = logging.getLogger(__name__)
-
-
-SUPPORTED_PARAMS = ('StackName', 'TemplateBody', 'TemplateUrl',
- 'NotificationARNs', 'Parameters', 'Version',
- 'SignatureVersion', 'Timestamp', 'AWSAccessKeyId',
- 'Signature', 'TimeoutInMinutes', 'DisableRollback',
- 'LogicalResourceId', 'PhysicalResourceId', 'NextToken',
- )
-
-
-class V1Client(base_client.BaseClient):
-
- """Main client class for accessing heat resources."""
-
- DEFAULT_DOC_ROOT = "/v1"
-
- def _insert_common_parameters(self, params):
- params['Version'] = '2010-05-15'
- params['SignatureVersion'] = '2'
- params['SignatureMethod'] = 'HmacSHA256'
-
- def stack_request(self, action, method, **kwargs):
- params = self._extract_params(kwargs, SUPPORTED_PARAMS)
- self._insert_common_parameters(params)
- params['Action'] = action
- headers = {'X-Auth-User': self.creds['username'],
- 'X-Auth-Key': self.creds['password']}
-
- res = self.do_request(method, "/", params=params, headers=headers)
- doc = etree.fromstring(res.read())
- return etree.tostring(doc, pretty_print=True)
-
- def list_stacks(self, **kwargs):
- return self.stack_request("ListStacks", "GET", **kwargs)
-
- def describe_stacks(self, **kwargs):
- return self.stack_request("DescribeStacks", "GET", **kwargs)
-
- def create_stack(self, **kwargs):
- return self.stack_request("CreateStack", "POST", **kwargs)
-
- def update_stack(self, **kwargs):
- return self.stack_request("UpdateStack", "POST", **kwargs)
-
- def delete_stack(self, **kwargs):
- return self.stack_request("DeleteStack", "GET", **kwargs)
-
- def list_stack_events(self, **kwargs):
- return self.stack_request("DescribeStackEvents", "GET", **kwargs)
-
- def describe_stack_resource(self, **kwargs):
- return self.stack_request("DescribeStackResource", "GET", **kwargs)
-
- def describe_stack_resources(self, **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 Exception:
- 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):
- return self.stack_request("ListStackResources", "GET", **kwargs)
-
- def validate_template(self, **kwargs):
- return self.stack_request("ValidateTemplate", "GET", **kwargs)
-
- def get_template(self, **kwargs):
- return self.stack_request("GetTemplate", "GET", **kwargs)
-
- def estimate_template_cost(self, **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_stack_resource_detail(self, res):
- return str(res)
-
- 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('=', 1)
- parameters['Parameters.member.%d.ParameterKey' % count] = n
- parameters['Parameters.member.%d.ParameterValue' % count] = v
- return parameters
-
-
-HeatClient = V1Client
-
-
-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=False):
- """
- Returns a new client heat client object based on common kwargs.
- If an option isn't specified falls back to common environment variable
- defaults.
- """
-
- if auth_url:
- force_strategy = 'keystone'
- else:
- force_strategy = None
-
- creds = dict(username=username,
- password=password,
- tenant=tenant,
- auth_url=auth_url,
- strategy=force_strategy or auth_strategy,
- region=region)
-
- if creds['strategy'] == 'keystone' and not creds['auth_url']:
- msg = ("--auth_url option or OS_AUTH_URL environment variable "
- "required when keystone authentication strategy is enabled\n")
- raise exception.ClientConfigurationError(msg)
-
- use_ssl = (creds['auth_url'] is not None and
- creds['auth_url'].find('https') != -1)
-
- client = HeatClient
-
- return client(host=host,
- port=port,
- use_ssl=use_ssl,
- auth_tok=auth_token,
- creds=creds,
- insecure=insecure,
- service_type='cloudformation')
+++ /dev/null
-# 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.
-
-import functools
-from heat.common import exception
-from heat.openstack.common import log as logging
-
-LOG = logging.getLogger(__name__)
-
-SUCCESS = 0
-FAILURE = 1
-
-
-def catch_error(action):
- """Decorator to provide sensible default error handling for CLI actions."""
- def wrap(func):
- @functools.wraps(func)
- def wrapper(*arguments, **kwargs):
- try:
- ret = func(*arguments, **kwargs)
- return SUCCESS if ret is None else ret
- except exception.NotAuthorized:
- LOG.error("Not authorized to make this request. Check " +
- "your credentials (OS_USERNAME, OS_PASSWORD, " +
- "OS_TENANT_NAME, OS_AUTH_URL and OS_AUTH_STRATEGY).")
- return FAILURE
- except exception.ClientConfigurationError:
- raise
- except exception.KeystoneError as e:
- LOG.error("Keystone did not finish the authentication and "
- "returned the following message:\n\n%s" % e.message)
- return FAILURE
- except Exception as e:
- options = arguments[0]
- if options.debug:
- raise
- LOG.error("Failed to %s. Got error:" % action)
- pieces = unicode(e).split('\n')
- for piece in pieces:
- LOG.error(piece)
- return FAILURE
-
- return wrapper
- return wrap
+++ /dev/null
-# 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 auth module is intended to allow Openstack client-tools to select from a
-variety of authentication strategies, including NoAuth (the default), and
-Keystone (an identity management system).
-
- > auth_plugin = AuthPlugin(creds)
-
- > auth_plugin.authenticate()
-
- > auth_plugin.auth_token
- abcdefg
-
- > auth_plugin.management_url
- http://service_endpoint/
-"""
-import httplib2
-import json
-import urlparse
-
-from heat.common import exception
-
-from heat.openstack.common.gettextutils import _
-
-
-class BaseStrategy(object):
- def __init__(self):
- self.auth_token = None
- # TODO(sirp): Should expose selecting public/internal/admin URL.
- self.management_url = None
-
- def authenticate(self):
- raise NotImplementedError
-
- @property
- def is_authenticated(self):
- raise NotImplementedError
-
- @property
- def strategy(self):
- raise NotImplementedError
-
-
-class NoAuthStrategy(BaseStrategy):
- def authenticate(self):
- pass
-
- @property
- def is_authenticated(self):
- return True
-
- @property
- def strategy(self):
- return 'noauth'
-
-
-class KeystoneStrategy(BaseStrategy):
- MAX_REDIRECTS = 10
-
- def __init__(self, creds, service_type):
- self.creds = creds
- self.service_type = service_type
- super(KeystoneStrategy, self).__init__()
-
- def check_auth_params(self):
- # Ensure that supplied credential parameters are as required
- for required in ('username', 'password', 'auth_url',
- 'strategy'):
- if required not in self.creds:
- raise exception.MissingCredentialError(required=required)
- if self.creds['strategy'] != 'keystone':
- raise exception.BadAuthStrategy(expected='keystone',
- received=self.creds['strategy'])
- # For v2.0 also check tenant is present
- if self.creds['auth_url'].rstrip('/').endswith('v2.0'):
- if 'tenant' not in self.creds:
- raise exception.MissingCredentialError(required='tenant')
-
- def authenticate(self):
- """Authenticate with the Keystone service.
-
- There are a few scenarios to consider here:
-
- 1. Which version of Keystone are we using? v1 which uses headers to
- pass the credentials, or v2 which uses a JSON encoded request body?
-
- 2. Keystone may respond back with a redirection using a 305 status
- code.
-
- 3. We may attempt a v1 auth when v2 is what's called for. In this
- case, we rewrite the url to contain /v2.0/ and retry using the v2
- protocol.
- """
- def _authenticate(auth_url):
- # If OS_AUTH_URL is missing a trailing slash add one
- if not auth_url.endswith('/'):
- auth_url += '/'
- token_url = urlparse.urljoin(auth_url, "tokens")
- # 1. Check Keystone version
- is_v2 = auth_url.rstrip('/').endswith('v2.0')
- if is_v2:
- self._v2_auth(token_url)
- else:
- self._v1_auth(token_url)
-
- self.check_auth_params()
- auth_url = self.creds['auth_url']
- for x in range(self.MAX_REDIRECTS):
- try:
- _authenticate(auth_url)
- except exception.AuthorizationRedirect as e:
- # 2. Keystone may redirect us
- auth_url = e.url
- except exception.AuthorizationFailure:
- # 3. In some configurations nova makes redirection to
- # v2.0 keystone endpoint. Also, new location does not
- # contain real endpoint, only hostname and port.
- if 'v2.0' not in auth_url:
- auth_url = urlparse.urljoin(auth_url, 'v2.0/')
- else:
- # If we sucessfully auth'd, then memorize the correct auth_url
- # for future use.
- self.creds['auth_url'] = auth_url
- break
- else:
- # Guard against a redirection loop
- raise exception.MaxRedirectsExceeded(redirects=self.MAX_REDIRECTS)
-
- def _v1_auth(self, token_url):
- creds = self.creds
-
- headers = {}
- headers['X-Auth-User'] = creds['username']
- headers['X-Auth-Key'] = creds['password']
-
- tenant = creds.get('tenant')
- if tenant:
- headers['X-Auth-Tenant'] = tenant
-
- resp, resp_body = self._do_request(token_url, 'GET', headers=headers)
-
- def _management_url(self, resp):
- for url_header in ('x-heat-management-url',
- 'x-server-management-url',
- 'x-heat'):
- try:
- return resp[url_header]
- except KeyError as e:
- not_found = e
- raise not_found
-
- if resp.status in (200, 204):
- try:
- self.management_url = _management_url(self, resp)
- self.auth_token = resp['x-auth-token']
- except KeyError:
- raise exception.AuthorizationFailure()
- elif resp.status == 305:
- raise exception.AuthorizationRedirect(resp['location'])
- elif resp.status == 400:
- raise exception.AuthBadRequest(url=token_url)
- elif resp.status == 401:
- raise exception.NotAuthorized()
- elif resp.status == 404:
- raise exception.AuthUrlNotFound(url=token_url)
- else:
- status = resp.status
- raise Exception(_('Unexpected response: %(status)s')
- % {'status': resp.status})
-
- def _v2_auth(self, token_url):
- def get_endpoint(service_catalog):
- """
- Select an endpoint from the service catalog
-
- We search the full service catalog for services
- matching both type and region. If the client
- supplied no region then any endpoint for the service
- is considered a match. There must be one -- and
- only one -- successful match in the catalog,
- otherwise we will raise an exception.
- """
- region = self.creds.get('region')
-
- service_type_matches = lambda s: s.get('type') == self.service_type
- region_matches = lambda e: region is None or e['region'] == region
-
- endpoints = [ep for s in service_catalog if service_type_matches(s)
- for ep in s['endpoints'] if region_matches(ep)]
-
- if len(endpoints) > 1:
- raise exception.RegionAmbiguity(region=region)
- elif not endpoints:
- raise exception.NoServiceEndpoint()
- else:
- # FIXME(sirp): for now just use the public url.
- return endpoints[0]['publicURL']
-
- creds = self.creds
-
- creds = {
- "auth": {
- "tenantName": creds['tenant'],
- "passwordCredentials": {
- "username": creds['username'],
- "password": creds['password']}}}
-
- headers = {}
- headers['Content-Type'] = 'application/json'
- req_body = json.dumps(creds)
-
- resp, resp_body = self._do_request(
- token_url, 'POST', headers=headers, body=req_body)
-
- if resp.status == 200:
- resp_auth = json.loads(resp_body)['access']
- self.management_url = get_endpoint(resp_auth['serviceCatalog'])
- self.auth_token = resp_auth['token']['id']
- elif resp.status == 305:
- raise exception.RedirectException(resp['location'])
- elif resp.status == 400:
- raise exception.AuthBadRequest(url=token_url)
- elif resp.status == 401:
- raise exception.NotAuthorized()
- elif resp.status == 404:
- raise exception.AuthUrlNotFound(url=token_url)
- else:
- try:
- body = json.loads(resp_body)
- msg = body['error']['message']
- except (ValueError, KeyError):
- msg = resp_body
- raise exception.KeystoneError(resp.status, msg)
-
- @property
- def is_authenticated(self):
- return self.auth_token is not None
-
- @property
- def strategy(self):
- return 'keystone'
-
- @staticmethod
- def _do_request(url, method, headers=None, body=None):
- headers = headers or {}
- conn = httplib2.Http()
- conn.force_exception_to_status_code = True
- headers['User-Agent'] = 'heat-client'
- resp, resp_body = conn.request(url, method, headers=headers, body=body)
- return resp, resp_body
-
-
-def get_plugin_from_strategy(strategy, creds=None, service_type=None):
- if strategy == 'noauth':
- return NoAuthStrategy()
- elif strategy == 'keystone':
- return KeystoneStrategy(creds, service_type)
- else:
- raise Exception(_("Unknown auth strategy '%s'") % strategy)
+++ /dev/null
-# 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.
-
-# HTTPSClientAuthConnection code comes courtesy of ActiveState website:
-# http://code.activestate.com/recipes/
-# 577548-https-httplib-client-connection-with-certificate-v/
-
-import collections
-import functools
-import httplib
-import os
-import urllib
-import urlparse
-
-try:
- from eventlet.green import socket
- from eventlet.green import ssl
-except ImportError:
- import socket
- import ssl
-
-from heat.common import auth
-from heat.common import exception
-from heat.common import utils
-
-from heat.openstack.common.gettextutils import _
-
-
-# common chunk size for get and put
-CHUNKSIZE = 65536
-
-
-def handle_unauthorized(func):
- """
- Wrap a function to re-authenticate and retry.
- """
- @functools.wraps(func)
- def wrapped(self, *args, **kwargs):
- try:
- return func(self, *args, **kwargs)
- except exception.NotAuthorized:
- self._authenticate(force_reauth=True)
- return func(self, *args, **kwargs)
- return wrapped
-
-
-def handle_redirects(func):
- """
- Wrap the _do_request function to handle HTTP redirects.
- """
- MAX_REDIRECTS = 5
-
- @functools.wraps(func)
- def wrapped(self, method, url, body, headers):
- for _ in xrange(MAX_REDIRECTS):
- try:
- return func(self, method, url, body, headers)
- except exception.RedirectException as redirect:
- if redirect.url is None:
- raise exception.InvalidRedirect()
- url = redirect.url
- raise exception.MaxRedirectsExceeded(redirects=MAX_REDIRECTS)
- return wrapped
-
-
-class ImageBodyIterator(object):
-
- """
- A class that acts as an iterator over an image file's
- chunks of data. This is returned as part of the result
- tuple from `heat.cfn_client.client.Client.get_image`
- """
-
- def __init__(self, source):
- """
- Constructs the object from a readable image source
- (such as an HTTPResponse or file-like object)
- """
- self.source = source
-
- def __iter__(self):
- """
- Exposes an iterator over the chunks of data in the
- image file.
- """
- while True:
- chunk = self.source.read(CHUNKSIZE)
- if chunk:
- yield chunk
- else:
- break
-
-
-class HTTPSClientAuthConnection(httplib.HTTPSConnection):
- """
- Class to make a HTTPS connection, with support for
- full client-based SSL Authentication
-
- :see http://code.activestate.com/recipes/
- 577548-https-httplib-client-connection-with-certificate-v/
- """
-
- def __init__(self, host, port, key_file, cert_file,
- ca_file, timeout=None, insecure=False):
- httplib.HTTPSConnection.__init__(self, host, port, key_file=key_file,
- cert_file=cert_file)
- self.key_file = key_file
- self.cert_file = cert_file
- self.ca_file = ca_file
- self.timeout = timeout
- self.insecure = insecure
-
- def connect(self):
- """
- Connect to a host on a given (SSL) port.
- If ca_file is pointing somewhere, use it to check Server Certificate.
-
- Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
- This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to
- ssl.wrap_socket(), which forces SSL to check server certificate against
- our client certificate.
- """
- sock = socket.create_connection((self.host, self.port), self.timeout)
- if self._tunnel_host:
- self.sock = sock
- self._tunnel()
- # Check CA file unless 'insecure' is specificed
- if self.insecure is True:
- self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
- cert_reqs=ssl.CERT_NONE)
- else:
- self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
- ca_certs=self.ca_file,
- cert_reqs=ssl.CERT_REQUIRED)
-
-
-class BaseClient(object):
-
- """A base client class."""
-
- DEFAULT_PORT = 80
- DEFAULT_DOC_ROOT = None
- # Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
- # Suse, FreeBSD/OpenBSD
- DEFAULT_CA_FILE_PATH = '/etc/ssl/certs/ca-certificates.crt:'\
- '/etc/pki/tls/certs/ca-bundle.crt:'\
- '/etc/ssl/ca-bundle.pem:'\
- '/etc/ssl/cert.pem'
-
- OK_RESPONSE_CODES = (
- httplib.OK,
- httplib.CREATED,
- httplib.ACCEPTED,
- httplib.NO_CONTENT,
- )
-
- REDIRECT_RESPONSE_CODES = (
- httplib.MOVED_PERMANENTLY,
- httplib.FOUND,
- httplib.SEE_OTHER,
- httplib.USE_PROXY,
- httplib.TEMPORARY_REDIRECT,
- )
-
- def __init__(self, host=None, port=None, use_ssl=False, auth_tok=None,
- creds=None, doc_root=None, key_file=None,
- cert_file=None, ca_file=None, insecure=False,
- configure_via_auth=True, service_type=None):
- """
- Creates a new client to some service.
-
- :param host: The host where service resides
- :param port: The port where service resides
- :param use_ssl: Should we use HTTPS?
- :param auth_tok: The auth token to pass to the server
- :param creds: The credentials to pass to the auth plugin
- :param doc_root: Prefix for all URLs we request from host
- :param key_file: Optional PEM-formatted file that contains the private
- key.
- If use_ssl is True, and this param is None (the
- default), then an environ variable
- HEAT_CLIENT_KEY_FILE is looked for. If no such
- environ variable is found, ClientConnectionError
- will be raised.
- :param cert_file: Optional PEM-formatted certificate chain file.
- If use_ssl is True, and this param is None (the
- default), then an environ variable
- HEAT_CLIENT_CERT_FILE is looked for. If no such
- environ variable is found, ClientConnectionError
- will be raised.
- :param ca_file: Optional CA cert file to use in SSL connections
- If use_ssl is True, and this param is None (the
- default), then an environ variable
- HEAT_CLIENT_CA_FILE is looked for.
- :param insecure: Optional. If set then the server's certificate
- will not be verified.
- """
- self.host = host
- self.port = port or self.DEFAULT_PORT
- self.use_ssl = use_ssl
- self.auth_tok = auth_tok
- self.creds = creds or {}
- self.connection = None
- self.configure_via_auth = configure_via_auth
- self.service_type = service_type
- # doc_root can be a nullstring, which is valid, and why we
- # cannot simply do doc_root or self.DEFAULT_DOC_ROOT below.
- self.doc_root = (doc_root if doc_root is not None
- else self.DEFAULT_DOC_ROOT)
- self.auth_plugin = self.make_auth_plugin(self.creds)
-
- self.key_file = key_file
- self.cert_file = cert_file
- self.ca_file = ca_file
- self.insecure = insecure
- self.connect_kwargs = self.get_connect_kwargs()
-
- def get_connect_kwargs(self):
- connect_kwargs = {}
- if self.use_ssl:
- if self.key_file is None:
- self.key_file = os.environ.get('HEAT_CLIENT_KEY_FILE')
- if self.cert_file is None:
- self.cert_file = os.environ.get('HEAT_CLIENT_CERT_FILE')
- if self.ca_file is None:
- self.ca_file = os.environ.get('HEAT_CLIENT_CA_FILE')
-
- # Check that key_file/cert_file are either both set or both unset
- if self.cert_file is not None and self.key_file is None:
- msg = _("You have selected to use SSL in connecting, "
- "and you have supplied a cert, "
- "however you have failed to supply either a "
- "key_file parameter or set the "
- "HEAT_CLIENT_KEY_FILE environ variable")
- raise exception.ClientConnectionError(msg)
-
- if self.key_file is not None and self.cert_file is None:
- msg = _("You have selected to use SSL in connecting, "
- "and you have supplied a key, "
- "however you have failed to supply either a "
- "cert_file parameter or set the "
- "HEAT_CLIENT_CERT_FILE environ variable")
- raise exception.ClientConnectionError(msg)
-
- if (self.key_file is not None and
- not os.path.exists(self.key_file)):
- msg = _("The key file you specified %s does not "
- "exist") % self.key_file
- raise exception.ClientConnectionError(msg)
- connect_kwargs['key_file'] = self.key_file
-
- if (self.cert_file is not None and
- not os.path.exists(self.cert_file)):
- msg = _("The cert file you specified %s does not "
- "exist") % self.cert_file
- raise exception.ClientConnectionError(msg)
- connect_kwargs['cert_file'] = self.cert_file
-
- if (self.ca_file is not None and
- not os.path.exists(self.ca_file)):
- msg = _("The CA file you specified %s does not "
- "exist") % self.ca_file
- raise exception.ClientConnectionError(msg)
-
- if self.ca_file is None:
- for ca in self.DEFAULT_CA_FILE_PATH.split(":"):
- if os.path.exists(ca):
- self.ca_file = ca
- break
-
- connect_kwargs['ca_file'] = self.ca_file
- connect_kwargs['insecure'] = self.insecure
-
- return connect_kwargs
-
- def set_auth_token(self, auth_tok):
- """
- Updates the authentication token for this client connection.
- """
- # FIXME(sirp): Nova image/heat.py currently calls this. Since this
- # method isn't really doing anything useful[1], we should go ahead and
- # rip it out, first in Nova, then here. Steps:
- #
- # 1. Change auth_tok in heat to auth_token
- # 2. Change image/heat.py in Nova to use client.auth_token
- # 3. Remove this method
- #
- # [1] http://mail.python.org/pipermail/tutor/2003-October/025932.html
- self.auth_tok = auth_tok
-
- def configure_from_url(self, url):
- """
- Setups the connection based on the given url.
-
- The form is:
-
- <http|https>://<host>:port/doc_root
- """
- parsed = urlparse.urlparse(url)
- self.use_ssl = parsed.scheme == 'https'
- if self.host is None:
- self.host = parsed.hostname
- self.port = parsed.port or 80
- self.doc_root = parsed.path
-
- # ensure connection kwargs are re-evaluated after the service catalog
- # publicURL is parsed for potential SSL usage
- self.connect_kwargs = self.get_connect_kwargs()
-
- def make_auth_plugin(self, creds):
- """
- Returns an instantiated authentication plugin.
- """
- strategy = creds.get('strategy', 'noauth')
- plugin = auth.get_plugin_from_strategy(strategy,
- creds, self.service_type)
- return plugin
-
- def get_connection_type(self):
- """
- Returns the proper connection type
- """
- if self.use_ssl:
- return HTTPSClientAuthConnection
- else:
- return httplib.HTTPConnection
-
- def _authenticate(self, force_reauth=False):
- """
- Use the authentication plugin to authenticate and set the auth token.
-
- :param force_reauth: For re-authentication to bypass cache.
- """
- auth_plugin = self.auth_plugin
-
- if not auth_plugin.is_authenticated or force_reauth:
- auth_plugin.authenticate()
-
- self.auth_tok = auth_plugin.auth_token
-
- management_url = auth_plugin.management_url
- if management_url and self.configure_via_auth:
- self.configure_from_url(management_url)
-
- @handle_unauthorized
- def do_request(self, method, action, body=None, headers=None,
- params=None):
- """
- Make a request, returning an HTTP response object.
-
- :param method: HTTP verb (GET, POST, PUT, etc.)
- :param action: Requested path to append to self.doc_root
- :param body: Data to send in the body of the request
- :param headers: Headers to send with the request
- :param params: Key/value pairs to use in query string
- :returns: HTTP response object
- """
- if not self.auth_tok:
- self._authenticate()
-
- url = self._construct_url(action, params)
- return self._do_request(method=method, url=url, body=body,
- headers=headers)
-
- def _construct_url(self, action, params=None):
- """
- Create a URL object we can use to pass to _do_request().
- """
- path = '/'.join([self.doc_root or '', action.lstrip('/')])
- scheme = "https" if self.use_ssl else "http"
- netloc = "%s:%d" % (self.host, self.port)
-
- if isinstance(params, dict):
- for (key, value) in params.items():
- if value is None:
- del params[key]
- query = urllib.urlencode(params)
- else:
- query = None
-
- return urlparse.ParseResult(scheme, netloc, path, '', query, '')
-
- @handle_redirects
- def _do_request(self, method, url, body, headers):
- """
- Connects to the server and issues a request. Handles converting
- any returned HTTP error status codes to OpenStack/heat exceptions
- and closing the server connection. Returns the result data, or
- raises an appropriate exception.
-
- :param method: HTTP method ("GET", "POST", "PUT", etc...)
- :param url: urlparse.ParsedResult object with URL information
- :param body: data to send (as string, filelike or iterable),
- or None (default)
- :param headers: mapping of key/value pairs to add as headers
-
- :note
-
- If the body param has a read attribute, and method is either
- POST or PUT, this method will automatically conduct a chunked-transfer
- encoding and use the body as a file object or iterable, transferring
- chunks of data using the connection's send() method. This allows large
- objects to be transferred efficiently without buffering the entire
- body in memory.
- """
- if url.query:
- path = url.path + "?" + url.query
- else:
- path = url.path
-
- try:
- connection_type = self.get_connection_type()
- headers = headers or {}
-
- if 'x-auth-token' not in headers and self.auth_tok:
- headers['x-auth-token'] = self.auth_tok
-
- c = connection_type(url.hostname, url.port, **self.connect_kwargs)
-
- def _pushing(method):
- return method.lower() in ('post', 'put')
-
- def _simple(body):
- return body is None or isinstance(body, basestring)
-
- def _filelike(body):
- return hasattr(body, 'read')
-
- def _sendbody(connection, iter):
- connection.endheaders()
- for sent in iter:
- # iterator has done the heavy lifting
- pass
-
- def _chunkbody(connection, iter):
- connection.putheader('Transfer-Encoding', 'chunked')
- connection.endheaders()
- for chunk in iter:
- connection.send('%x\r\n%s\r\n' % (len(chunk), chunk))
- connection.send('0\r\n\r\n')
-
- # Do a simple request or a chunked request, depending
- # on whether the body param is file-like or iterable and
- # the method is PUT or POST
- #
- if not _pushing(method) or _simple(body):
- # Simple request...
- c.request(method, path, body, headers)
- elif _filelike(body) or self._iterable(body):
- c.putrequest(method, path)
-
- for header, value in headers.items():
- c.putheader(header, value)
-
- iter = self.image_iterator(c, headers, body)
-
- _chunkbody(c, iter)
- else:
- raise TypeError('Unsupported image type: %s' % body.__class__)
-
- res = c.getresponse()
-
- def _retry(res):
- return res.getheader('Retry-After')
-
- status_code = self.get_status_code(res)
- if status_code in self.OK_RESPONSE_CODES:
- return res
- elif status_code in self.REDIRECT_RESPONSE_CODES:
- raise exception.RedirectException(res.getheader('Location'))
- elif status_code == httplib.UNAUTHORIZED:
- raise exception.NotAuthorized()
- elif status_code == httplib.FORBIDDEN:
- raise exception.NotAuthorized()
- elif status_code == httplib.NOT_FOUND:
- raise exception.NotFound(res.read())
- elif status_code == httplib.CONFLICT:
- raise exception.Duplicate(res.read())
- elif status_code == httplib.BAD_REQUEST:
- raise exception.Invalid(reason=res.read())
- elif status_code == httplib.MULTIPLE_CHOICES:
- raise exception.MultipleChoices(body=res.read())
- elif status_code == httplib.REQUEST_ENTITY_TOO_LARGE:
- raise exception.LimitExceeded(retry=_retry(res),
- body=res.read())
- elif status_code == httplib.INTERNAL_SERVER_ERROR:
- raise Exception("Internal Server error: %s" % res.read())
- elif status_code == httplib.SERVICE_UNAVAILABLE:
- raise exception.ServiceUnavailable(retry=_retry(res))
- elif status_code == httplib.REQUEST_URI_TOO_LONG:
- raise exception.RequestUriTooLong(body=res.read())
- else:
- raise Exception("Unknown error occurred! %s" % res.read())
-
- except (socket.error, IOError) as e:
- raise exception.ClientConnectionError(e)
-
- def _iterable(self, body):
- return isinstance(body, collections.Iterable)
-
- def image_iterator(self, connection, headers, body):
- if self._iterable(body):
- return utils.chunkreadable(body)
- else:
- return ImageBodyIterator(body)
-
- def get_status_code(self, response):
- """
- Returns the integer status code from the response, which
- can be either a Webob.Response (used in testing) or httplib.Response
- """
- if hasattr(response, 'status_int'):
- return response.status_int
- else:
- return response.status
-
- def _extract_params(self, actual_params, allowed_params):
- """
- Extract a subset of keys from a dictionary. The filters key
- will also be extracted, and each of its values will be returned
- as an individual param.
-
- :param actual_params: dict of keys to filter
- :param allowed_params: list of keys that 'actual_params' will be
- reduced to
- :retval subset of 'params' dict
- """
- result = {}
-
- for param in actual_params:
- if param in allowed_params:
- result[param] = actual_params[param]
- elif 'Parameters.member.' in param:
- result[param] = actual_params[param]
-
- return result
+++ /dev/null
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# All Rights Reserved.
-#
-# 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.
-
-"""
-System-level utilities and helper functions.
-"""
-
-from heat.openstack.common import log as logging
-
-
-LOG = logging.getLogger(__name__)
-
-
-def chunkreadable(iter, chunk_size=65536):
- """
- Wrap a readable iterator with a reader yielding chunks of
- a preferred size, otherwise leave iterator unchanged.
-
- :param iter: an iter which may also be readable
- :param chunk_size: maximum size of chunk
- """
- return chunkiter(iter, chunk_size) if hasattr(iter, 'read') else iter
-
-
-def chunkiter(fp, chunk_size=65536):
- """
- Return an iterator to a file-like obj which yields fixed size chunks
-
- :param fp: a file-like object
- :param chunk_size: maximum size of chunk
- """
- while True:
- chunk = fp.read(chunk_size)
- if chunk:
- yield chunk
- else:
- break
+++ /dev/null
-# 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.
-
-
-import testtools
-import heat
-import os
-import subprocess
-
-basepath = os.path.join(heat.__path__[0], os.path.pardir)
-
-
-class CliTest(testtools.TestCase):
-
- def test_heat_cfn(self):
- self.bin_run('heat-cfn')
-
- def test_heat_boto(self):
- self.bin_run('heat-boto')
-
- def test_heat_watch(self):
- self.bin_run('heat-watch')
-
- def bin_run(self, bin):
- fullpath = basepath + '/bin/' + bin
-
- proc = subprocess.Popen(fullpath,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- stdout, stderr = proc.communicate()
-
- if proc.returncode:
- print('Error executing %s:\n %s %s ' % (bin, stdout, stderr))
- raise subprocess.CalledProcessError(proc.returncode, bin)
pbr>=0.5.21,<1.0
pycrypto>=2.6
-boto>=2.4.0
eventlet>=0.13.0
greenlet>=0.3.2
httplib2
bin/heat-api
bin/heat-api-cfn
bin/heat-api-cloudwatch
- bin/heat-boto
- bin/heat-cfn
bin/heat-db-setup
bin/heat-engine
bin/heat-keystone-setup
bin/heat-manage
- bin/heat-watch
[global]
setup-hooks =
sudo rm -f $BIN_PATH/heat-api
sudo rm -f $BIN_PATH/heat-api-cfn
sudo rm -f $BIN_PATH/heat-engine
- sudo rm -f $BIN_PATH/heat-cfn
echo 1>&2
fi