Exemple #1
0
    def construct_stacks(self):
        """
        Traverses the files under the command path.
        For each file encountered, a Stack is constructed
        using the correct config. Dependencies are traversed
        and a final set of Stacks is returned.

        :returns: A set of Stacks.
        :rtype: set
        """
        stack_map = {}
        command_stacks = set()
        if self.context.ignore_dependencies:
            root = self.context.full_command_path()
        else:
            root = self.context.full_config_path()

        if path.isfile(root):
            todo = {root}
        else:
            todo = set()
            for directory_name, sub_directories, files in walk(root, followlinks=True):
                for filename in fnmatch.filter(files, '*.yaml'):
                    if filename.startswith('config.'):
                        continue

                    todo.add(path.join(directory_name, filename))

        stack_group_configs = {}

        while todo:
            abs_path = todo.pop()
            rel_path = path.relpath(
                abs_path, start=self.context.full_config_path())
            directory, filename = path.split(rel_path)

            if directory in stack_group_configs:
                stack_group_config = stack_group_configs[directory]
            else:
                stack_group_config = stack_group_configs[directory] = \
                    self.read(path.join(directory, self.context.config_file))

            stack = self._construct_stack(rel_path, stack_group_config)
            stack_map[sceptreise_path(rel_path)] = stack

            if abs_path.startswith(self.context.full_command_path()):
                command_stacks.add(stack)

        stacks = set()
        for stack in stack_map.values():
            if not self.context.ignore_dependencies:
                stack.dependencies = [
                    stack_map[sceptreise_path(dep)]
                    for dep in stack.dependencies
                ]
            else:
                stack.dependencies = []
            stacks.add(stack)

        return stacks, command_stacks
Exemple #2
0
    def _construct_stack(self, rel_path, stack_group_config=None):
        """
        Constructs an individual Stack object from a config path and a
        base config.

        :param rel_path: A relative config file path.
        :type rel_path: str
        :param stack_group_config: The Stack group config to use as defaults.
        :type stack_group_config: dict
        :returns: Stack object
        :rtype: sceptre.stack.Stack
        """

        directory, filename = path.split(rel_path)
        if filename == self.context.config_file:
            pass

        self.templating_vars["stack_group_config"] = stack_group_config
        parsed_stack_group_config = self._parsed_stack_group_config(
            stack_group_config)
        config = self.read(rel_path, stack_group_config)
        stack_name = path.splitext(rel_path)[0]

        # Check for missing mandatory attributes
        for required_key in REQUIRED_KEYS:
            if required_key not in config:
                raise InvalidConfigFileError(
                    "Required attribute '{0}' not found in configuration of '{1}'."
                    .format(required_key, stack_name))

        abs_template_path = path.join(self.context.project_path,
                                      self.context.templates_path,
                                      sceptreise_path(config["template_path"]))

        s3_details = self._collect_s3_details(stack_name, config)
        stack = Stack(name=stack_name,
                      project_code=config["project_code"],
                      template_path=abs_template_path,
                      region=config["region"],
                      template_bucket_name=config.get("template_bucket_name"),
                      template_key_prefix=config.get("template_key_prefix"),
                      required_version=config.get("required_version"),
                      iam_role=config.get("iam_role"),
                      profile=config.get("profile"),
                      parameters=config.get("parameters", {}),
                      sceptre_user_data=config.get("sceptre_user_data", {}),
                      hooks=config.get("hooks", {}),
                      s3_details=s3_details,
                      dependencies=config.get("dependencies", []),
                      role_arn=config.get("role_arn"),
                      protected=config.get("protect", False),
                      tags=config.get("stack_tags", {}),
                      external_name=config.get("stack_name"),
                      notifications=config.get("notifications"),
                      on_failure=config.get("on_failure"),
                      stack_timeout=config.get("stack_timeout", 0),
                      stack_group_config=parsed_stack_group_config)

        del self.templating_vars["stack_group_config"]
        return stack
Exemple #3
0
    def _collect_s3_details(stack_name, config):
        """
        Collects and constructs details for where to store the Template in S3.

        :param stack_name: Stack name.
        :type stack_name: str
        :param config: Config with details.
        :type config: dict
        :returns: S3 details.
        :rtype: dict
        """
        s3_details = None
        if "template_bucket_name" in config:
            template_key = "/".join([
                sceptreise_path(stack_name),
                "{time_stamp}.json".format(time_stamp=datetime.datetime.utcnow(
                ).strftime("%Y-%m-%d-%H-%M-%S-%fZ"))
            ])

            bucket_region = config.get("region", None)

            if "template_key_prefix" in config:
                prefix = config["template_key_prefix"]
                template_key = "/".join([prefix.strip("/"), template_key])

            s3_details = {
                "bucket_name": config["template_bucket_name"],
                "bucket_key": template_key,
                "bucket_region": bucket_region
            }
        return s3_details
