예제 #1
0
    def run(self):

        # Ensure we have an index specified
        index = Stack.get_config("index")
        if not index:
            logger.error("Stack package index not specified, cannot proceed")
            exit(1)

        # Get the package, if specified
        if self.options["<package>"]:
            packages = [self.options["<package>"]]
        else:
            packages = [package["name"] for package in Stack.get_config("packages")]

        # Process each package
        for package in packages:

            # Update the package
            if self.update(package):
                logger.info("Package '{}' was updated successfully".format(package))

                # Update services
                self.update_apps(package)

            else:
                logger.error("Error: Could not update package")
                exit(1)
예제 #2
0
def main():
    """Main CLI entrypoint."""
    import os
    from dbmisvc_stack import commands
    from dbmisvc_stack.commands.base import Base

    options = docopt(__doc__, version=VERSION)

    # Setup logging.
    logger = setup_logger(options)

    # Make sure we are in a valid location
    if not Stack.check_stack(os.getcwd()):
        logger.critical("The Stack is invalid, cannot run...")
        return

    # Here we'll try to dynamically match the command the user is trying to run
    # with a pre-defined command class we've already created.
    for (k, v) in options.items():
        if hasattr(commands, k) and v:
            module = getattr(commands, k)
            _commands = getmembers(
                module, lambda cmd: isclass(cmd) and issubclass(cmd, Base))
            command = [
                command[1] for command in _commands if command[0] != "Base"
            ][0]
            command = command(options)
            command.run()
예제 #3
0
    def run(self):

        # Check for time constraints.
        if self.options["--minutes"]:

            # Build the command.
            command = [
                "docker",
                "logs",
                "-t",
                "--since",
                "{}m".format(self.options["--minutes"]),
            ]

            # Check for follow
            if self.options["--follow"]:
                command.append("-f")

            # Add the app.
            container = App.get_container_name(self.options["<app>"])
            command.append(container)

            # Capture and redirect output.
            Stack.run(command)

        else:

            # Build the command.
            command = ["docker-compose", "logs", "-t"]

            # Check for lines.
            if self.options["--lines"]:
                command.extend(["--tail", self.options["--lines"]])

            # Check for follow
            if self.options["--follow"]:
                command.append("-f")

            # Add the app.
            command.append(self.options["<app>"])

            # Capture and redirect output.
            Stack.run(command)
예제 #4
0
    def run(self):

        # Build the command.
        command = ["docker-compose", "down"]

        # Check for flags
        if self.options.get("<flags>"):

            # Split them, append the '--' and add them to the command
            for flag in self.options.get("<flags>").split(","):
                command.append("-{}".format(flag) if len(flag) ==
                               1 else "--{}".format(flag))

        # Check for cleaning up
        if (self.options["--clean"] and "-v" not in command
                and "--volumes" not in command):
            command.append("--volumes")

        # Capture and redirect output.
        logger.debug("(stack) Running docker-compose down...")
        Stack.run(command)
예제 #5
0
    def update(package):

        try:
            # Get devpi index details
            port = App.get_external_port("devpi", "3141")
            index_url = "http://localhost:{}/root/public/".format(port)

            # Get the description
            config = App.get_packages_stack_config(package)

            # Get password
            password = App.get_config("devpi", "environment").get("DEVPI_PASSWORD")

            # Get the location
            path = os.path.abspath(config["path"])
            dists = os.path.join(path, "dist", "*")

            # Build the package
            Stack.run(config.get("build"), cwd=path)

            # Upload
            cmd = [
                "twine",
                "upload",
                dists,
                "--repository-url",
                index_url,
                "--skip-existing",
                "-u",
                "root",
                "-p",
                password,
            ]

            # Run it and make sure it was successful
            return Stack.run(cmd) == 0

        except Exception as e:
            logger.exception("Error updating package: {}".format(e), exc_info=True)
            return False
예제 #6
0
    def run(self):

        # Get the app.
        app = self.options["<app>"]
        branch = self.options["<branch>"]

        # Get the repo URL
        repo_url = App.get_repo_url(app)
        if repo_url is None:
            logger.error("({}) No repository URL specified...".format(app))
            return

        # Determine the path to the app directory
        apps_dir = os.path.relpath(Stack.get_config("apps-directory"))
        subdir = os.path.join(apps_dir, app)

        # Ensure it exists.
        if not os.path.exists(subdir):
            logger.error(
                '({}) No repository at {}, run "stack clone" command first'.
                format(app))
            return

        # Build the command
        command = [
            "git",
            "subtree",
            "pull",
            "--prefix={}".format(subdir),
            repo_url,
            branch,
        ]

        # Check for a squash.
        if self.options.get("--squash"):
            command.append("--squash")

        # Run the command.
        Stack.run(command)
