def _resolve_deferred_resolvers(self, stack: 'stack.Stack', container: T_Container): def raise_if_not_resolved(attr, key, value): # If this function has been hit, it means that after attempting to resolve all the # ResolveLaters, there STILL are ResolveLaters left in the container. Rather than # continuing to try to resolve (possibly infinitely), we'll raise a RecursiveGet to # break that infinite loop. This situation would happen if a resolver accesses a resolver # in the same container, which then accesses another resolver (possibly the same one) in # the same container. raise RecursiveResolve(f"Resolving Stack.{self.name[1:]} required resolving itself") has_been_resolved_attr_name = f'{self.name}_is_resolved' if not getattr(stack, has_been_resolved_attr_name, False): # We set it first rather than after to avoid entering this block again on this property # for this stack. setattr(stack, has_been_resolved_attr_name, True) _call_func_on_values( lambda attr, key, value: value(), container, self.ResolveLater ) # Search the container to see if there are any ResolveLaters left; # Raise a RecursiveResolve if there are. _call_func_on_values( raise_if_not_resolved, container, self.ResolveLater )
def _extract_parameters_dict(self, stack: Stack) -> dict: """Extracts a USABLE dict of parameters from the stack. Because stack parameters often contain resolvers referencing stacks that might not exist yet (such as when producing a diff on a yet-to-be-deployed stack), we cannot always resolve the stack parameters. Therefore, we need to resolve them (if possible) and fall back to a different representation of that resolver, if necessary. :param stack: The stack to extract the parameters from :return: A dictionary of stack parameters to be compared. """ def resolve_or_replace(attr, key, value: Resolver): try: attr[key] = str(value.resolve()).rstrip('\n') except Exception: attr[key] = self._represent_unresolvable_resolver(value) # parameters is a ResolvableProperty, but the underlying, pre-resolved parameters are # stored in _parameters. We might not actually be able to resolve values, such as in the # case where it attempts to get a value from a stack that doesn't exist yet. unresolved_parameters_dict = stack._parameters _call_func_on_values(resolve_or_replace, unresolved_parameters_dict, Resolver) formatted_parameters = {} for key, value in stack.parameters.items(): if isinstance(value, list): value = ','.join(value) formatted_parameters[key] = value return formatted_parameters
def __set__(self, instance, value): """ Attribute setter which adds a stack reference to any hooks in the data structure `value` and calls the setup method. """ def setup(attr, key, value): value.stack = instance value.setup() _call_func_on_values(setup, value, Hook) setattr(instance, self.name, value)
def get_resolved_value(self, stack: 'stack.Stack', stack_class: Type['stack.Stack']) -> T_Container: """Obtains the resolved value for this property. Any resolvers that resolve to None will have their key/index removed from their dict/list where they are. Other resolvers will have their key/index's value replace with the resolved value to avoid redundant resolutions. :param stack: The Stack instance to obtain the value for :param stack_class: The class of the Stack instance. :return: The fully resolved container. """ keys_to_delete = [] def resolve(attr: Union[dict, list], key: Union[int, str], value: Resolver): # Update the container key's value with the resolved value, if possible... try: result = self.resolve_resolver_value(value) if result is None: self.logger.debug(f"Removing item {key} because resolver returned None.") # We gather up resolvers (and their immediate containers) that resolve to None, # since that really means the resolver resolves to nothing. This is not common, # but should be supported. We gather these rather than immediately remove them # because this function is called in the context of looping over that attr, so # we cannot alter its size until after the loop is complete. keys_to_delete.append((attr, key)) else: attr[key] = result except RecursiveResolve: # It's possible that resolving the resolver might attempt to access another # resolvable property's value in this same container. In this case, we'll delay # resolution and instead return a ResolveLater so the value can be resolved outside # this recursion. attr[key] = self.ResolveLater( stack, self.name, key, lambda: value.resolve(), ) container = getattr(stack, self.name) _call_func_on_values( resolve, container, Resolver ) # Remove keys and indexes from their containers that had resolvers resolve to None. for attr, key in keys_to_delete: del attr[key] return container
def _replace_unresolvable_user_data_resolvers(self, stack: Stack): """Recursively traverses the sceptre_user_data and replaces unresolvable resolvers with placeholder values so that we can continue to render the diff, even if the resolvers can't be resolved right now. :param stack: The stack whose sceptre_user_data cannot be resolved. """ def resolve_or_replace(attr, key, value: Resolver): try: attr[key] = value.resolve() except Exception: explicit_resolver_repr = self._represent_unresolvable_resolver(value) only_alphanumeric = ''.join(c for c in explicit_resolver_repr if c.isalnum()) attr[key] = only_alphanumeric # Because the name is mangled, we cannot access the user data normally, so we need to access # it directly out of the __dict__. user_data = stack.__dict__['__sceptre_user_data'] if not user_data: return _call_func_on_values(resolve_or_replace, user_data, Resolver)
def __get__(self, instance, type): """ Attribute getter which resolves any Resolver object contained in the complex data structure. :return: The attribute stored with the suffix ``name`` in the instance. :rtype: dict or list """ def resolve(attr, key, value): attr[key] = value.resolve() if hasattr(instance, self.name): return _call_func_on_values(resolve, getattr(instance, self.name), Resolver)
def __get__(self, instance, type): """ Attribute getter which resolves any Resolver object contained in the complex data structure. :return: The attribute stored with the suffix ``name`` in the instance. :rtype: dict or list """ with self._no_recursive_get(): def resolve(attr, key, value): try: attr[key] = value.resolve() except RecursiveGet: attr[key] = self.ResolveLater(instance, self.name, key, lambda: value.resolve()) if hasattr(instance, self.name): retval = _call_func_on_values(resolve, getattr(instance, self.name), Resolver) return retval