commit
3b9c41fb6c
30 changed files with 5288 additions and 0 deletions
@ -0,0 +1,6 @@
|
||||
*.pyc |
||||
*.swp |
||||
*.log |
||||
build |
||||
dist |
||||
heat/vcsversion.py |
@ -0,0 +1,176 @@
|
||||
|
||||
Apache License |
||||
Version 2.0, January 2004 |
||||
http://www.apache.org/licenses/ |
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
||||
1. Definitions. |
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, |
||||
and distribution as defined by Sections 1 through 9 of this document. |
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by |
||||
the copyright owner that is granting the License. |
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all |
||||
other entities that control, are controlled by, or are under common |
||||
control with that entity. For the purposes of this definition, |
||||
"control" means (i) the power, direct or indirect, to cause the |
||||
direction or management of such entity, whether by contract or |
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity |
||||
exercising permissions granted by this License. |
||||
|
||||
"Source" form shall mean the preferred form for making modifications, |
||||
including but not limited to software source code, documentation |
||||
source, and configuration files. |
||||
|
||||
"Object" form shall mean any form resulting from mechanical |
||||
transformation or translation of a Source form, including but |
||||
not limited to compiled object code, generated documentation, |
||||
and conversions to other media types. |
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or |
||||
Object form, made available under the License, as indicated by a |
||||
copyright notice that is included in or attached to the work |
||||
(an example is provided in the Appendix below). |
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object |
||||
form, that is based on (or derived from) the Work and for which the |
||||
editorial revisions, annotations, elaborations, or other modifications |
||||
represent, as a whole, an original work of authorship. For the purposes |
||||
of this License, Derivative Works shall not include works that remain |
||||
separable from, or merely link (or bind by name) to the interfaces of, |
||||
the Work and Derivative Works thereof. |
||||
|
||||
"Contribution" shall mean any work of authorship, including |
||||
the original version of the Work and any modifications or additions |
||||
to that Work or Derivative Works thereof, that is intentionally |
||||
submitted to Licensor for inclusion in the Work by the copyright owner |
||||
or by an individual or Legal Entity authorized to submit on behalf of |
||||
the copyright owner. For the purposes of this definition, "submitted" |
||||
means any form of electronic, verbal, or written communication sent |
||||
to the Licensor or its representatives, including but not limited to |
||||
communication on electronic mailing lists, source code control systems, |
||||
and issue tracking systems that are managed by, or on behalf of, the |
||||
Licensor for the purpose of discussing and improving the Work, but |
||||
excluding communication that is conspicuously marked or otherwise |
||||
designated in writing by the copyright owner as "Not a Contribution." |
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||
on behalf of whom a Contribution has been received by Licensor and |
||||
subsequently incorporated within the Work. |
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
copyright license to reproduce, prepare Derivative Works of, |
||||
publicly display, publicly perform, sublicense, and distribute the |
||||
Work and such Derivative Works in Source or Object form. |
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
(except as stated in this section) patent license to make, have made, |
||||
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||
where such license applies only to those patent claims licensable |
||||
by such Contributor that are necessarily infringed by their |
||||
Contribution(s) alone or by combination of their Contribution(s) |
||||
with the Work to which such Contribution(s) was submitted. If You |
||||
institute patent litigation against any entity (including a |
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||
or a Contribution incorporated within the Work constitutes direct |
||||
or contributory patent infringement, then any patent licenses |
||||
granted to You under this License for that Work shall terminate |
||||
as of the date such litigation is filed. |
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the |
||||
Work or Derivative Works thereof in any medium, with or without |
||||
modifications, and in Source or Object form, provided that You |
||||
meet the following conditions: |
||||
|
||||
(a) You must give any other recipients of the Work or |
||||
Derivative Works a copy of this License; and |
||||
|
||||
(b) You must cause any modified files to carry prominent notices |
||||
stating that You changed the files; and |
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works |
||||
that You distribute, all copyright, patent, trademark, and |
||||
attribution notices from the Source form of the Work, |
||||
excluding those notices that do not pertain to any part of |
||||
the Derivative Works; and |
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its |
||||
distribution, then any Derivative Works that You distribute must |
||||
include a readable copy of the attribution notices contained |
||||
within such NOTICE file, excluding those notices that do not |
||||
pertain to any part of the Derivative Works, in at least one |
||||
of the following places: within a NOTICE text file distributed |
||||
as part of the Derivative Works; within the Source form or |
||||
documentation, if provided along with the Derivative Works; or, |
||||
within a display generated by the Derivative Works, if and |
||||
wherever such third-party notices normally appear. The contents |
||||
of the NOTICE file are for informational purposes only and |
||||
do not modify the License. You may add Your own attribution |
||||
notices within Derivative Works that You distribute, alongside |
||||
or as an addendum to the NOTICE text from the Work, provided |
||||
that such additional attribution notices cannot be construed |
||||
as modifying the License. |
||||
|
||||
You may add Your own copyright statement to Your modifications and |
||||
may provide additional or different license terms and conditions |
||||
for use, reproduction, or distribution of Your modifications, or |
||||
for any such Derivative Works as a whole, provided Your use, |
||||
reproduction, and distribution of the Work otherwise complies with |
||||
the conditions stated in this License. |
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||
any Contribution intentionally submitted for inclusion in the Work |
||||
by You to the Licensor shall be under the terms and conditions of |
||||
this License, without any additional terms or conditions. |
||||
Notwithstanding the above, nothing herein shall supersede or modify |
||||
the terms of any separate license agreement you may have executed |
||||
with Licensor regarding such Contributions. |
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade |
||||
names, trademarks, service marks, or product names of the Licensor, |
||||
except as required for reasonable and customary use in describing the |
||||
origin of the Work and reproducing the content of the NOTICE file. |
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or |
||||
agreed to in writing, Licensor provides the Work (and each |
||||
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
implied, including, without limitation, any warranties or conditions |
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||
appropriateness of using or redistributing the Work and assume any |
||||
risks associated with Your exercise of permissions under this License. |
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, |
||||
whether in tort (including negligence), contract, or otherwise, |
||||
unless required by applicable law (such as deliberate and grossly |
||||
negligent acts) or agreed to in writing, shall any Contributor be |
||||
liable to You for damages, including any direct, indirect, special, |
||||
incidental, or consequential damages of any character arising as a |
||||
result of this License or out of the use or inability to use the |
||||
Work (including but not limited to damages for loss of goodwill, |
||||
work stoppage, computer failure or malfunction, or any and all |
||||
other commercial damages or losses), even if such Contributor |
||||
has been advised of the possibility of such damages. |
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing |
||||
the Work or Derivative Works thereof, You may choose to offer, |
||||
and charge a fee for, acceptance of support, warranty, indemnity, |
||||
or other liability obligations and/or rights consistent with this |
||||
License. However, in accepting such obligations, You may act only |
||||
on Your own behalf and on Your sole responsibility, not on behalf |
||||
of any other Contributor, and only if You agree to indemnify, |
||||
defend, and hold each Contributor harmless for any liability |
||||
incurred by, or claims asserted against, such Contributor by reason |
||||
of your accepting any such warranty or additional liability. |
||||
|
@ -0,0 +1,377 @@
|
||||
#!/usr/bin/env python |
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright 2011 OpenStack, LLC |
||||
# 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. |
||||
|
||||
""" |
||||
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. |
||||
""" |
||||
|
||||
import functools |
||||
import gettext |
||||
import optparse |
||||
import os |
||||
import sys |
||||
import time |
||||
import json |
||||
|
||||
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) |
||||
|
||||
gettext.install('heat', unicode=1) |
||||
|
||||
from heat import client as heat_client |
||||
from heat.common import exception |
||||
from heat import version |
||||
|
||||
|
||||
SUCCESS = 0 |
||||
FAILURE = 1 |
||||
|
||||
DEFAULT_PORT = 8000 |
||||
|
||||
def catch_error(action): |
||||
"""Decorator to provide sensible default error handling for 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: |
||||
print "Not authorized to make this request. Check "\ |
||||
"your credentials (OS_AUTH_USER, OS_AUTH_KEY, ...)." |
||||
return FAILURE |
||||
except exception.ClientConfigurationError: |
||||
raise |
||||
except Exception, e: |
||||
options = arguments[0] |
||||
if options.debug: |
||||
raise |
||||
print "Failed to %s. Got error:" % action |
||||
pieces = unicode(e).split('\n') |
||||
for piece in pieces: |
||||
print piece |
||||
return FAILURE |
||||
|
||||
return wrapper |
||||
return wrap |
||||
|
||||
@catch_error('validate') |
||||
def template_validate(options, arguments): |
||||
''' |
||||
''' |
||||
pass |
||||
|
||||
@catch_error('gettemplate') |
||||
def get_template(options, arguments): |
||||
''' |
||||
''' |
||||
pass |
||||
|
||||
@catch_error('create') |
||||
def stack_create(options, arguments): |
||||
''' |
||||
''' |
||||
|
||||
parameters = {} |
||||
try: |
||||
parameters['StackName'] = arguments.pop(0) |
||||
except IndexError: |
||||
print "Please specify the stack name you wish to create " |
||||
print "as the first argument" |
||||
return FAILURE |
||||
|
||||
if options.parameters: |
||||
for p in options.parameters.split(';'): |
||||
(n, v) = p.split('=') |
||||
parameters[n] = v |
||||
|
||||
if options.template_file: |
||||
parameters['TemplateBody'] = open(options.template_file).read() |
||||
elif options.template_url: |
||||
parameters['TemplateUrl'] = options.template_url |
||||
else: |
||||
print 'Please specify a template file or url' |
||||
return FAILURE |
||||
|
||||
c = get_client(options) |
||||
result = c.create_stack(**parameters) |
||||
print json.dumps(result, indent=2) |
||||
|
||||
@catch_error('update') |
||||
def stack_update(options, arguments): |
||||
''' |
||||
''' |
||||
parameters = {} |
||||
try: |
||||
parameters['StackName'] = arguments.pop(0) |
||||
except IndexError: |
||||
print "Please specify the stack name you wish to update " |
||||
print "as the first argument" |
||||
return FAILURE |
||||
|
||||
c = get_client(options) |
||||
result = c.update_stack(parameters) |
||||
print json.dumps(result, indent=2) |
||||
|
||||
@catch_error('delete') |
||||
def stack_delete(options, arguments): |
||||
''' |
||||
''' |
||||
parameters = {} |
||||
try: |
||||
parameters['StackName'] = arguments.pop(0) |
||||
except IndexError: |
||||
print "Please specify the stack name you wish to delete " |
||||
print "as the first argument" |
||||
return FAILURE |
||||
|
||||
c = get_client(options) |
||||
result = c.delete_stack(parameters) |
||||
print json.dumps(result, indent=2) |
||||
|
||||
@catch_error('describe') |
||||
def stack_describe(options, arguments): |
||||
''' |
||||
''' |
||||
parameters = {} |
||||
try: |
||||
parameters['StackName'] = arguments.pop(0) |
||||
except IndexError: |
||||
print "Describing all stacks" |
||||
|
||||
c = get_client(options) |
||||
result = c.describe_stacks(parameters) |
||||
print json.dumps(result, indent=2) |
||||
|
||||
@catch_error('list') |
||||
def stack_list(options, arguments): |
||||
''' |
||||
''' |
||||
c = get_client(options) |
||||
result = c.list_stacks() |
||||
print json.dumps(result, indent=2) |
||||
|
||||
def get_client(options): |
||||
""" |
||||
Returns a new client object to a heat server |
||||
specified by the --host and --port options |
||||
supplied to the CLI |
||||
""" |
||||
return heat_client.get_client(host=options.host, |
||||
port=options.port, |
||||
username=options.username, |
||||
password=options.password, |
||||
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('-H', '--host', metavar="ADDRESS", default="0.0.0.0", |
||||
help="Address of heat API host. " |
||||
"Default: %default") |
||||
parser.add_option('-p', '--port', dest="port", metavar="PORT", |
||||
type=int, default=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(DEFAULT_PORT) + |
||||
"/v1 Default: No<F3>ne") |
||||
parser.add_option('-k', '--insecure', dest="insecure", |
||||
default=False, action="store_true", |
||||
help="Explicitly allow heat to perform \"insecure\" " |
||||
"SSL (https) requests. The server's certificate will " |
||||
"not be verified against any certificate authorities. " |
||||
"This option should be used with caution.") |
||||
parser.add_option('-A', '--auth_token', dest="auth_token", |
||||
metavar="TOKEN", default=None, |
||||
help="Authentication token to use to identify the " |
||||
"client to the heat server") |
||||
parser.add_option('-I', '--username', dest="username", |
||||
metavar="USER", default=None, |
||||
help="User name used to acquire an authentication token") |
||||
parser.add_option('-K', '--password', dest="password", |
||||
metavar="PASSWORD", default=None, |
||||
help="Password used to acquire an authentication token") |
||||
parser.add_option('-R', '--region', dest="region", |
||||
metavar="REGION", default=None, |
||||
help="Region name. When using keystone authentication " |
||||
"version 2.0 or later this identifies the region " |
||||
"name to use when selecting the service endpoint. A " |
||||
"region name must be provided if more than one " |
||||
"region endpoint is available") |
||||
parser.add_option('-N', '--auth_url', dest="auth_url", |
||||
metavar="AUTH_URL", default=None, |
||||
help="Authentication URL") |
||||
parser.add_option('-S', '--auth_strategy', dest="auth_strategy", |
||||
metavar="STRATEGY", default=None, |
||||
help="Authentication strategy (keystone or noauth)") |
||||
|
||||
parser.add_option('-u', '--template-url', metavar="template_url", default=None, |
||||
help="URL of template. Default: None") |
||||
parser.add_option('-t', '--template-file', metavar="template_file", default=None, |
||||
help="Path to the template. Default: None") |
||||
|
||||
parser.add_option('-P', '--parameters', metavar="parameters", default=None, |
||||
help="Parameter values used to create the stack.") |
||||
|
||||
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) |
||||
if options.url is not None: |
||||
u = urlparse(options.url) |
||||
options.port = u.port |
||||
options.host = u.hostname |
||||
|
||||
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) |
||||
|
||||
return (options, command, args) |
||||
|
||||
|
||||
def print_help(options, args): |
||||
""" |
||||
Print help specific to a command |
||||
""" |
||||
if len(args) != 1: |
||||
sys.exit("Please specify a command") |
||||
|
||||
parser = options.__parser |
||||
command_name = args.pop() |
||||
command = lookup_command(parser, command_name) |
||||
|
||||
print command.__doc__ % {'prog': os.path.basename(sys.argv[0])} |
||||
|
||||
|
||||
def lookup_command(parser, command_name): |
||||
base_commands = {'help': print_help} |
||||
|
||||
image_commands = { |
||||
'create': stack_create, |
||||
'update': stack_update, |
||||
'delete': stack_delete, |
||||
'list': stack_list, |
||||
'validate': template_validate, |
||||
'gettemplate': get_template, |
||||
'describe': stack_describe} |
||||
|
||||
commands = {} |
||||
for command_set in (base_commands, image_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 |
||||
|
||||
validate Validate a template |
||||
|
||||
""" |
||||
|
||||
oparser = optparse.OptionParser(version='%%prog %s' |
||||
% version.version_string(), |
||||
usage=usage.strip()) |
||||
create_options(oparser) |
||||
(opts, cmd, args) = parse_options(oparser, sys.argv[1:]) |
||||
|
||||
try: |
||||
start_time = time.time() |
||||
result = cmd(opts, args) |
||||
end_time = time.time() |
||||
if opts.verbose: |
||||
print "Completed in %-0.4f sec." % (end_time - start_time) |
||||
sys.exit(result) |
||||
except (RuntimeError, |
||||
NotImplementedError, |
||||
exception.ClientConfigurationError), ex: |
||||
oparser.print_usage() |
||||
print >> sys.stderr, "ERROR: ", ex |
||||
sys.exit(1) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
main() |
@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python |
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright 2010 United States Government as represented by the |
||||
# Administrator of the National Aeronautics and Space Administration. |
||||
# Copyright 2011 OpenStack LLC. |
||||
# 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. |
||||
|
||||
""" |
||||
Heat API Server |
||||
""" |
||||
|
||||
import gettext |
||||
import os |
||||
import sys |
||||
|
||||
# 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) |
||||
|
||||
gettext.install('heat', unicode=1) |
||||
|
||||
from heat.common import config |
||||
from heat.common import wsgi |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
try: |
||||
conf = config.HeatConfigOpts() |
||||
conf() |
||||
|
||||
app = config.load_paste_app(conf) |
||||
|
||||
server = wsgi.Server() |
||||
server.start(app, conf, default_port=9292) |
||||
server.wait() |
||||
except RuntimeError, e: |
||||
sys.exit("ERROR: %s" % e) |
@ -0,0 +1,25 @@
|
||||
[DEFAULT] |
||||
# Show more verbose log output (sets INFO log level output) |
||||
verbose = True |
||||
|
||||
# Show debugging output in logs (sets DEBUG log level output) |
||||
debug = True |
||||
|
||||
# Address to bind the server to |
||||
bind_host = 0.0.0.0 |
||||
|
||||
# Port the bind the server to |
||||
bind_port = 8000 |
||||
|
||||
# Log to this file. Make sure the user running heat-api has |
||||
# permissions to write to this file! |
||||
log_file = /var/log/heat/api.log |
||||
|
||||
# ================= Syslog Options ============================ |
||||
|
||||
# Send logs to syslog (/dev/log) instead of to file specified |
||||
# by `log_file` |
||||
use_syslog = False |
||||
|
||||
# Facility to use. If unset defaults to LOG_USER. |
||||
# syslog_log_facility = LOG_LOCAL0 |
@ -0,0 +1,80 @@
|
||||
# Default minimal pipeline |
||||
[pipeline:heat-api] |
||||
pipeline = versionnegotiation context apiv1app |
||||
|
||||
# Use the following pipeline for keystone auth |
||||
# i.e. in heat-api.conf: |
||||
# [paste_deploy] |
||||
# flavor = keystone |
||||
# |
||||
[pipeline:heat-api-keystone] |
||||
pipeline = versionnegotiation authtoken auth-context apiv1app |
||||
|
||||
# Use the following pipeline to enable transparent caching of image files |
||||
# i.e. in heat-api.conf: |
||||
# [paste_deploy] |
||||
# flavor = caching |
||||
# |
||||
[pipeline:heat-api-caching] |
||||
pipeline = versionnegotiation context cache apiv1app |
||||
|
||||
# Use the following pipeline for keystone auth with caching |
||||
# i.e. in heat-api.conf: |
||||
# [paste_deploy] |
||||
# flavor = keystone+caching |
||||
# |
||||
[pipeline:heat-api-keystone+caching] |
||||
pipeline = versionnegotiation authtoken auth-context cache apiv1app |
||||
|
||||
# Use the following pipeline to enable the Image Cache Management API |
||||
# i.e. in heat-api.conf: |
||||
# [paste_deploy] |
||||
# flavor = cachemanagement |
||||
# |
||||
[pipeline:heat-api-cachemanagement] |
||||
pipeline = versionnegotiation context cache cachemanage apiv1app |
||||
|
||||
# Use the following pipeline for keystone auth with cache management |
||||
# i.e. in heat-api.conf: |
||||
# [paste_deploy] |
||||
# flavor = keystone+cachemanagement |
||||
# |
||||
[pipeline:heat-api-keystone+cachemanagement] |
||||
pipeline = versionnegotiation authtoken auth-context cache cachemanage apiv1app |
||||
|
||||
[app:apiv1app] |
||||
paste.app_factory = heat.common.wsgi:app_factory |
||||
heat.app_factory = heat.api.v1.router:API |
||||
|
||||
[filter:versionnegotiation] |
||||
paste.filter_factory = heat.common.wsgi:filter_factory |
||||
heat.filter_factory = heat.api.middleware.version_negotiation:VersionNegotiationFilter |
||||
|
||||
[filter:cache] |
||||
paste.filter_factory = heat.common.wsgi:filter_factory |
||||
heat.filter_factory = heat.api.middleware.cache:CacheFilter |
||||
|
||||
[filter:cachemanage] |
||||
paste.filter_factory = heat.common.wsgi:filter_factory |
||||
heat.filter_factory = heat.api.middleware.cache_manage:CacheManageFilter |
||||
|
||||
[filter:context] |
||||
paste.filter_factory = heat.common.wsgi:filter_factory |
||||
heat.filter_factory = heat.common.context:ContextMiddleware |
||||
|
||||
[filter:authtoken] |
||||
paste.filter_factory = keystone.middleware.auth_token:filter_factory |
||||
service_protocol = http |
||||
service_host = 127.0.0.1 |
||||
service_port = 5000 |
||||
auth_host = 127.0.0.1 |
||||
auth_port = 35357 |
||||
auth_protocol = http |
||||
auth_uri = http://127.0.0.1:5000/ |
||||
admin_tenant_name = %SERVICE_TENANT_NAME% |
||||
admin_user = %SERVICE_USER% |
||||
admin_password = %SERVICE_PASSWORD% |
||||
|
||||
[filter:auth-context] |
||||
paste.filter_factory = heat.common.wsgi:filter_factory |
||||
heat.filter_factory = keystone.middleware.heat_auth_token:KeystoneContextMiddleware |
@ -0,0 +1,20 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright 2010-2011 OpenStack LLC. |
||||
# 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. |
||||
|
||||
import gettext |
||||
|
||||
gettext.install('heat', unicode=1) |
@ -0,0 +1,16 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright 2011 OpenStack LLC. |
||||
# 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. |
@ -0,0 +1,16 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright 2011 OpenStack LLC. |
||||
# 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. |
@ -0,0 +1,64 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright 2011 OpenStack LLC. |
||||
# 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. |
||||
|
||||
""" |
||||
Middleware that attaches a context to the WSGI request |
||||
""" |
||||
|
||||
from heat.common import utils |
||||
from heat.common import wsgi |
||||
from heat.common import context |
||||
|
||||
|
||||
class ContextMiddleware(wsgi.Middleware): |
||||
def __init__(self, app, options): |
||||
self.options = options |
||||
super(ContextMiddleware, self).__init__(app) |
||||
|
||||
def make_context(self, *args, **kwargs): |
||||
""" |
||||
Create a context with the given arguments. |
||||
""" |
||||
|
||||
# Determine the context class to use |
||||
ctxcls = context.RequestContext |
||||
if 'context_class' in self.options: |
||||
ctxcls = utils.import_class(self.options['context_class']) |
||||
|
||||
return ctxcls(*args, **kwargs) |
||||
|
||||
def process_request(self, req): |
||||
""" |
||||
Extract any authentication information in the request and |
||||
construct an appropriate context from it. |
||||
""" |
||||
# Use the default empty context, with admin turned on for |
||||
# backwards compatibility |
||||
req.context = self.make_context(is_admin=True) |
||||
|
||||
|
||||
def filter_factory(global_conf, **local_conf): |
||||
""" |
||||
Factory method for paste.deploy |
||||
""" |
||||
conf = global_conf.copy() |
||||
conf.update(local_conf) |
||||
|
||||
def filter(app): |
||||
return ContextMiddleware(app, conf) |
||||
|
||||
return filter |
@ -0,0 +1,123 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright 2011 OpenStack LLC. |
||||
# 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. |
||||
|
||||
""" |
||||
A filter middleware that inspects the requested URI for a version string |
||||
and/or Accept headers and attempts to negotiate an API controller to |
||||
return |
||||
""" |
||||
|
||||
import logging |
||||
import re |
||||
|
||||
import routes |
||||
|
||||
from heat.api import v1 |
||||
from heat.api import versions |
||||
from heat.common import wsgi |
||||
|
||||
logger = logging.getLogger('heat.api.middleware.version_negotiation') |
||||
|
||||
|
||||
class VersionNegotiationFilter(wsgi.Middleware): |
||||
|
||||
def __init__(self, app, conf, **local_conf): |
||||
self.versions_app = versions.Controller(conf) |
||||
self.version_uri_regex = re.compile(r"^v(\d+)\.?(\d+)?") |
||||
self.conf = conf |
||||
super(VersionNegotiationFilter, self).__init__(app) |
||||
|
||||
def process_request(self, req): |
||||
""" |
||||
If there is a version identifier in the URI, simply |
||||
return the correct API controller, otherwise, if we |
||||
find an Accept: header, process it |
||||
""" |
||||
# See if a version identifier is in the URI passed to |
||||
# us already. If so, simply return the right version |
||||
# API controller |
||||
msg = _("Processing request: %(method)s %(path)s Accept: " |
||||
"%(accept)s") % ({'method': req.method, |
||||
'path': req.path, 'accept': req.accept}) |
||||
logger.debug(msg) |
||||
|
||||
# If the request is for /versions, just return the versions container |
||||
if req.path_info_peek() == "versions": |
||||
return self.versions_app |
||||
|
||||
match = self._match_version_string(req.path_info_peek(), req) |
||||
if match: |
||||
if (req.environ['api.major_version'] == 1 and |
||||
req.environ['api.minor_version'] == 0): |
||||
logger.debug(_("Matched versioned URI. Version: %d.%d"), |
||||
req.environ['api.major_version'], |
||||
req.environ['api.minor_version']) |
||||
# Strip the version from the path |
||||
req.path_info_pop() |
||||
return None |
||||
else: |
||||
logger.debug(_("Unknown version in versioned URI: %d.%d. " |
||||
"Returning version choices."), |
||||
req.environ['api.major_version'], |
||||
req.environ['api.minor_version']) |
||||
return self.versions_app |
||||
|
||||
accept = str(req.accept) |
||||
if accept.startswith('application/vnd.openstack.images-'): |
||||
token_loc = len('application/vnd.openstack.images-') |
||||
accept_version = accept[token_loc:] |
||||
match = self._match_version_string(accept_version, req) |
||||
if match: |
||||
if (req.environ['api.major_version'] == 1 and |
||||
req.environ['api.minor_version'] == 0): |
||||
logger.debug(_("Matched versioned media type. " |
||||
"Version: %d.%d"), |
||||
req.environ['api.major_version'], |
||||
req.environ['api.minor_version']) |
||||
return None |
||||
else: |
||||
logger.debug(_("Unknown version in accept header: %d.%d..." |
||||
"returning version choices."), |
||||
req.environ['api.major_version'], |
||||
req.environ['api.minor_version']) |
||||
return self.versions_app |
||||
else: |
||||
if req.accept not in ('*/*', ''): |
||||
logger.debug(_("Unknown accept header: %s..." |
||||
"returning version choices."), req.accept) |
||||
return self.versions_app |
||||
return None |
||||
|
||||
def _match_version_string(self, subject, req): |
||||
""" |
||||
Given a subject string, tries to match a major and/or |
||||
minor version number. If found, sets the api.major_version |
||||
and api.minor_version environ variables. |
||||
|
||||
Returns True if there was a match, false otherwise. |
||||
|
||||
:param subject: The string to check |
||||
:param req: Webob.Request object |
||||
""" |
||||
match = self.version_uri_regex.match(subject) |
||||
if match: |
||||
major_version, minor_version = match.groups(0) |
||||
major_version = int(major_version) |
||||
minor_version = int(minor_version) |
||||
req.environ['api.major_version'] = major_version |
||||
req.environ['api.minor_version'] = minor_version |
||||
return match is not None |
@ -0,0 +1,21 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright 2011 OpenStack LLC. |
||||
# 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. |
||||
|
||||
SUPPORTED_PARAMS = ('StackName', 'TemplateBody', 'NotificationARNs', 'Parameters', |
||||
'Version', 'SignatureVersion', 'Timestamp', 'AWSAccessKeyId', |
||||
'Signature') |
||||
|
@ -0,0 +1,54 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright 2011 OpenStack LLC. |
||||
# 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. |
||||
|
||||
import logging |
||||
|
||||
import routes |
||||
|
||||
from heat.api.v1 import stacks |
||||
from heat.common import wsgi |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
class API(wsgi.Router): |
||||
|
||||
"""WSGI router for Heat v1 API requests.""" |
||||
#TODO |
||||
#DeleteStack |
||||
#GetTemplate |
||||
#UpdateStack |
||||
#ValidateTemplate |
||||
|
||||
|
||||
def __init__(self, conf, **local_conf): |
||||
self.conf = conf |
||||
mapper = routes.Mapper() |
||||
|
||||
stacks_resource = stacks.create_resource(conf) |
||||
|
||||
mapper.resource("stack", "stacks", controller=stacks_resource, |
||||
collection={'detail': 'GET'}) |
||||
|
||||
mapper.connect("/CreateStack", controller=stacks_resource, |
||||
action="create", conditions=dict(method=["POST"])) |
||||
mapper.connect("/", controller=stacks_resource, action="index") |
||||
mapper.connect("/ListStacks", controller=stacks_resource, |
||||
action="list", conditions=dict(method=["GET"])) |
||||
mapper.connect("/DescribeStacks", controller=stacks_resource, |
||||
action="show", conditions=dict(method=["GET"])) |
||||
|
||||
super(API, self).__init__(mapper) |
@ -0,0 +1,157 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright 2010 OpenStack LLC. |
||||
# 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. |
||||
|
||||
""" |
||||
/stack endpoint for heat v1 API |
||||
""" |
||||
|
||||
import httplib |
||||
import json |
||||
import logging |
||||
import sys |
||||
|
||||
import webob |
||||
from webob.exc import (HTTPNotFound, |
||||
HTTPConflict, |
||||
HTTPBadRequest) |
||||
|
||||
from heat.common import exception |
||||
from heat.common import wsgi |
||||
|
||||
logger = logging.getLogger('heat.api.v1.stacks') |
||||
|
||||
class StackController(object): |
||||
|
||||
""" |
||||
WSGI controller for stacks resource in heat v1 API |
||||
|
||||
""" |
||||
|
||||
def __init__(self, options): |
||||
self.options = options |
||||
|
||||
def list(self, req): |
||||
""" |
||||
Returns the following information for all stacks: |
||||
""" |
||||
return {'ListStacksResponse': [ |
||||
{'ListStacksResult': [ |
||||
{'StackSummaries': [ |
||||
{'member': [ |
||||
{'StackId': 'arn:aws:cloudformation:us-east-1:1234567:stack/TestCreate1/aaaaa', |
||||
'StackStatus': 'CREATE_IN_PROGRESS', |
||||
'StackName': 'vpc1', |
||||
'CreationTime': '2011-05-23T15:47:44Z', |
||||
'TemplateDescription': 'Creates one EC2 instance and a load balancer.', |
||||
}] |
||||
}, |
||||
{'member': [ |
||||
{'StackId': 'arn:aws:cloudformation:us-east-1:1234567:stack/TestDelete2/bbbbb', |
||||
'StackStatus': 'DELETE_COMPLETE', |
||||
'StackName': 'WP1', |
||||
'CreationTime': '2011-03-05T19:57:58Z', |
||||
'TemplateDescription': 'A simple basic Cloudformation Template.', |
||||
}] |
||||
} |
||||
]}]}]} |
||||
|
||||
|
||||
def describe(self, req): |
||||
|
||||
return {'stack': [ |
||||
{'id': 'id', |
||||
'name': '<stack NAME', |
||||
'disk_format': '<DISK_FORMAT>', |
||||
'container_format': '<CONTAINER_FORMAT>' } ] } |
||||
|
||||
|
||||
def create(self, req): |
||||
for p in req.params: |
||||
print 'create %s=%s' % (p, req.params[p]) |
||||
|
||||
return {'CreateStackResult': [{'StackId': '007'}]} |
||||
|
||||
def update(self, req, id, image_meta, image_data): |
||||
""" |
||||
Updates an existing image with the registry. |
||||
|
||||
:param request: The WSGI/Webob Request object |
||||
:param id: The opaque image identifier |
||||
|
||||
:retval Returns the updated image information as a mapping |
||||
""" |
||||
|
||||
return {'image_meta': 'bla'} |
||||
|
||||
|
||||
def delete(self, req, id): |
||||
""" |
||||
Deletes the image and all its chunks from heat |
||||
|
||||
:param req: The WSGI/Webob Request object |
||||
:param id: The opaque image identifier |
||||
|
||||
:raises HttpBadRequest if image registry is invalid |
||||
:raises HttpNotFound if image or any chunk is not available |
||||
:raises HttpNotAuthorized if image or any chunk is not |
||||
deleteable by the requesting user |
||||
""" |
||||
|
||||
|
||||
class StackDeserializer(wsgi.JSONRequestDeserializer): |
||||
"""Handles deserialization of specific controller method requests.""" |
||||
|
||||
def _deserialize(self, request): |
||||
result = {} |
||||
return result |
||||
|
||||
def create(self, request): |
||||
return self._deserialize(request) |
||||
|
||||
def update(self, request): |
||||
return self._deserialize(request) |
||||
|
||||
|
||||
class StackSerializer(wsgi.JSONResponseSerializer): |
||||
"""Handles serialization of specific controller method responses.""" |
||||
|
||||
def _inject_location_header(self, response, image_meta): |
||||
response.headers['Location'] = 'location' |
||||
|
||||
def _inject_checksum_header(self, response, image_meta): |
||||
response.headers['ETag'] = 'checksum' |
||||
|
||||
def update(self, response, result): |
||||
return |
||||
|
||||
def create(self, response, result): |
||||
""" Create """ |
||||
response.status = 201 |
||||
response.headers['Content-Type'] = 'application/json' |
||||
response.body = self.to_json(dict(CreateStackResult=result)) |
||||
self._inject_location_header(response, result) |
||||
self._inject_checksum_header(response, result) |
||||
return response |
||||
|
||||
def handle_stack(self, req, id): |
||||
return {'got-stack-id': id} |
||||
|
||||
def create_resource(options): |
||||
"""Stacks resource factory method""" |
||||
deserializer = StackDeserializer() |
||||
serializer = StackSerializer() |
||||
return wsgi.Resource(StackController(options), deserializer, serializer) |
@ -0,0 +1,68 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright 2011 OpenStack LLC. |
||||
# 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. |
||||
|
||||
""" |
||||
Controller that returns information on the heat API versions |
||||
""" |
||||
|
||||
import httplib |
||||
import json |
||||
|
||||
import webob.dec |
||||
|
||||
from heat.common import wsgi |
||||
|
||||
|
||||
class Controller(object): |
||||
|
||||
""" |
||||
A controller that produces information on the heat API versions. |
||||
""" |
||||
|
||||
def __init__(self, conf): |
||||
self.conf = conf |
||||
|
||||
@webob.dec.wsgify |
||||
def __call__(self, req): |
||||
"""Respond to a request for all OpenStack API versions.""" |
||||
version_objs = [ |
||||
{ |
||||
"id": "v1.1", |
||||
"status": "CURRENT", |
||||
"links": [ |
||||
{ |
||||
"rel": "self", |
||||
"href": self.get_href(req)}]}, |
||||
{ |
||||
"id": "v1.0", |
||||
"status": "SUPPORTED", |
||||
"links": [ |
||||
{ |
||||
"rel": "self", |
||||
"href": self.get_href(req)}]}] |
||||
|
||||
body = json.dumps(dict(versions=version_objs)) |
||||
|
||||
response = webob.Response(request=req, |
||||
status=httplib.MULTIPLE_CHOICES, |
||||
content_type='application/json') |
||||
response.body = body |
||||
|
||||
return response |
||||
|
||||
def get_href(self, req): |
||||
return "%s/v1/" % req.host_url |
@ -0,0 +1,133 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright 2010-2011 OpenStack, LLC |
||||
# 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. |
||||
|
||||
""" |
||||
Client classes for callers of a heat system |
||||
""" |
||||
|
||||
import errno |
||||
import httplib |
||||
import json |
||||
import logging |
||||
import os |
||||
import socket |
||||
import sys |
||||
|
||||
import heat.api.v1 |
||||
from heat.common import client as base_client |
||||
from heat.common import exception |
||||
from heat.common import utils |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
SUPPORTED_PARAMS = heat.api.v1.SUPPORTED_PARAMS |
||||
|
||||
|
||||
class V1Client(base_client.BaseClient): |
||||
|
||||
"""Main client class for accessing heat resources""" |
||||
|
||||
DEFAULT_PORT = 8000 |
||||
DEFAULT_DOC_ROOT = "/v1" |
||||
|
||||
def _insert_common_parameters(self, params): |
||||
params['Version'] = '2010-05-15' |
||||
params['SignatureVersion'] = '2' |
||||
params['SignatureMethod'] = 'HmacSHA256' |
||||
|
||||
def list_stacks(self, **kwargs): |
||||
params = self._extract_params({}, SUPPORTED_PARAMS) |
||||
self._insert_common_parameters(params) |
||||
|
||||
res = self.do_request("GET", "/ListStacks", params=params) |
||||
data = json.loads(res.read()) |
||||
return data |
||||
|
||||
def show_stack(self, **kwargs): |
||||
params = self._extract_params(kwargs, SUPPORTED_PARAMS) |
||||
self._insert_common_parameters(params) |
||||
|
||||
res = self.do_request("GET", "/DescribeStacks", params=params) |
||||
data = json.loads(res.read()) |
||||
return data |
||||
|
||||
def create_stack(self, **kwargs): |
||||
|
||||
params = self._extract_params(kwargs, SUPPORTED_PARAMS) |
||||
self._insert_common_parameters(params) |
||||
res = self.do_request("POST", "/CreateStack", params=params) |
||||
|
||||
data = json.loads(res.read()) |
||||
return data |
||||
|
||||
def update_stack(self, **kwargs): |
||||
return |
||||
|
||||
def delete_stack(self, **kwargs): |
||||
self._insert_common_parameters(params) |
||||
params = self._extract_params(kwargs, SUPPORTED_PARAMS) |
||||
self.do_request("DELETE", "/DeleteStack", params) |
||||
return True |
||||
|
||||
Client = 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 or os.getenv('OS_AUTH_URL'): |
||||
force_strategy = 'keystone' |
||||
else: |
||||
force_strategy = None |
||||
|
||||
creds = dict(username=username or |
||||
os.getenv('OS_AUTH_USER', os.getenv('OS_USERNAME')), |
||||
password=password or |
||||
os.getenv('OS_AUTH_KEY', os.getenv('OS_PASSWORD')), |
||||
tenant=tenant or |
||||
os.getenv('OS_AUTH_TENANT', |
||||
os.getenv('OS_TENANT_NAME')), |
||||
auth_url=auth_url or os.getenv('OS_AUTH_URL'), |
||||
strategy=force_strategy or auth_strategy or |
||||
os.getenv('OS_AUTH_STRATEGY', 'noauth'), |
||||
region=region or os.getenv('OS_REGION_NAME'), |
||||
) |
||||
|
||||
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 = Client |
||||
|
||||
return client(host=host, |
||||
port=port, |
||||
use_ssl=use_ssl, |
||||
auth_tok=auth_token or |
||||
os.getenv('OS_TOKEN'), |
||||
creds=creds, |
||||
insecure=insecure) |
@ -0,0 +1,16 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright 2010-2011 OpenStack LLC. |
||||
# 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. |
@ -0,0 +1,267 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
||||
|
||||
# Copyright 2011 OpenStack LLC. |
||||
# 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. |
||||
|
||||
""" |
||||
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 |
||||
|
||||
|
||||
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): |
||||
self.creds = creds |
||||
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? |
||||
|
||||