예제 #7
0
    def run(self):

        # Get a docker client.
        docker_client = docker.from_env()

        # Check all the build parameters.
        apps = App.get_apps()
        for app in apps:

            # Check images.
            if not App.check_docker_images(docker_client, app):
                logger.error("({}) Container image does not exist, build and"
                             " try again...".format(app))
                return

            # Ensure it is running.
            if not App.check_running(docker_client, app):
                logger.error(
                    "({}) Container is not running, ensure all containers"
                    " are started...".format(app))
                return

        # Capture and redirect output.
        Stack.run(["nosetests", "-s", "-v"])
예제 #8
0
    def run(self):

        # Get the docker client.
        docker_client = docker.from_env()

        # Check it.
        if not App.check(docker_client):
            logger.critical(
                "Stack is invalid! Ensure all paths and images are correct"
                " and try again")
            return

        # Check for clean.
        if self.options["--clean"]:

            App.clean_images(docker_client)

        # Iterate through built apps
        for app in App.get_built_apps():
            App.build(app)

        # Build the command.
        command = ["docker-compose", "up"]

        # Check for the daemon flag.
        if self.options["-d"]:
            command.append("-d")

        # Check for flags
        if self.options.get("<flags>"):

            # Split them, append the '--' and add them to the command
            for flag in self.options.get("<flags>").split(","):
                command.append("-{}".format(flag) if len(flag) ==
                               1 else "--{}".format(flag))

        # Run the pre-build hook, if any
        Stack.hook("pre-up")

        # Capture and redirect output.
        logger.debug("(stack) Running docker-compose up...")

        Stack.run(command)

        # Run the pre-build hook, if any
        Stack.hook("post-up")
예제 #9
0
    def run(self):

        # Get the app.
        app = self.options["<app>"]
        branch = self.options["<branch>"]

        # Get the repo URL
        repo_url = App.get_repo_url(app)
        if repo_url is None:
            logger.error("({}) No repository URL specified...".format(app))
            return

        # Determine the path to the app directory
        apps_dir = os.path.relpath(Stack.get_config("apps-directory"))
        subdir = os.path.join(apps_dir, app)

        # Ensure it exists.
        if os.path.exists(subdir):
            logger.error(
                "({}) A repository already exists, use 'stack checkout' to"
                " change branches".format(app)
            )
            return

        # Build the command
        command = [
            "git",
            "subtree",
            "add",
            "--prefix={}".format(subdir),
            repo_url,
            branch,
            "--squash",
        ]

        # Check for pre-clone hook
        Stack.hook("pre-clone", app, [os.path.realpath(subdir)])

        # Run the command.
        return_code = Stack.run(command)

        # Check for post-clone hook
        if return_code == 0:
            Stack.hook("post-clone", app, [os.path.realpath(subdir)])
예제 #10
0
    def run(self):

        # Get a docker client.
        docker_client = docker.from_env()

        # Get options.
        clean = self.options["--clean"]
        app = self.options["<app>"]

        # Check for stack or app
        if app:

            # Check for clean.
            if clean:

                # Clean and fetch.
                App.clean_images(docker_client, app)

                # Build it.
                App.build(app)

            # Capture and redirect output.
            Stack.run(["docker-compose", "kill", app])
            Stack.run(["docker-compose", "rm", "-f", "-v", app])

            # Run the pre-up hook, if any
            Stack.hook("pre-up", app)

            # Build the  up command
            up = ["docker-compose", "up", "--no-start"]

            # Check for purge
            if self.options["--purge"]:

                # Confirm
                if self.yes_no("This will remove all app data, continue?"):
                    logger.warning("({}) Database will be purged!".format(app))

                    # Process it
                    App.purge_data(docker_client, app)
            else:
                logger.info("({}) Database will not be purged".format(app))

            # Check for recreate flag
            if not self.options.get("--recreate"):
                up.append("--no-recreate")

            # Check for flags
            if self.options.get("--flags"):

                # Split them
                flags = self.options.get("--flags").split(",")

                # Don't add default options twice
                for flag in [f for f in flags if f in up]:
                    flags.remove(flag)
                    logger.debug(
                        "(stack) Removing double option: '{}'".format(flag))

                # Split them, append the '--' and add them to the command
                for flag in flags:
                    up.append("-{}".format(flag) if len(flag) ==
                              1 else "--{}".format(flag))

            # Add the app
            up.append(app)

            logger.debug("(stack) Running command: '{}'".format(up))
            Stack.run(up)

            # Run it
            run_cmd = ["docker-compose", "start", app]
            logger.debug("(stack) Running command: '{}'".format(run_cmd))
            Stack.run(run_cmd)

            # Run the post-up hook, if any
            Stack.hook("post-up", app)

        else:

            # Check for clean.
            if clean and self.yes_no("Clean: Rebuild all app images?"):

                # Clean and fetch.
                for app in App.get_apps():
                    if self.yes_no("({}) Rebuild app image?"):
                        logger.info("({}) Rebuilding image...".format(app))

                        # Rebuild images
                        App.clean_images(docker_client, app)

            # Build and run stack down
            down_command = ["stack", "down"]
            if clean:
                down_command.append("--clean")

            Stack.run(down_command)

            # Run the pre-up hook, if any
            Stack.hook("pre-up")

            # Build and run stack up
            up_command = ["stack", "up"]
            if self.options["-d"]:
                up_command.append("-d")

            Stack.run(up_command)

            # Run the pre-up hook, if any
            Stack.hook("post-up")
