Supply outputs for reference IDs in StackResources

Previously, in 1fff951639, we added outputs
to the generated templates of various nested stack resources that
referenced any attributes required to calculate the parent resource's
attributes. That was necessary because attribute values are not cached
unless they are referenced somewhere. It isn't necessary for reference IDs
(i.e. the results of get_resource) because those are always available.
However, providing reference IDs as outputs as well will allow us to get
all attributes of the parent resource from outputs of the nested stack.

Unlike ResourceGroup/Chain, in InstanceGroup and AutoscalingGroup the order
and content of the member in list-type outputs cannot be determined at
template generation time (failed resources are excluded, and the sort order
depends on creation time). Therefore, always use dicts instead of lists as
outputs, both for the new reference ID outputs in AutoscalingGroup and
existing attribute outputs. For ResourceGroup/Chain, create both dicts and
lists as appropriate, so that each attribute value can be obtained directly
from an output.

Change-Id: I3139246a5acb17df9018e7c7a2cf88f740b3f8be
Partial-Bug: #1731349
This commit is contained in:
Zane Bitter 2018-01-08 17:23:12 -05:00
parent 7a046e6ffd
commit adcb72b17b
6 changed files with 57 additions and 33 deletions

View File

@ -99,7 +99,7 @@ def Ref(stack, fn_name, args):
{ "Ref" : "<resource_name>" }
"""
if args in stack:
if stack is None or args in stack:
RefClass = hot_funcs.GetResource
else:
RefClass = ParamRef

View File

@ -219,28 +219,34 @@ class AutoScalingResourceGroup(aws_asg.AutoScalingGroup):
raise exception.InvalidTemplateAttribute(resource=self.name,
key=key)
def _nested_output_defns(self, resource_names, get_attr_fn):
def _nested_output_defns(self, resource_names, get_attr_fn, get_res_fn):
for attr in self.referenced_attrs():
if isinstance(attr, six.string_types):
key, path = attr, []
output_name = attr
else:
key, path = attr[0], list(attr[1:])
output_name = ', '.join(six.text_type(a) for a in attr)
# Always use map types, as list order is not defined at
# template generation time.
if key == self.OUTPUTS_LIST:
key = self.OUTPUTS
if key == self.REFS:
key = self.REFS_MAP
if key.startswith("resource."):
keycomponents = key.split('.', 2)
path = keycomponents[2:] + path
if path:
key = self.OUTPUTS
output_name = ', '.join([self.OUTPUTS] + path)
else:
key = self.REFS_MAP
output_name = ', '.join(six.text_type(a) for a in [key] + path)
value = None
if key == self.OUTPUTS and path:
if key == self.REFS_MAP:
value = {r: get_res_fn(r) for r in resource_names}
elif key == self.OUTPUTS and path:
value = {r: get_attr_fn([r] + path) for r in resource_names}
yield output.OutputDefinition(output_name, value)
elif key == self.OUTPUTS_LIST and path:
value = [get_attr_fn([r] + path) for r in resource_names]
if value is not None:
yield output.OutputDefinition(output_name, value)

View File

@ -274,12 +274,13 @@ class InstanceGroup(stack_resource.StackResource):
child_env=child_env)
# Subclasses use HOT templates
att_func = 'get_attr'
if att_func not in tmpl.functions:
att_func = 'Fn::GetAtt'
att_func, res_func = 'get_attr', 'get_resource'
if att_func not in tmpl.functions or res_func not in tmpl.functions:
att_func, res_func = 'Fn::GetAtt', 'Ref'
get_attr = functools.partial(tmpl.functions[att_func], None, att_func)
get_res = functools.partial(tmpl.functions[res_func], None, res_func)
for odefn in self._nested_output_defns([k for k, d in definitions],
get_attr):
get_attr, get_res):
tmpl.add_output(odefn)
return tmpl
@ -400,7 +401,7 @@ class InstanceGroup(stack_resource.StackResource):
return u','.join(inst.FnGetAtt('PublicIp') or '0.0.0.0'
for inst in grouputils.get_members(self)) or None
def _nested_output_defns(self, resource_names, get_attr_fn):
def _nested_output_defns(self, resource_names, get_attr_fn, get_res_fn):
for attr in self.referenced_attrs():
if isinstance(attr, six.string_types):
key = attr
@ -408,7 +409,8 @@ class InstanceGroup(stack_resource.StackResource):
key = attr[0]
if key == self.INSTANCE_LIST:
value = [get_attr_fn([r, 'PublicIp']) for r in resource_names]
value = {r: get_attr_fn([r, 'PublicIp'])
for r in resource_names}
yield output.OutputDefinition(key, value)
def child_template(self):

View File

@ -130,8 +130,11 @@ class ResourceChain(stack_resource.StackResource):
att_func = 'get_attr'
get_attr = functools.partial(nested_template.functions[att_func],
None, att_func)
res_func = 'get_resource'
get_res = functools.partial(nested_template.functions[res_func],
None, res_func)
res_names = [k for k, d in name_def_tuples]
for odefn in self._nested_output_defns(res_names, get_attr):
for odefn in self._nested_output_defns(res_names, get_attr, get_res):
nested_template.add_output(odefn)
return nested_template
@ -159,7 +162,7 @@ class ResourceChain(stack_resource.StackResource):
return [grouputils.get_rsrc_attr(self, key, False, n, *path)
for n in names]
def _nested_output_defns(self, resource_names, get_attr_fn):
def _nested_output_defns(self, resource_names, get_attr_fn, get_res_fn):
for attr in self.referenced_attrs():
if isinstance(attr, six.string_types):
key, path = attr, []
@ -167,21 +170,25 @@ class ResourceChain(stack_resource.StackResource):
else:
key, path = attr[0], list(attr[1:])
output_name = ', '.join(six.text_type(a) for a in attr)
value = None
if key.startswith("resource."):
keycomponents = key.split('.', 2)
res_name = keycomponents[1]
attr_path = keycomponents[2:] + path
if attr_path and (res_name in resource_names):
value = get_attr_fn([res_name] + attr_path)
yield output.OutputDefinition(output_name, value)
if res_name in resource_names:
if attr_path:
value = get_attr_fn([res_name] + attr_path)
else:
value = get_res_fn(res_name)
elif key == self.REFS:
value = [get_res_fn(r) for r in resource_names]
elif key == self.ATTR_ATTRIBUTES and path:
value = {r: get_attr_fn([r] + path) for r in resource_names}
yield output.OutputDefinition(output_name, value)
elif key not in self.ATTRIBUTES:
value = [get_attr_fn([r, key] + path) for r in resource_names]
if value is not None:
yield output.OutputDefinition(output_name, value)
@staticmethod

View File

@ -480,7 +480,7 @@ class ResourceGroup(stack_resource.StackResource):
return [grouputils.get_rsrc_attr(self, key, False, n, *path)
for n in names]
def _nested_output_defns(self, resource_names, get_attr_fn):
def _nested_output_defns(self, resource_names, get_attr_fn, get_res_fn):
for attr in self.referenced_attrs():
if isinstance(attr, six.string_types):
key, path = attr, []
@ -488,21 +488,28 @@ class ResourceGroup(stack_resource.StackResource):
else:
key, path = attr[0], list(attr[1:])
output_name = ', '.join(six.text_type(a) for a in attr)
value = None
if key.startswith("resource."):
keycomponents = key.split('.', 2)
res_name = keycomponents[1]
attr_path = keycomponents[2:] + path
if attr_path and (res_name in resource_names):
value = get_attr_fn([res_name] + attr_path)
yield output.OutputDefinition(output_name, value)
if attr_path:
if res_name in resource_names:
value = get_attr_fn([res_name] + attr_path)
else:
output_name = key = self.REFS_MAP
elif key == self.ATTR_ATTRIBUTES and path:
value = {r: get_attr_fn([r] + path) for r in resource_names}
yield output.OutputDefinition(output_name, value)
elif key not in self.ATTRIBUTES:
value = [get_attr_fn([r, key] + path) for r in resource_names]
if key == self.REFS:
value = [get_res_fn(r) for r in resource_names]
elif key == self.REFS_MAP:
value = {r: get_res_fn(r) for r in resource_names}
if value is not None:
yield output.OutputDefinition(output_name, value)
def build_resource_definition(self, res_name, res_defn):
@ -579,8 +586,10 @@ class ResourceGroup(stack_resource.StackResource):
def _add_output_defns_to_template(self, tmpl, resource_names):
att_func = 'get_attr'
get_attr = functools.partial(tmpl.functions[att_func], None, att_func)
res_func = 'get_resource'
get_res = functools.partial(tmpl.functions[res_func], None, res_func)
for odefn in self._nested_output_defns(resource_names,
get_attr):
get_attr, get_res):
tmpl.add_output(odefn)
def _assemble_nested(self, names, include_all=False,

View File

@ -735,7 +735,7 @@ class SoftwareDeploymentGroup(resource_group.ResourceGroup):
rg_attr = rg.get_attribute(rg.ATTR_ATTRIBUTES, n_attr)
return attributes.select_from_attribute(rg_attr, path)
def _nested_output_defns(self, resource_names, get_attr_fn):
def _nested_output_defns(self, resource_names, get_attr_fn, get_res_fn):
for attr in self.referenced_attrs():
key = attr if isinstance(attr, six.string_types) else attr[0]
n_attr = self._member_attribute_name(key)