Exemple #4
0
    def resolve_stacks(self, stack_map):
        """
        Transforms map of Stacks into a set of Stacks, transforms dependencies
        from a list of Strings (stack names) to a list of Stacks.

        :param stack_map: Map of stacks, containing dependencies as list of Strings.
        :type base_config: dict
        :returns: Set of stacks, containing dependencies as list of Stacks.
        :rtype: set
        :raises: sceptre.exceptions.DependencyDoesNotExistError
        """
        stacks = set()
        for stack in stack_map.values():
            if not self.context.ignore_dependencies:
                for i, dep in enumerate(stack.dependencies):
                    try:
                        stack.dependencies[i] = stack_map[sceptreise_path(dep)]
                    except KeyError:
                        raise DependencyDoesNotExistError(
                            "{stackname}: Dependency {dep} not found. "
                            "Valid dependency names are: "
                            "{stackkeys}. "
                            "Please make sure that your dependencies stack_outputs "
                            "have their full path from `config` defined.".
                            format(stackname=stack.name,
                                   dep=dep,
                                   stackkeys=", ".join(stack_map.keys())))

            else:
                stack.dependencies = []
            stacks.add(stack)
        return stacks
Exemple #5
0
    def _collect_s3_details(stack_name, config):
        """
        Collects and constructs details for where to store the Template in S3.

        :param stack_name: Stack name.
        :type stack_name: str
        :param config: Config with details.
        :type config: dict
        :returns: S3 details.
        :rtype: dict
        """
        s3_details = None
        # If the config explicitly sets the template_bucket_name to None, we don't want to enter
        # this conditional block.
        if config.get("template_bucket_name") is not None:
            template_key = "/".join([
                sceptreise_path(stack_name),
                "{time_stamp}.json".format(time_stamp=datetime.datetime.utcnow(
                ).strftime("%Y-%m-%d-%H-%M-%S-%fZ"))
            ])

            if "template_key_prefix" in config:
                prefix = config["template_key_prefix"]
                template_key = "/".join([prefix.strip("/"), template_key])

            s3_details = {
                "bucket_name": config["template_bucket_name"],
                "bucket_key": template_key
            }
        return s3_details
Exemple #6
0
 def _valid_stack_paths(self):
     return [
         sceptreise_path(
             path.relpath(path.join(dirpath, f), self.context.config_path))
         for dirpath, dirnames, files in walk(self.context.config_path)
         for f in files if not f.endswith(self.context.config_file)
     ]
Exemple #7
0
    def _generate_launch_order(self, reverse=False):
        if self.context.ignore_dependencies:
            return [self.command_stacks]

        graph = self.graph.filtered(self.command_stacks, reverse)
        if self.context.ignore_dependencies:
            return [self.command_stacks]

        launch_order = []
        while graph.graph:
            batch = set()
            for stack in graph:
                if graph.count_dependencies(stack) == 0:
                    batch.add(stack)
            launch_order.append(batch)

            for stack in batch:
                graph.remove_stack(stack)

        if not launch_order:
            raise ConfigFileNotFoundError(
                "No stacks detected from the given path '{}'. Valid stack paths are: {}"
                .format(sceptreise_path(self.context.command_path),
                        self._valid_stack_paths()))

        return launch_order
Exemple #8
0
    def __init__(
        self, name, project_code, template_path, region, template_bucket_name=None,
        template_key_prefix=None, required_version=None, parameters=None,
        sceptre_user_data=None, hooks=None, s3_details=None,
        dependencies=None, role_arn=None, protected=False, tags=None,
        external_name=None, notifications=None, on_failure=None, profile=None,
        stack_timeout=0, stack_group_config={}
    ):
        self.logger = logging.getLogger(__name__)

        self.name = sceptreise_path(name)
        self.project_code = project_code
        self.region = region
        self.template_bucket_name = template_bucket_name
        self.template_key_prefix = template_key_prefix
        self.required_version = required_version
        self.external_name = external_name or get_external_stack_name(self.project_code, self.name)

        self.template_path = template_path
        self.s3_details = s3_details
        self._template = None

        self.protected = protected
        self.role_arn = role_arn
        self.on_failure = on_failure
        self.dependencies = dependencies or []
        self.tags = tags or {}
        self.stack_timeout = stack_timeout
        self.profile = profile
        self.hooks = hooks or {}
        self.parameters = parameters or {}
        self.sceptre_user_data = sceptre_user_data or {}
        self.notifications = notifications or []
        self.stack_group_config = stack_group_config or {}
 def setup(self):
     """
     Adds dependency to a Stack.
     """
     dep_stack_name, self.output_key = self.argument.split("::")
     self.dependency_stack_name = sceptreise_path(
         normalise_path(dep_stack_name))
     self.stack.dependencies.append(self.dependency_stack_name)