예제 #11
0
    def run(self):

        # Create a list of apps to update
        apps = App.get_apps()

        # Get the app.
        app = self.options.get("<app>")
        if app:
            apps = [app]

        # Filter out apps without repository details
        apps = [app for app in apps if App.get_repo_branch(app)]

        # Ensure no local changes
        if Stack.run(
            ["git", "diff-index", "--name-status", "--exit-code", "HEAD"]):
            logger.error(
                "Current working copy has changes, cannot update app repositories"
            )
            exit(1)

        logger.info("Will update {}".format(", ".join(apps)))

        # Iterate and update
        for app in apps:

            # Get the repo URL
            repo_url = App.get_repo_url(app)
            branch = App.get_repo_branch(app)
            if repo_url is None or branch is None:
                logger.error(
                    "({}) No repository URL and/or branch specified...".format(
                        app))
                continue

            # Determine the path to the app directory
            apps_dir = os.path.relpath(Stack.get_config("apps-directory"))
            subdir = os.path.join(apps_dir, app)

            # Check for pre-checkout hook
            Stack.hook("pre-checkout", app, [os.path.realpath(subdir)])

            # Build the command
            command = [
                "git",
                "subtree",
                "add",
                "--prefix={}".format(subdir),
                repo_url,
                branch,
                "--squash",
            ]

            # Remove the current subtree.
            Stack.run(["git", "rm", "-rf", subdir])
            Stack.run(["rm", "-rf", subdir])
            Stack.run([
                "git",
                "commit",
                "-m",
                '"Stack op: Removing subtree {} for cloning branch {}"'.format(
                    app, branch),
            ])

            # Run the command.
            Stack.run(command)

            # Check for post-checkout hook
            Stack.hook("post-checkout", app, [os.path.realpath(subdir)])
예제 #12
0
    def update_apps(package):

        # Find all services listing this package
        for app, config in Stack.get_config("apps").items():

            try:
                # Check packages
                if config.get("packages") and package in config.get("packages"):

                    # Get the docker client.
                    docker_client = docker.from_env()

                    # Determine the app.
                    if App.check_running(docker_client, app):
                        logger.info(
                            "App '{}' depends on '{}', reinstalling...".format(
                                app, package
                            )
                        )

                        # Build the uninstall command
                        uninstall = [
                            "docker-compose",
                            "exec",
                            app,
                            "pip",
                            "uninstall",
                            "-y",
                            package,
                        ]

                        # Execute a shell.
                        code = Stack.run(uninstall)
                        if code == 0:
                            logger.info("    ... uninstalled ...")

                            # Build the install command
                            install = [
                                "docker-compose",
                                "exec",
                                app,
                                "pip",
                                "install",
                                package,
                            ]

                            # Execute a shell.
                            code = Stack.run(install)
                            if code == 0:
                                logger.info("        ... reinstall succeeded!")

                            else:
                                logger.error(
                                    "    .... failed with exit code: {}".format(code)
                                )

                        else:
                            logger.error(
                                "    .... failed with exit code: {}".format(code)
                            )

                    else:
                        logger.error(
                            "    .... App {} is not running, cannot update".format(app)
                        )

            except Exception as e:
                logger.exception(
                    "Error reinstalling package for {}: {}".format(app, e),
                    exc_info=True,
                )
