Example #1
0
    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
            )
Example #2
0
    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
Example #3
0
    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)
Example #4
0
    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
Example #5
0
    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)
Example #6
0
    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)
Example #7
0
    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