167 lines
6.5 KiB
Python
167 lines
6.5 KiB
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.
|
|
|
|
from requests import exceptions
|
|
|
|
from heat.common import exception
|
|
from heat.common import template_format
|
|
from heat.common import urlfetch
|
|
from heat.engine import attributes
|
|
from heat.engine import environment
|
|
from heat.engine import properties
|
|
from heat.engine import stack_resource
|
|
from heat.engine import template
|
|
|
|
from heat.openstack.common import log as logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class TemplateResource(stack_resource.StackResource):
|
|
'''
|
|
A resource implemented by a nested stack.
|
|
|
|
This implementation passes resource properties as parameters to the nested
|
|
stack. Outputs of the nested stack are exposed as attributes of this
|
|
resource.
|
|
'''
|
|
|
|
def __init__(self, name, json_snippet, stack):
|
|
self._parsed_nested = None
|
|
self.stack = stack
|
|
tri = stack.env.get_resource_info(
|
|
json_snippet['Type'],
|
|
registry_type=environment.TemplateResourceInfo)
|
|
self.template_name = tri.template_name
|
|
if tri.user_resource:
|
|
self.allowed_schemes = ('http', 'https')
|
|
else:
|
|
self.allowed_schemes = ('http', 'https', 'file')
|
|
|
|
tmpl = template.Template(self.parsed_nested)
|
|
self.properties_schema = (properties.Properties
|
|
.schema_from_params(tmpl.param_schemata()))
|
|
self.attributes_schema = (attributes.Attributes
|
|
.schema_from_outputs(tmpl[template.OUTPUTS]))
|
|
|
|
super(TemplateResource, self).__init__(name, json_snippet, stack)
|
|
|
|
def _to_parameters(self):
|
|
'''
|
|
:return: parameter values for our nested stack based on our properties
|
|
'''
|
|
params = {}
|
|
for n, v in iter(self.properties.props.items()):
|
|
if not v.implemented():
|
|
continue
|
|
|
|
val = self.properties[n]
|
|
|
|
if val is not None:
|
|
# take a list and create a CommaDelimitedList
|
|
if v.type() == properties.LIST:
|
|
if isinstance(val[0], dict):
|
|
flattened = []
|
|
for (i, item) in enumerate(val):
|
|
for (k, v) in iter(item.items()):
|
|
mem_str = '.member.%d.%s=%s' % (i, k, v)
|
|
flattened.append(mem_str)
|
|
params[n] = ','.join(flattened)
|
|
else:
|
|
val = ','.join(val)
|
|
|
|
# for MAP, the JSON param takes either a collection or string,
|
|
# so just pass it on and let the param validate as appropriate
|
|
|
|
params[n] = val
|
|
|
|
return params
|
|
|
|
@property
|
|
def parsed_nested(self):
|
|
if not self._parsed_nested:
|
|
self._parsed_nested = template_format.parse(self.template_data)
|
|
return self._parsed_nested
|
|
|
|
@property
|
|
def template_data(self):
|
|
t_data = self.stack.t.files.get(self.template_name)
|
|
if not t_data and self.template_name.endswith((".yaml", ".template")):
|
|
try:
|
|
t_data = urlfetch.get(self.template_name,
|
|
allowed_schemes=self.allowed_schemes)
|
|
except (exceptions.RequestException, IOError) as r_exc:
|
|
raise ValueError("Could not fetch remote template '%s': %s" %
|
|
(self.template_name, str(r_exc)))
|
|
else:
|
|
# TODO(Randall) Whoops, misunderstanding on my part; this
|
|
# doesn't actually persist to the db like I thought.
|
|
# Find a better way
|
|
self.stack.t.files[self.template_name] = t_data
|
|
return t_data
|
|
|
|
def validate(self):
|
|
cri = self.stack.env.get_resource_info(
|
|
self.type(),
|
|
registry_type=environment.ClassResourceInfo)
|
|
|
|
# If we're using an existing resource type as a facade for this
|
|
# template, check for compatibility between the interfaces.
|
|
if cri is not None and not isinstance(self, cri.get_class()):
|
|
facade_cls = cri.get_class()
|
|
facade_schemata = properties.schemata(facade_cls.properties_schema)
|
|
|
|
for n, fs in facade_schemata.items():
|
|
if fs.required and n not in self.properties_schema:
|
|
msg = ("Required property %s for facade %s "
|
|
"missing in provider") % (n, self.type())
|
|
raise exception.StackValidationFailed(message=msg)
|
|
|
|
ps = self.properties_schema.get(n)
|
|
if (n in self.properties_schema and
|
|
(fs.type != ps.type)):
|
|
# Type mismatch
|
|
msg = ("Property %s type mismatch between facade %s (%s) "
|
|
"and provider (%s)") % (n, self.type(),
|
|
fs.type, ps.type)
|
|
raise exception.StackValidationFailed(message=msg)
|
|
|
|
for n, ps in self.properties_schema.items():
|
|
if ps.required and n not in facade_schemata:
|
|
# Required property for template not present in facade
|
|
msg = ("Provider requires property %s "
|
|
"unknown in facade %s") % (n, self.type())
|
|
raise exception.StackValidationFailed(message=msg)
|
|
|
|
for attr in facade_cls.attributes_schema:
|
|
if attr not in self.attributes_schema:
|
|
msg = ("Attribute %s for facade %s "
|
|
"missing in provider") % (attr, self.type())
|
|
raise exception.StackValidationFailed(message=msg)
|
|
|
|
return super(TemplateResource, self).validate()
|
|
|
|
def handle_create(self):
|
|
return self.create_with_template(self.parsed_nested,
|
|
self._to_parameters())
|
|
|
|
def handle_delete(self):
|
|
return self.delete_nested()
|
|
|
|
def FnGetRefId(self):
|
|
if not self.nested():
|
|
return unicode(self.name)
|
|
return self.nested().identifier().arn()
|