예제 #13
0
    def run(self):

        # Get name of secret
        secret_name = Stack.get_secrets_config("name")
        try:
            # Get the secrets manager client.
            session = boto3.session.Session(
                profile_name=Stack.get_secrets_config("profile"))
            client = session.client(
                "secretsmanager",
                region_name=Stack.get_secrets_config("region"),
            )

            # Get the secrets
            response = client.get_secret_value(SecretId=secret_name)

            # Get values of secrets
            if "SecretString" in response:
                secrets = json.loads(response["SecretString"])
            else:
                secrets = base64.b64decode(response["SecretBinary"])

            # Check if file exists
            path = os.path.join(Stack.get_stack_root(), ".env")
            if os.path.exists(path):

                # Prompt to overwrite
                if not self.options["--force"]:
                    logger.error(
                        "(secrets) The .env secrets file already exists."
                        ' Run with "-f" to overwrite.')
                    exit(0)

            # Determine how to write
            if type(secrets) is dict:
                with open(path, "w") as f:
                    f.write(headers + "\n")
                    for key, value in secrets.items():
                        if type(value) is str:
                            f.write("{}={}\n".format(key.upper(), value))
                        else:
                            f.write("{}={}\n".format(key.upper(),
                                                     json.dumps(value)))

            else:
                with open(path, "w+b") as f:
                    f.write(secrets)

            logger.info(
                "(secrets) Fetched and saved secrets to '{}'".format(path))

        except ClientError as e:
            if e.response["Error"]["Code"] == "DecryptionFailureException":
                # Secrets Manager can't decrypt the protected secret text using
                # the provided KMS key.
                # Deal with the exception here, and/or rethrow at your discretion.
                raise e
            elif e.response["Error"][
                    "Code"] == "InternalServiceErrorException":
                # An error occurred on the server side.
                # Deal with the exception here, and/or rethrow at your discretion.
                raise e
            elif e.response["Error"]["Code"] == "InvalidParameterException":
                # You provided an invalid value for a parameter.
                # Deal with the exception here, and/or rethrow at your discretion.
                raise e
            elif e.response["Error"]["Code"] == "InvalidRequestException":
                # You provided a parameter value that is not valid for the current
                # state of the resource.
                # Deal with the exception here, and/or rethrow at your discretion.
                raise e
            elif e.response["Error"]["Code"] == "ResourceNotFoundException":
                # We can't find the resource that you asked for.
                # Deal with the exception here, and/or rethrow at your discretion.
                logger.error(
                    "(secrets) Error: No secret could be found for name '{}'".
                    format(secret_name))

        except Exception as e:
            logger.exception("Secrets error: {}".format(e))
            exit(1)
예제 #14
0
#!/usr/bin/env python3
# coding: utf-8

import os

from dbmisvc_stack.app import Stack

import logging

logger = logging.getLogger("stack")

# Get the secrets file
path = os.path.join(Stack.get_stack_root(), ".env")
예제 #15
0
    def run(self):

        # Get the app.
        app = self.options["<app>"]
        branch = self.options["<branch>"]

        # Get the repo URL
        repo_url = App.get_repo_url(app)
        if repo_url is None:
            logger.error("({}) No repository URL specified...".format(app))
            return

        # Determine the path to the app directory
        apps_dir = os.path.relpath(Stack.get_config("apps-directory"))
        subdir = os.path.join(apps_dir, app)

        # Ensure no local changes
        if Stack.run(["git", "diff-index", "--name-status", "--exit-code", "HEAD"]):
            logger.error(
                "Current working copy has changes, cannot update app repositories"
            )
            exit(1)

        # Check if new branch.
        if self.options["-b"]:

            # Ensure it exists.
            if not os.path.exists(subdir):
                logger.error(
                    "({}) This repository does not exist yet, run"
                    " 'stack clone' command first".format(app, subdir)
                )
                return

            # Check for pre-checkout hook
            Stack.hook("pre-checkout", app, [os.path.realpath(subdir)])

            # Do a split.
            command = [
                "git",
                "subtree",
                "split",
                "--prefix={}".format(subdir),
                "--branch",
                branch,
            ]

            Stack.run(command)

        else:

            # Check for pre-checkout hook
            Stack.hook("pre-checkout", app, [os.path.realpath(subdir)])

            # Build the command
            command = [
                "git",
                "subtree",
                "add",
                "--prefix={}".format(subdir),
                repo_url,
                branch,
                "--squash",
            ]

            # Remove the current subtree.
            Stack.run(["git", "rm", "-rf", subdir])
            Stack.run(["rm", "-rf", subdir])
            Stack.run(
                [
                    "git",
                    "commit",
                    "-m",
                    '"Stack op: Removing subtree {} for cloning branch {}"'.format(
                        app, branch
                    ),
                ]
            )

            # Run the command.
            Stack.run(command)

        # Check for post-checkout hook
        Stack.hook("post-checkout", app, [os.path.realpath(subdir)])