Add describe resource API calls

Fixes #62.

This commit implements the `DescribeStackResource`,
`DescribeStackResources` and `ListStackResources` AWS API calls.

Change-Id: Id9161b3c3eb527d5936c5b8978e32a67ba6c12bb
This commit is contained in:
Tomas Sedovic 2012-06-14 17:51:45 +02:00
parent 614f7868c4
commit 247cc2bb9a
8 changed files with 268 additions and 1 deletions

View File

@ -297,6 +297,61 @@ def stack_events_list(options, arguments):
print 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 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 result
@utils.catch_error('resource-list-details')
def stack_resources_list_details(options, arguments):
'''
Display details of all resources in the specified stack.
'''
c = get_client(options)
logical_resource_id = arguments.pop(0) if arguments else None
parameters = {
'StackName': options.stack_name,
'PhysicalResourceId': options.physical_resource_id,
'LogicalResourceId': logical_resource_id,
}
result = c.describe_stack_resources(**parameters)
print result
@utils.catch_error('list')
def stack_list(options, arguments):
'''
@ -397,6 +452,10 @@ def create_options(parser):
parser.add_option('-P', '--parameters', metavar="parameters", default=None,
help="Parameter values used to create the stack.")
parser.add_option('-n', '--stack-name', default=None,
help="Name of the queried stack")
parser.add_option('-c', '--physical-resource-id', default=None,
help="Physical ID of the queried resource")
def credentials_from_env():
@ -484,6 +543,9 @@ def lookup_command(parser, command_name):
'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,
@ -530,6 +592,12 @@ Commands:
event-list List events for a stack
resource Describe the resource
resource-list Show list of resources belonging to a stack
resource-list-details Detailed view of resources belonging to a stack
"""
oparser = optparse.OptionParser(version='%%prog %s'

View File

@ -127,6 +127,9 @@ class API(wsgi.Router):
'validate_template': 'ValidateTemplate',
'get_template': 'GetTemplate',
'estimate_template_cost': 'EstimateTemplateCost',
'describe_stack_resource': 'DescribeStackResource',
'describe_stack_resources': 'DescribeStackResources',
'list_stack_resources': 'ListStackResources',
}
def __init__(self, conf, **local_conf):

View File

@ -243,6 +243,98 @@ class StackController(object):
return {'DescribeStackEventsResult': {'StackEvents': events}}
def describe_stack_resource(self, req):
"""
Return the details of the given resource belonging to the given stack.
"""
con = req.context
args = {
'stack_name': req.params.get('StackName'),
'resource_name': req.params.get('LogicalResourceId'),
}
try:
resource_details = rpc.call(con, 'engine',
{'method': 'describe_stack_resource',
'args': args})
except rpc_common.RemoteError as ex:
return webob.exc.HTTPBadRequest(str(ex))
return {
'DescribeStackResourceResponse': {
'DescribeStackResourceResult': {
'StackResourceDetail': resource_details,
},
},
}
def describe_stack_resources(self, req):
"""
Return details of resources specified by the parameters.
`StackName`: returns all resources belonging to the stack
`PhysicalResourceId`: returns all resources belonging to the stack this
resource is associated with.
Only one of the parameters may be specified.
Optional parameter:
`LogicalResourceId`: filter the resources list by the logical resource
id.
"""
con = req.context
stack_name = req.params.get('StackName')
physical_resource_id = req.params.get('PhysicalResourceId')
if stack_name and physical_resource_id:
msg = 'Use `StackName` or `PhysicalResourceId` but not both'
return webob.exc.HTTPBadRequest(msg)
args = {
'stack_name': stack_name,
'physical_resource_id': physical_resource_id,
'logical_resource_id': req.params.get('LogicalResourceId'),
}
try:
resources = rpc.call(con, 'engine',
{'method': 'describe_stack_resources',
'args': args})
except rpc_common.RemoteError as ex:
return webob.exc.HTTPBadRequest(str(ex))
response = {
'DescribeStackResourcesResult': {
'StackResources': resources,
}
}
return response
def list_stack_resources(self, req):
"""
Return summary of the resources belonging to the specified stack.
"""
con = req.context
try:
resources = rpc.call(con, 'engine', {
'method': 'list_stack_resources',
'args': {'stack_name': req.params.get('StackName')}
})
except rpc_common.RemoteError as ex:
return webob.exc.HTTPBadRequest(str(ex))
return {
'ListStackResourcesResponse': {
'ListStackResourcesResult': {
'StackResourceSummaries': resources,
},
},
}
def create_resource(options):
"""

View File

@ -65,6 +65,15 @@ class V1Client(base_client.BaseClient):
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):
return self.stack_request("DescribeStackResources", "GET", **kwargs)
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)

View File

@ -16,4 +16,6 @@
SUPPORTED_PARAMS = ('StackName', 'TemplateBody', 'TemplateUrl',
'NotificationARNs', 'Parameters', 'Version',
'SignatureVersion', 'Timestamp', 'AWSAccessKeyId',
'Signature', 'KeyStoneCreds', 'Timeout')
'Signature', 'KeyStoneCreds', 'Timeout',
'LogicalResourceId', 'PhysicalResourceId', 'NextToken',
)

View File

@ -95,6 +95,11 @@ def resource_get_by_name_and_stack(context, resource_name, stack_id):
resource_name, stack_id)
def resource_get_by_physical_resource_id(context, physical_resource_id):
return IMPL.resource_get_by_physical_resource_id(context,
physical_resource_id)
def stack_get(context, stack_id):
return IMPL.stack_get(context, stack_id)

View File

@ -98,6 +98,13 @@ def resource_get_by_name_and_stack(context, resource_name, stack_id):
return result
def resource_get_by_physical_resource_id(context, physical_resource_id):
result = (model_query(context, models.Resource)
.filter_by(nova_instance=physical_resource_id)
.first())
return result
def resource_get_all(context):
results = model_query(context, models.Resource).all()

View File

@ -391,6 +391,67 @@ class EngineManager(manager.Manager):
msg = 'Error creating event'
return [msg, None]
def describe_stack_resource(self, context, stack_name, resource_name):
self._authenticate(context)
stack = db_api.stack_get(context, stack_name)
if not stack:
raise AttributeError('Unknown stack name')
resource = db_api.resource_get_by_name_and_stack(context,
resource_name,
stack.id)
if not resource:
raise AttributeError('Unknown resource name')
return format_resource_attributes(stack, resource)
def describe_stack_resources(self, context, stack_name,
physical_resource_id, logical_resource_id):
self._authenticate(context)
if stack_name:
stack = db_api.stack_get(context, stack_name)
else:
resource = db_api.resource_get_by_physical_resource_id(context,
physical_resource_id)
if not resource:
msg = "The specified PhysicalResourceId doesn't exist"
raise AttributeError(msg)
stack = resource.stack
if not stack:
raise AttributeError("The specified stack doesn't exist")
resources = []
for r in stack.resources:
if logical_resource_id and r.name != logical_resource_id:
continue
formatted = format_resource_attributes(stack, r)
# this API call uses Timestamp instead of LastUpdatedTimestamp
formatted['Timestamp'] = formatted['LastUpdatedTimestamp']
del formatted['LastUpdatedTimestamp']
resources.append(formatted)
return resources
def list_stack_resources(self, context, stack_name):
self._authenticate(context)
stack = db_api.stack_get(context, stack_name)
if not stack:
raise AttributeError('Unknown stack name')
resources = []
response_keys = ('ResourceStatus', 'LogicalResourceId',
'LastUpdatedTimestamp', 'PhysicalResourceId',
'ResourceType')
for r in stack.resources:
formatted = format_resource_attributes(stack, r)
for key in formatted.keys():
if not key in response_keys:
del formatted[key]
resources.append(formatted)
return resources
def metadata_register_address(self, context, url):
config.FLAGS.heat_metadata_server_url = url
@ -520,3 +581,23 @@ class EngineManager(manager.Manager):
self.run_rule(None, wr)
return [None, wd.data]
def format_resource_attributes(stack, resource):
"""
Return a representation of the given resource that mathes the API output
expectations.
"""
template = resource.parsed_template.template
template_resources = template.get('Resources', {})
resource_type = template_resources.get(resource.name, {}).get('Type', '')
last_updated_time = resource.updated_at or resource.created_at
return {
'StackId': stack.id,
'StackName': stack.name,
'LogicalResourceId': resource.name,
'PhysicalResourceId': resource.nova_instance or '',
'ResourceType': resource_type,
'LastUpdatedTimestamp': last_updated_time.isoformat(),
'ResourceStatus': resource.state,
}