diff options
author | Zane Bitter <zbitter@redhat.com> | 2015-11-24 12:29:38 -0500 |
---|---|---|
committer | Zane Bitter <zbitter@redhat.com> | 2016-01-18 19:11:25 -0500 |
commit | fa19a617a79fd1cb0d892bb8ea87c4b9f6398c34 (patch) | |
tree | 19687495cdc5159bbfab3b0e918f9e150d00ecea | |
parent | 0a1daa7d2e049077fef03d1134a993b81e5739b0 (diff) | |
download | heat-fa19a617a79fd1cb0d892bb8ea87c4b9f6398c34.tar.gz |
Load template files only from their known source
Modify get_class to ensure that user-defined resources cannot result in
reads from the local filesystem. Only resources defined by the operator
in the global environment should read local files.
To make this work, this patch also adds a separate
get_class_to_instantiate() method to the Environment.
We were previously using get_class for two different purposes - to get a
resource plugin on which we could perform introspection to obtain the
properties and attributes schema, and to get a resource plugin we could
instantiate to create a Resource object. These are both the same except in
the case of a TemplateResource, where having two different use cases for
the same piece of code was adding considerable extra complexity. Combining
the use cases in this way also made the error handling confusing (leading
to bug 1518458).
This change separates out the two cases.
Change-Id: I845e7d23c73242a4a4c9c40599690ab705c75caa
Closes-Bug: #1496277
Related-Bug: #1447194
Related-Bug: #1518458
Related-Bug: #1508115
(cherry picked from commit 06a713c4456203cd561f16721dc8ac3bcbb37a3
and 26e6d5f6d776c1027c4f27058767952a58d15e25)
-rw-r--r-- | heat/engine/environment.py | 35 | ||||
-rw-r--r-- | heat/engine/resource.py | 11 | ||||
-rw-r--r-- | heat/engine/resources/openstack/heat/resource_group.py | 6 | ||||
-rw-r--r-- | heat/engine/resources/template_resource.py | 11 | ||||
-rw-r--r-- | heat/engine/service.py | 4 | ||||
-rw-r--r-- | heat/tests/test_provider_template.py | 10 | ||||
-rw-r--r-- | heat/tests/test_resource.py | 5 |
7 files changed, 51 insertions, 31 deletions
diff --git a/heat/engine/environment.py b/heat/engine/environment.py index bd81c8677..41d91d5af 100644 --- a/heat/engine/environment.py +++ b/heat/engine/environment.py @@ -112,6 +112,12 @@ class ResourceInfo(object): def matches(self, resource_type): return False + def get_class(self): + raise NotImplemented + + def get_class_to_instantiate(self): + return self.get_class() + def __str__(self): return '[%s](User:%s) %s -> %s' % (self.description, self.user_resource, @@ -140,10 +146,20 @@ class TemplateResourceInfo(ResourceInfo): def get_class(self): from heat.engine.resources import template_resource + if self.user_resource: + allowed_schemes = template_resource.REMOTE_SCHEMES + else: + allowed_schemes = template_resource.LOCAL_SCHEMES + data = template_resource.TemplateResource.get_template_file( + self.template_name, + allowed_schemes) env = self.registry.environment - return template_resource.generate_class(str(self.name), - self.template_name, - env) + return template_resource.generate_class_from_template(str(self.name), + data, env) + + def get_class_to_instantiate(self): + from heat.engine.resources import template_resource + return template_resource.TemplateResource class MapResourceInfo(ResourceInfo): @@ -398,6 +414,13 @@ class ResourceRegistry(object): return match def get_class(self, resource_type, resource_name=None): + info = self.get_resource_info(resource_type, + resource_name=resource_name) + if info is None: + raise exception.ResourceTypeNotFound(type_name=resource_type) + return info.get_class() + + def get_class_to_instantiate(self, resource_type, resource_name=None): if resource_type == "": msg = _('Resource "%s" has no type') % resource_name raise exception.StackValidationFailed(message=msg) @@ -414,7 +437,7 @@ class ResourceRegistry(object): if info is None: msg = _("Unknown resource Type : %s") % resource_type raise exception.StackValidationFailed(message=msg) - return info.get_class() + return info.get_class_to_instantiate() def as_dict(self): """Return user resources in a dict format.""" @@ -521,6 +544,10 @@ class Environment(object): def get_class(self, resource_type, resource_name=None): return self.registry.get_class(resource_type, resource_name) + def get_class_to_instantiate(self, resource_type, resource_name=None): + return self.registry.get_class_to_instantiate(resource_type, + resource_name) + def get_types(self, support_status=None): return self.registry.get_types(support_status) diff --git a/heat/engine/resource.py b/heat/engine/resource.py index d934a1e0e..0c01504de 100644 --- a/heat/engine/resource.py +++ b/heat/engine/resource.py @@ -137,14 +137,11 @@ class Resource(object): # Call is already for a subclass, so pass it through ResourceClass = cls else: - from heat.engine.resources import template_resource - registry = stack.env.registry - try: - ResourceClass = registry.get_class(definition.resource_type, - resource_name=name) - except exception.NotFound: - ResourceClass = template_resource.TemplateResource + ResourceClass = registry.get_class_to_instantiate( + definition.resource_type, + resource_name=name) + assert issubclass(ResourceClass, Resource) return super(Resource, cls).__new__(ResourceClass) diff --git a/heat/engine/resources/openstack/heat/resource_group.py b/heat/engine/resources/openstack/heat/resource_group.py index ddb6dd91f..a98b00405 100644 --- a/heat/engine/resources/openstack/heat/resource_group.py +++ b/heat/engine/resources/openstack/heat/resource_group.py @@ -193,11 +193,7 @@ class ResourceGroup(stack_resource.StackResource): val_templ = template.Template(test_tmpl) res_def = val_templ.resource_definitions(self.stack)["0"] # make sure we can resolve the nested resource type - try: - self.stack.env.get_class(res_def.resource_type) - except exception.NotFound: - # its a template resource - pass + self.stack.env.get_class_to_instantiate(res_def.resource_type) try: name = "%s-%s" % (self.stack.name, self.name) diff --git a/heat/engine/resources/template_resource.py b/heat/engine/resources/template_resource.py index ffc74601b..e8c15a5c9 100644 --- a/heat/engine/resources/template_resource.py +++ b/heat/engine/resources/template_resource.py @@ -26,8 +26,11 @@ from heat.engine.resources import stack_resource from heat.engine import template -def generate_class(name, template_name, env): - data = TemplateResource.get_template_file(template_name, ('file',)) +REMOTE_SCHEMES = ('http', 'https') +LOCAL_SCHEMES = ('file',) + + +def generate_class_from_template(name, data, env): tmpl = template.Template(template_format.parse(data)) props, attrs = TemplateResource.get_schemas(tmpl, env.param_defaults) cls = type(name, (TemplateResource,), @@ -79,9 +82,9 @@ class TemplateResource(stack_resource.StackResource): self.template_name = tri.template_name self.resource_type = tri.name if tri.user_resource: - self.allowed_schemes = ('http', 'https') + self.allowed_schemes = REMOTE_SCHEMES else: - self.allowed_schemes = ('http', 'https', 'file') + self.allowed_schemes = REMOTE_SCHEMES + LOCAL_SCHEMES return tri diff --git a/heat/engine/service.py b/heat/engine/service.py index 070244e07..8fe8746db 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -989,8 +989,6 @@ class EngineService(service.Service): """ try: resource_class = resources.global_env().get_class(type_name) - except exception.StackValidationFailed: - raise exception.ResourceTypeNotFound(type_name=type_name) except exception.NotFound as ex: raise exception.StackValidationFailed(message=ex.message) @@ -1021,8 +1019,6 @@ class EngineService(service.Service): try: return resources.global_env().get_class( type_name).resource_to_template(type_name) - except exception.StackValidationFailed: - raise exception.ResourceTypeNotFound(type_name=type_name) except exception.NotFound as ex: raise exception.StackValidationFailed(message=ex.message) diff --git a/heat/tests/test_provider_template.py b/heat/tests/test_provider_template.py index 068468dcc..01b6bc675 100644 --- a/heat/tests/test_provider_template.py +++ b/heat/tests/test_provider_template.py @@ -613,7 +613,11 @@ class ProviderTemplateTest(common.HeatTestCase): env_str = {'resource_registry': {'resources': {'fred': { "OS::ResourceType": test_templ_name}}}} - env = environment.Environment(env_str) + global_env = environment.Environment({}, user_env=False) + global_env.load(env_str) + with mock.patch('heat.engine.resources._environment', + global_env): + env = environment.Environment({}) cls = env.get_class('OS::ResourceType', 'fred') self.assertNotEqual(template_resource.TemplateResource, cls) self.assertTrue(issubclass(cls, template_resource.TemplateResource)) @@ -640,10 +644,6 @@ class ProviderTemplateTest(common.HeatTestCase): self.assertTrue(test_templ, "Empty test template") self.m.StubOutWithMock(urlfetch, "get") urlfetch.get(test_templ_name, - allowed_schemes=('file',) - ).AndRaise(urlfetch.URLFetchError( - _('Failed to retrieve template'))) - urlfetch.get(test_templ_name, allowed_schemes=('http', 'https')).AndReturn(test_templ) parsed_test_templ = template_format.parse(test_templ) self.m.ReplayAll() diff --git a/heat/tests/test_resource.py b/heat/tests/test_resource.py index 2dda46eab..ab4052caf 100644 --- a/heat/tests/test_resource.py +++ b/heat/tests/test_resource.py @@ -67,12 +67,13 @@ class ResourceTest(common.HeatTestCase): self.patch('heat.engine.resource.warnings') def test_get_class_ok(self): - cls = resources.global_env().get_class('GenericResourceType') + cls = resources.global_env().get_class_to_instantiate( + 'GenericResourceType') self.assertEqual(generic_rsrc.GenericResource, cls) def test_get_class_noexist(self): self.assertRaises(exception.StackValidationFailed, - resources.global_env().get_class, + resources.global_env().get_class_to_instantiate, 'NoExistResourceType') def test_resource_new_ok(self): |