Exemple #10
0
def get_stack_names(context, stack_group_name):
    config_dir = Path(context.sceptre_dir) / 'config'
    path = config_dir / stack_group_name

    stack_names = []

    for child in path.rglob('*'):
        if child.is_dir() or child.stem == 'config':
            continue

        relative_path = child.relative_to(config_dir)
        stack_name = sceptreise_path(
            str(relative_path).replace(child.suffix, ''))
        stack_names.append(stack_name)

    return stack_names
Exemple #11
0
    def __init__(
        self, name, project_code, region, template_path=None, template_handler_config=None,
        template_bucket_name=None, template_key_prefix=None, required_version=None,
        parameters=None, sceptre_user_data=None, hooks=None, s3_details=None,
        iam_role=None, dependencies=None, role_arn=None, protected=False, tags=None,
        external_name=None, notifications=None, on_failure=None, profile=None,
        stack_timeout=0, stack_group_config={}
    ):
        self.logger = logging.getLogger(__name__)

        if template_path and template_handler_config:
            raise InvalidConfigFileError("Both 'template_path' and 'template' are set, specify one or the other")

        if not template_path and not template_handler_config:
            raise InvalidConfigFileError("Neither 'template_path' nor 'template' is set")

        self.name = sceptreise_path(name)
        self.project_code = project_code
        self.region = region
        self.template_bucket_name = template_bucket_name
        self.template_key_prefix = template_key_prefix
        self.required_version = required_version
        self.external_name = external_name or get_external_stack_name(self.project_code, self.name)
        self.template_path = template_path
        self.template_handler_config = template_handler_config
        self.s3_details = s3_details
        self._template = None
        self._connection_manager = None

        self.protected = protected
        self.role_arn = role_arn
        self.on_failure = on_failure
        self.dependencies = dependencies or []
        self.tags = tags or {}
        self.stack_timeout = stack_timeout
        self.iam_role = iam_role
        self.profile = profile
        self.hooks = hooks or {}
        self.parameters = parameters or {}
        self._sceptre_user_data = sceptre_user_data or {}
        self._sceptre_user_data_is_resolved = False
        self.notifications = notifications or []
        self.stack_group_config = stack_group_config or {}
Exemple #12
0
    def construct_stacks(self) -> Set[Stack]:
        """
        Traverses the files under the command path.
        For each file encountered, a Stack is constructed
        using the correct config. Dependencies are traversed
        and a final set of Stacks is returned.

        :returns: A set of Stacks.
        """
        stack_map = {}
        command_stacks = set()

        root = self.context.full_command_path()

        if self.context.full_scan:
            root = self.context.full_config_path()

        if path.isfile(root):
            todo = {root}
        else:
            todo = set()
            for directory_name, sub_directories, files in walk(
                    root, followlinks=True):
                for filename in fnmatch.filter(files, '*.yaml'):
                    if filename.startswith('config.'):
                        continue

                    todo.add(path.join(directory_name, filename))

        stack_group_configs = {}
        full_todo = todo.copy()
        deps_todo = set()

        while todo:
            abs_path = todo.pop()
            rel_path = path.relpath(abs_path,
                                    start=self.context.full_config_path())
            directory, filename = path.split(rel_path)

            if directory in stack_group_configs:
                stack_group_config = stack_group_configs[directory]
            else:
                stack_group_config = stack_group_configs[directory] = \
                    self.read(path.join(directory, self.context.config_file))

            stack = self._construct_stack(rel_path, stack_group_config)
            for dep in stack.dependencies:
                full_dep = str(Path(self.context.full_config_path(), dep))
                if not path.exists(full_dep):
                    raise DependencyDoesNotExistError(
                        "{stackname}: Dependency {dep} not found. "
                        "Please make sure that your dependencies stack_outputs "
                        "have their full path from `config` defined.".format(
                            stackname=stack.name, dep=dep))

                if full_dep not in full_todo and full_dep not in deps_todo:
                    todo.add(full_dep)
                    deps_todo.add(full_dep)

            stack_map[sceptreise_path(rel_path)] = stack

            full_command_path = self.context.full_command_path()
            if abs_path == full_command_path\
                    or abs_path.startswith(full_command_path.rstrip(path.sep) + path.sep):
                command_stacks.add(stack)

        stacks = self.resolve_stacks(stack_map)

        return stacks, command_stacks
Exemple #13
0
 def test_sceptreise_path_with_trailing_backslash(self):
     with pytest.raises(PathConversionError):
         sceptreise_path(
             'this\path\is\invalid\\'
         )
Exemple #14
0
 def test_sceptreise_path_with_trailing_slash(self):
     with pytest.raises(PathConversionError):
         sceptreise_path(
             "this/path/is/invalid/"
         )
Exemple #15
0
 def test_sceptreise_path_with_windows_path(self):
     windows_path = 'dev\\app\\stack'
     assert sceptreise_path(windows_path) == 'dev/app/stack'
Exemple #16
0
 def test_sceptreise_path_with_valid_path(self):
     path = 'dev/app/stack'
     assert sceptreise_path(path) == path