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)
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()
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)
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)
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
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)
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"])
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")
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)])
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")
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)])
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, )
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)
#!/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")
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)])