示例#1
0
def k8s_create(context, tag='latest', namespace=None, yaml_file_name=None):
    if yaml_file_name is None: yaml_file_name = context
    kubernetes_yml = os.path.join(os.getcwd(),
                                  "hokusai/%s.yml" % yaml_file_name)
    if not os.path.isfile(kubernetes_yml):
        raise HokusaiError("Yaml file %s does not exist." % kubernetes_yml)

    ecr = ECR()
    if not ecr.project_repo_exists():
        raise HokusaiError(
            "ECR repository %s does not exist... did you run `hokusai setup` for this project?"
            % config.project_name)

    if not ecr.tag_exists(tag):
        raise HokusaiError(
            "Image tag %s does not exist... did you run `hokusai push`?" % tag)

    if tag is 'latest' and not ecr.tag_exists(context):
        ecr.retag(tag, context)
        print_green("Updated tag 'latest' -> %s" % context)

    kctl = Kubectl(context, namespace=namespace)
    shout(kctl.command("create --save-config -f %s" % kubernetes_yml),
          print_output=True)
    print_green("Created Kubernetes environment %s" % kubernetes_yml)
示例#2
0
def k8s_create(context, tag='latest', namespace=None, filename=None):
    if filename is None:
        kubernetes_yml = os.path.join(CWD, HOKUSAI_CONFIG_DIR,
                                      "%s.yml" % context)
    else:
        kubernetes_yml = filename

    if not os.path.isfile(kubernetes_yml):
        raise HokusaiError("Yaml file %s does not exist." % kubernetes_yml)

    ecr = ECR()
    if not ecr.project_repo_exists():
        raise HokusaiError(
            "ECR repository %s does not exist... did you run `hokusai setup` for this project?"
            % config.project_name)

    if not ecr.tag_exists(tag):
        raise HokusaiError(
            "Image tag %s does not exist... did you run `hokusai registry push`?"
            % tag)

    if tag is 'latest' and not ecr.tag_exists(context):
        ecr.retag(tag, context)
        print_green("Updated tag 'latest' -> %s" % context)

    if filename is None:
        configmap = ConfigMap(context, namespace=namespace)
        configmap.create()
        print_green("Created configmap %s-environment" % config.project_name)

    kctl = Kubectl(context, namespace=namespace)
    shout(kctl.command("create --save-config -f %s" % kubernetes_yml),
          print_output=True)
    print_green("Created Kubernetes environment %s" % kubernetes_yml)
示例#3
0
def k8s_create(context, tag='latest', namespace=None, filename=None, environment=()):
  if filename is None:
    yaml_template = TemplateSelector().get(os.path.join(CWD, HOKUSAI_CONFIG_DIR, context))
  else:
    yaml_template = TemplateSelector().get(filename)

  ecr = ECR()
  if not ecr.project_repo_exists():
    raise HokusaiError("ECR repository %s does not exist... did you run `hokusai setup` for this project?" % config.project_name)

  if not ecr.tag_exists(tag):
    raise HokusaiError("Image tag %s does not exist... did you run `hokusai registry push`?" % tag)

  if tag == 'latest' and not ecr.tag_exists(context):
    ecr.retag(tag, context)
    print_green("Updated tag 'latest' -> %s" % context)

  if filename is None:
    configmap = ConfigMap(context, namespace=namespace)
    for s in environment:
      if '=' not in s:
        raise HokusaiError("Error: environment variables must be of the form 'KEY=VALUE'")
      split = s.split('=', 1)
      configmap.update(split[0], split[1])
    configmap.create()
    print_green("Created configmap %s-environment" % config.project_name)

  kctl = Kubectl(context, namespace=namespace)
  yaml_spec = YamlSpec(yaml_template).to_file()

  shout(kctl.command("create --save-config -f %s" % yaml_spec), print_output=True)
  print_green("Created Kubernetes environment %s" % yaml_template)
示例#4
0
def push(tag, build, force, overwrite, skip_latest=False):
  if force is None and shout('git status --porcelain'):
    raise HokusaiError("Working directory is not clean.  Aborting.")

  if force is None and shout('git status --porcelain --ignored'):
    raise HokusaiError("Working directory contains ignored files and/or directories.  Aborting.")

  ecr = ECR()
  if not ecr.project_repo_exists():
    raise HokusaiError("ECR repo %s does not exist... did you run `hokusai setup` for this project?" % config.project_name)

  shout(ecr.get_login())
  if tag is None:
    tag = shout('git rev-parse HEAD').strip()

  if overwrite is None and ecr.tag_exists(tag):
    raise HokusaiError("Tag %s already exists in registry.  Aborting." % tag)

  if build:
    Docker().build()

  build_tag = "hokusai_%s:latest" % config.project_name

  shout("docker tag %s %s:%s" % (build_tag, ecr.project_repo, tag))
  shout("docker push %s:%s" % (ecr.project_repo, tag), print_output=True)
  print_green("Pushed %s to %s:%s" % (build_tag, ecr.project_repo, tag), newline_after=True)

  if skip_latest: return

  shout("docker tag %s %s:%s" % (build_tag, ecr.project_repo, 'latest'))
  shout("docker push %s:%s" % (ecr.project_repo, 'latest'), print_output=True)
  print_green("Pushed %s to %s:%s" % (build_tag, ecr.project_repo, 'latest'), newline_after=True)
示例#5
0
class CommandRunner(object):
  def __init__(self, context, namespace=None):
    self.context = context
    self.kctl = Kubectl(self.context, namespace=namespace)
    self.ecr = ECR()

  def run(self, image_tag, cmd, tty=None, env=(), constraint=()):
    if not self.ecr.project_repo_exists():
      raise HokusaiError("Project repo does not exist.  Aborting.")

    if os.environ.get('USER') is not None:
      uuid = "%s-%s" % (os.environ.get('USER'), k8s_uuid())
    else:
      uuid = k8s_uuid()

    name = "%s-hokusai-run-%s" % (config.project_name, uuid)
    image_name = "%s:%s" % (self.ecr.project_repo, image_tag)
    container = {
      "args": cmd.split(' '),
      "name": name,
      "image": image_name,
      "imagePullPolicy": "Always",
      'envFrom': [{'configMapRef': {'name': "%s-environment" % config.project_name}}]
    }

    run_tty = tty if tty is not None else config.run_tty
    if run_tty:
      container.update({
        "stdin": True,
        "stdinOnce": True,
        "tty": True
      })

    if env:
      container['env'] = []
      for s in env:
        if '=' not in s:
          raise HokusaiError("Error: environment variables must be of the form 'KEY=VALUE'")
        split = s.split('=', 1)
        container['env'].append({'name': split[0], 'value': split[1]})

    spec = { "containers": [container] }
    constraints = constraint or config.run_constraints
    if constraints:
      spec['nodeSelector'] = {}
      for label in constraints:
        if '=' not in label:
          raise HokusaiError("Error: Node selectors must of the form 'key=value'")
        split = label.split('=', 1)
        spec['nodeSelector'][split[0]] = split[1]

    overrides = { "apiVersion": "v1", "spec": spec }

    if run_tty:
      shout(self.kctl.command("run %s -t -i --image=%s --restart=Never --overrides=%s --rm" %
                     (name, image_name, pipes.quote(json.dumps(overrides)))), print_output=True)
    else:
      return returncode(self.kctl.command("run %s --attach --image=%s --overrides=%s --restart=Never --rm" %
                                        (name, image_name, pipes.quote(json.dumps(overrides)))))
示例#6
0
def push(tag, build, force, overwrite, skip_latest=False):
    if force is None and shout('git status --porcelain'):
        raise HokusaiError("Working directory is not clean.  Aborting.")

    if force is None and shout('git status --porcelain --ignored'):
        raise HokusaiError(
            "Working directory contains ignored files and/or directories.  Aborting."
        )

    ecr = ECR()
    if not ecr.project_repo_exists():
        raise HokusaiError(
            "ECR repo %s does not exist... did you run `hokusai setup` for this project?"
            % config.project_name)

    shout(ecr.get_login())
    if tag is None:
        tag = shout('git rev-parse HEAD').strip()

    if overwrite is None and ecr.tag_exists(tag):
        raise HokusaiError("Tag %s already exists in registry.  Aborting." %
                           tag)

    if build:
        docker_compose_yml = os.path.join(os.getcwd(), 'hokusai/build.yml')
        legacy_docker_compose_yml = os.path.join(os.getcwd(),
                                                 'hokusai/common.yml')
        if not os.path.isfile(docker_compose_yml) and not os.path.isfile(
                legacy_docker_compose_yml):
            raise HokusaiError("Yaml files %s / %s do not exist." %
                               (docker_compose_yml, legacy_docker_compose_yml))
        if os.path.isfile(docker_compose_yml):
            shout("docker-compose -f %s -p hokusai build" % docker_compose_yml,
                  print_output=True)
        if os.path.isfile(legacy_docker_compose_yml):
            shout("docker-compose -f %s -p hokusai build" %
                  legacy_docker_compose_yml,
                  print_output=True)

    build_tag = "hokusai_%s:latest" % config.project_name

    shout("docker tag %s %s:%s" % (build_tag, ecr.project_repo, tag))
    shout("docker push %s:%s" % (ecr.project_repo, tag), print_output=True)
    print_green("Pushed %s to %s:%s" % (build_tag, ecr.project_repo, tag))

    if skip_latest: return

    shout("docker tag %s %s:%s" % (build_tag, ecr.project_repo, 'latest'))
    shout("docker push %s:%s" % (ecr.project_repo, 'latest'),
          print_output=True)
    print_green("Pushed %s to %s:%s" % (build_tag, ecr.project_repo, 'latest'))
示例#7
0
文件: retag.py 项目: artsy/hokusai
def retag(tag_to_change, tag_to_match):
    ecr = ECR()

    if not ecr.project_repo_exists():
        raise HokusaiError("Project repo does not exist. Aborting.")

    try:
        ecr.retag(tag_to_match, tag_to_change)
        print_green(
            "Updated ECR '%s' tag to point to the image that '%s' tag points to."
            % (tag_to_change, tag_to_match),
            newline_after=True)
    except (ValueError, ClientError) as e:
        raise HokusaiError("Updating ECR tag failed due to the error: '%s'" %
                           str(e))
示例#8
0
文件: pull.py 项目: artsy/hokusai
def pull(tag, local_tag):
    ecr = ECR()
    if not ecr.project_repo_exists():
        raise HokusaiError(
            "ECR repo %s does not exist... did you run `hokusai setup` for this project?"
            % config.project_name)

    shout(ecr.get_login(),
          mask=(r'^(docker login -u) .+ (-p) .+ (.+)$',
                r'\1 ****** \2 ***** \3'))

    shout("docker pull %s:%s" % (ecr.project_repo, tag))

    shout("docker tag %s:%s hokusai_%s:%s" %
          (ecr.project_repo, tag, config.project_name, local_tag))

    print_green("Pulled %s:%s to hokusai_%s:%s" %
                (ecr.project_repo, tag, config.project_name, local_tag),
                newline_after=True)
示例#9
0
def check():
  return_code = 0

  def check_ok(check_item):
    print_green(u'\u2714 ' + check_item + ' found')

  def check_err(check_item):
    print_red(u'\u2718 ' + check_item + ' not found')

  config.check()

  try:
    config.project_name
    check_ok('Config project-name')
  except HokusaiError:
    check_err('Config project-name')

  try:
    shout('docker --version')
    check_ok('docker')
  except CalledProcessError:
    check_err('docker')
    return_code += 1

  try:
    shout('docker-compose --version')
    check_ok('docker-compose')
  except CalledProcessError:
    check_err('docker-compose')
    return_code += 1

  try:
    shout('kubectl version')
    check_ok('kubectl')
  except CalledProcessError:
    check_err('kubectl')
    return_code += 1

  try:
    shout('git version')
    check_ok('git')
  except CalledProcessError:
    check_err('git')
    return_code += 1

  if os.environ.get('AWS_ACCESS_KEY_ID') is not None:
    check_ok('$AWS_ACCESS_KEY_ID')
  else:
    check_err('$AWS_ACCESS_KEY_ID')
    return_code += 1

  if os.environ.get('AWS_SECRET_ACCESS_KEY') is not None:
    check_ok('$AWS_SECRET_ACCESS_KEY')
  else:
    check_err('$AWS_SECRET_ACCESS_KEY')
    return_code += 1

  ecr = ECR()
  if ecr.project_repo_exists():
    check_ok("ECR repository '%s'" % config.project_name)
  else:
    check_err("ECR repository '%s'" % config.project_name)
    return_code += 1

  if not os.path.isfile(os.path.join(os.getcwd(), 'hokusai/build.yml')):
    if os.path.isfile(os.path.join(os.getcwd(), 'hokusai/common.yml')):
      check_ok('./hokusai/common.yml')
    else:
      check_err('./hokusai/build.yml')
  else:
    check_ok('./hokusai/build.yml')
    return_code += 1

  if os.path.isfile(os.path.join(os.getcwd(), 'hokusai/development.yml')):
    check_ok('./hokusai/development.yml')
  else:
    check_err('./hokusai/development.yml')
    return_code += 1

  if os.path.isfile(os.path.join(os.getcwd(), 'hokusai/test.yml')):
    check_ok('./hokusai/test.yml')
  else:
    check_err('./hokusai/test.yml')
    return_code += 1

  for context in ['staging', 'production']:
    if context in Kubectl('staging').contexts():
      check_ok("kubectl context '%s'" % context)
    else:
      check_err("kubectl context '%s'" % context)
      return_code += 1

    if os.path.isfile(os.path.join(os.getcwd(), "hokusai/%s.yml" % context)):
      check_ok("./hokusai/%s.yml" % context)
    else:
      check_err("./hokusai/%s.yml" % context)
      return_code += 1

  return return_code
示例#10
0
class Deployment(object):
    def __init__(self, context, deployment_name=None, namespace=None):
        self.context = context
        self.namespace = namespace
        self.kctl = Kubectl(self.context, namespace=namespace)
        self.ecr = ECR()
        if deployment_name:
            self.cache = [
                self.kctl.get_object("deployment %s" % deployment_name)
            ]
        else:
            self.cache = self.kctl.get_objects(
                'deployment',
                selector="app=%s,layer=application" % config.project_name)

    def update(self,
               tag,
               constraint,
               git_remote,
               timeout,
               resolve_tag_sha1=True):
        if not self.ecr.project_repo_exists():
            raise HokusaiError("Project repo does not exist.  Aborting.")

        if resolve_tag_sha1:
            tag = self.ecr.find_git_sha1_image_tag(tag)
            if tag is None:
                raise HokusaiError(
                    "Could not find a git SHA1 for tag %s.  Aborting." % tag)

        if self.namespace is None:
            print_green("Deploying %s to %s..." % (tag, self.context),
                        newline_after=True)
        else:
            print_green("Deploying %s to %s/%s..." %
                        (tag, self.context, self.namespace),
                        newline_after=True)

        if config.pre_deploy is not None:
            print_green("Running pre-deploy hook '%s'..." % config.pre_deploy,
                        newline_after=True)
            return_code = CommandRunner(self.context,
                                        namespace=self.namespace).run(
                                            tag,
                                            config.pre_deploy,
                                            constraint=constraint,
                                            tty=False)
            if return_code:
                raise HokusaiError(
                    "Pre-deploy hook failed with return code %s" % return_code,
                    return_code=return_code)

        deployment_timestamp = datetime.datetime.utcnow().strftime("%s%f")
        for deployment in self.cache:
            containers = [(container['name'], container['image'])
                          for container in deployment['spec']['template']
                          ['spec']['containers']]
            deployment_targets = [{
                "name":
                name,
                "image":
                "%s:%s" % (self.ecr.project_repo, tag)
            } for name, image in containers if self.ecr.project_repo in image]
            patch = {
                "spec": {
                    "template": {
                        "metadata": {
                            "labels": {
                                "deploymentTimestamp": deployment_timestamp
                            }
                        },
                        "spec": {
                            "containers": deployment_targets
                        }
                    },
                    "progressDeadlineSeconds": timeout
                }
            }

            print_green("Patching deployment %s..." %
                        deployment['metadata']['name'],
                        newline_after=True)
            shout(
                self.kctl.command(
                    "patch deployment %s -p '%s'" %
                    (deployment['metadata']['name'], json.dumps(patch))))

        print_green("Waiting for deployment rollouts to complete...")

        rollout_commands = [
            self.kctl.command("rollout status deployment/%s" %
                              deployment['metadata']['name'])
            for deployment in self.cache
        ]
        return_codes = shout_concurrent(rollout_commands, print_output=True)
        if any(return_codes):
            print_red(
                "One or more deployment rollouts timed out!  Rolling back...",
                newline_before=True,
                newline_after=True)
            rollback_commands = [
                self.kctl.command("rollout undo deployment/%s" %
                                  deployment['metadata']['name'])
                for deployment in self.cache
            ]
            shout_concurrent(rollback_commands, print_output=True)
            raise HokusaiError("Deployment failed!")

        post_deploy_success = True

        if config.post_deploy is not None:
            print_green("Running post-deploy hook '%s'..." %
                        config.post_deploy,
                        newline_after=True)
            return_code = CommandRunner(self.context,
                                        namespace=self.namespace).run(
                                            tag,
                                            config.post_deploy,
                                            constraint=constraint,
                                            tty=False)
            if return_code:
                print_yellow(
                    "WARNING: Running the post-deploy hook failed with return code %s"
                    % return_code,
                    newline_before=True,
                    newline_after=True)
                print_yellow(
                    "The tag %s has been rolled out.  However, you should run the post-deploy hook '%s' manually, or re-run this deployment."
                    % (tag, config.post_deploy),
                    newline_after=True)
                post_deploy_success = False

        if self.namespace is None:
            deployment_tag = "%s--%s" % (
                self.context,
                datetime.datetime.utcnow().strftime("%Y-%m-%d--%H-%M-%S"))
            print_green("Updating ECR deployment tags in %s..." %
                        self.ecr.project_repo,
                        newline_after=True)
            try:
                self.ecr.retag(tag, self.context)
                print_green("Updated ECR tag %s -> %s" % (tag, self.context))

                self.ecr.retag(tag, deployment_tag)
                print_green("Updated ECR tag %s -> %s" % (tag, deployment_tag),
                            newline_after=True)
            except (ValueError, ClientError) as e:
                print_yellow(
                    "WARNING: Updating ECR deployment tags failed due to the error: '%s'"
                    % str(e),
                    newline_before=True,
                    newline_after=True)
                print_yellow(
                    "The tag %s has been rolled out.  However, you should create the ECR tags '%s' and '%s' manually, or re-run this deployment."
                    % (tag, deployment_tag, self.context),
                    newline_after=True)
                post_deploy_success = False

            remote = git_remote or config.git_remote
            if remote is not None:
                print_green("Pushing Git deployment tags to %s..." % remote,
                            newline_after=True)
                try:
                    shout("git fetch %s" % remote)
                    shout("git tag -f %s %s" % (self.context, tag),
                          print_output=True)
                    shout("git tag -f %s %s" % (deployment_tag, tag),
                          print_output=True)
                    shout("git push -f --no-verify %s refs/tags/%s" %
                          (remote, self.context),
                          print_output=True)
                    print_green("Updated Git tag %s -> %s" %
                                (tag, self.context))
                    shout("git push -f --no-verify %s refs/tags/%s" %
                          (remote, deployment_tag),
                          print_output=True)
                    print_green("Updated Git tag %s -> %s" %
                                (tag, deployment_tag),
                                newline_after=True)
                except CalledProcessError as e:
                    print_yellow(
                        "WARNING: Creating Git deployment tags failed due to the error: '%s'"
                        % str(e),
                        newline_before=True,
                        newline_after=True)
                    print_yellow(
                        "The tag %s has been rolled out.  However, you should create the Git tags '%s' and '%s' manually, or re-run this deployment."
                        % (tag, deployment_tag, self.context),
                        newline_after=True)
                    post_deploy_success = False

        if post_deploy_success:
            print_green("Deployment succeeded!")
        else:
            raise HokusaiError("One or more post-deploy steps failed!")

    def refresh(self):
        deployment_timestamp = datetime.datetime.utcnow().strftime("%s%f")
        for deployment in self.cache:
            patch = {
                "spec": {
                    "template": {
                        "metadata": {
                            "labels": {
                                "deploymentTimestamp": deployment_timestamp
                            }
                        }
                    }
                }
            }
            print_green("Refreshing %s..." % deployment['metadata']['name'],
                        newline_after=True)
            shout(
                self.kctl.command(
                    "patch deployment %s -p '%s'" %
                    (deployment['metadata']['name'], json.dumps(patch))))

        print_green("Waiting for refresh to complete...")

        rollout_commands = [
            self.kctl.command("rollout status deployment/%s" %
                              deployment['metadata']['name'])
            for deployment in self.cache
        ]
        return_codes = shout_concurrent(rollout_commands, print_output=True)
        if any(return_codes):
            raise HokusaiError("Refresh failed!")

    @property
    def names(self):
        return [deployment['metadata']['name'] for deployment in self.cache]

    @property
    def current_tag(self):
        images = []

        for deployment in self.cache:
            containers = deployment['spec']['template']['spec']['containers']
            container_images = [
                container['image'] for container in containers
                if self.ecr.project_repo in container['image']
            ]

            if not container_images:
                raise HokusaiError(
                    "Deployment has no valid target containers.  Aborting.")
            if not all(x == container_images[0] for x in container_images):
                raise HokusaiError(
                    "Deployment's containers do not reference the same image tag.  Aborting."
                )

            images.append(container_images[0])

        if not all(y == images[0] for y in images):
            raise HokusaiError(
                "Deployments do not reference the same image tag. Aborting.")

        return images[0].rsplit(':', 1)[1]
示例#11
0
def setup(project_name, template_remote, template_dir, template_vars,
          allow_missing_vars):
    mkpath(os.path.join(CWD, HOKUSAI_CONFIG_DIR))
    config.create(clean_string(project_name))

    ecr = ECR()
    if ecr.project_repo_exists():
        print_green("Project repo %s already exists.  Skipping create." %
                    ecr.project_repo)
    else:
        ecr.create_project_repo()
        print_green("Created project repo %s" % ecr.project_repo)

    scratch_dir = None
    if template_remote:
        scratch_dir = tempfile.mkdtemp()
        git_repo_and_branch = template_remote.split('#', 1)
        git_repo = git_repo_and_branch[0]
        if len(git_repo_and_branch) == 2:
            git_branch = git_repo_and_branch[1]
        else:
            git_branch = "master"
        shout("git clone -b %s --single-branch %s %s" %
              (git_branch, git_repo, scratch_dir))

    custom_template_dir = None
    if allow_missing_vars:
        environment_kwargs = {}
    else:
        environment_kwargs = {"undefined": StrictUndefined}

    if scratch_dir and template_dir:
        custom_template_dir = os.path.join(scratch_dir,
                                           os.path.basename(template_dir))
        env = Environment(loader=FileSystemLoader(
            os.path.join(scratch_dir, os.path.basename(template_dir))),
                          **environment_kwargs)
    elif scratch_dir:
        custom_template_dir = scratch_dir
        env = Environment(loader=FileSystemLoader(scratch_dir),
                          **environment_kwargs)
    elif template_dir:
        custom_template_dir = os.path.abspath(template_dir)
        env = Environment(loader=FileSystemLoader(
            os.path.abspath(template_dir)),
                          **environment_kwargs)
    else:
        try:
            base_path = sys._MEIPASS
            env = Environment(loader=FileSystemLoader(
                os.path.join(base_path, 'hokusai', 'templates')))
        except:
            env = Environment(loader=PackageLoader('hokusai', 'templates'))

    required_templates = [
        'Dockerfile.j2', '.dockerignore.j2', 'hokusai/build.yml.j2',
        'hokusai/development.yml.j2', 'hokusai/test.yml.j2',
        'hokusai/staging.yml.j2', 'hokusai/production.yml.j2'
    ]

    template_context = {
        "project_name": config.project_name,
        "project_repo": ecr.project_repo
    }

    for s in template_vars:
        if '=' not in s:
            raise HokusaiError(
                "Error: template variables must be of the form 'key=value'")
        split = s.split('=', 1)
        template_context[split[0]] = split[1]

    try:
        for template in required_templates:
            if custom_template_dir and not os.path.isfile(
                    os.path.join(custom_template_dir, template)):
                raise HokusaiError("Could not find required template file %s" %
                                   template)
            with open(os.path.join(CWD, template.rstrip('.j2')), 'w') as f:
                f.write(env.get_template(template).render(**template_context))
            print_green("Created %s" % template.rstrip('.j2'))

        if custom_template_dir:
            for root, _, files in os.walk(custom_template_dir):
                subpath = os.path.relpath(root, custom_template_dir)
                if subpath is not '.':
                    mkpath(os.path.join(CWD, subpath))
                for file in files:
                    if subpath is not '.':
                        file_path = os.path.join(subpath, file)
                    else:
                        file_path = file
                    if file_path in required_templates:
                        continue
                    if file_path.endswith('.j2'):
                        with open(os.path.join(CWD, file_path.rstrip('.j2')),
                                  'w') as f:
                            f.write(
                                env.get_template(file_path).render(
                                    **template_context))
                    else:
                        copyfile(os.path.join(custom_template_dir, file_path),
                                 os.path.join(CWD, file_path))
                    print_green("Created %s" % file_path.rstrip('.j2'))
    finally:
        if scratch_dir:
            rmtree(scratch_dir)
示例#12
0
        shout('which git')
        check_ok('git')
    except CalledProcessError:
        check_err('git')
        return_code += 1

    try:
        boto3.client('sts',
                     region_name=get_region_name()).get_caller_identity()
        check_ok('Valid AWS credentials')
    except botoexceptions.ClientError, botoexceptions.NoCredentialsError:
        check_err('Valid AWS credentials')
        return_code += 1

    ecr = ECR()
    if ecr.project_repo_exists():
        check_ok("ECR repository '%s'" % config.project_name)
    else:
        check_err("ECR repository '%s'" % config.project_name)
        return_code += 1

    if not os.path.isfile(os.path.join(os.getcwd(), 'hokusai/build.yml')):
        if os.path.isfile(os.path.join(os.getcwd(), 'hokusai/common.yml')):
            check_ok('./hokusai/common.yml')
        else:
            check_err('./hokusai/build.yml')
    else:
        check_ok('./hokusai/build.yml')
        return_code += 1

    if os.path.isfile(os.path.join(os.getcwd(), 'hokusai/development.yml')):
示例#13
0
class Deployment(object):
    def __init__(self, context, deployment_name=None, namespace=None):
        self.context = context
        self.namespace = namespace
        self.kctl = Kubectl(self.context, namespace=namespace)
        self.ecr = ECR()
        if deployment_name:
            self.cache = [
                self.kctl.get_object("deployment %s" % deployment_name)
            ]
        else:
            self.cache = self.kctl.get_objects(
                'deployment',
                selector="app=%s,layer=application" % config.project_name)

    def update(self, tag, constraint, git_remote, resolve_tag_sha1=True):
        if not self.ecr.project_repo_exists():
            raise HokusaiError("Project repo does not exist.  Aborting.")

        if resolve_tag_sha1:
            tag = self.ecr.find_git_sha1_image_tag(tag)
            if tag is None:
                raise HokusaiError(
                    "Could not find a git SHA1 for tag %s.  Aborting." % tag)

        if self.namespace is None:
            print_green("Deploying %s to %s..." % (tag, self.context))
        else:
            print_green("Deploying %s to %s/%s..." %
                        (tag, self.context, self.namespace))

        if self.namespace is None:
            self.ecr.retag(tag, self.context)
            print_green("Updated tag %s -> %s" % (tag, self.context))

            deployment_tag = "%s--%s" % (
                self.context,
                datetime.datetime.utcnow().strftime("%Y-%m-%d--%H-%M-%S"))
            self.ecr.retag(tag, deployment_tag)
            print_green("Updated tag %s -> %s" % (tag, deployment_tag))

            if git_remote is not None:
                print_green("Pushing deployment tags to %s..." % git_remote)
                shout("git tag -f %s" % self.context, print_output=True)
                shout("git tag %s" % deployment_tag, print_output=True)
                shout("git push --force %s --tags" % git_remote,
                      print_output=True)

        if config.pre_deploy is not None:
            print_green("Running pre-deploy hook '%s'..." % config.pre_deploy)
            return_code = CommandRunner(self.context,
                                        namespace=self.namespace).run(
                                            tag,
                                            config.pre_deploy,
                                            constraint=constraint)
            if return_code:
                raise HokusaiError(
                    "Pre-deploy hook failed with return code %s" % return_code,
                    return_code=return_code)

        deployment_timestamp = datetime.datetime.utcnow().strftime("%s%f")
        for deployment in self.cache:
            containers = deployment['spec']['template']['spec']['containers']
            container_names = [container['name'] for container in containers]
            deployment_targets = [{
                "name":
                name,
                "image":
                "%s:%s" % (self.ecr.project_repo, tag)
            } for name in container_names]
            patch = {
                "spec": {
                    "template": {
                        "metadata": {
                            "labels": {
                                "deploymentTimestamp": deployment_timestamp
                            }
                        },
                        "spec": {
                            "containers": deployment_targets
                        }
                    }
                }
            }
            print_green("Patching deployment %s..." %
                        deployment['metadata']['name'])
            shout(
                self.kctl.command(
                    "patch deployment %s -p '%s'" %
                    (deployment['metadata']['name'], json.dumps(patch))))

        print_green("Waiting for rollout to complete...")

        rollout_commands = [
            self.kctl.command("rollout status deployment/%s" %
                              deployment['metadata']['name'])
            for deployment in self.cache
        ]
        return_code = shout_concurrent(rollout_commands)
        if return_code:
            raise HokusaiError("Deployment failed!", return_code=return_code)

        if config.post_deploy is not None:
            print_green("Running post-deploy hook '%s'..." %
                        config.post_deploy)
            return_code = CommandRunner(self.context,
                                        namespace=self.namespace).run(
                                            tag,
                                            config.post_deploy,
                                            constraint=constraint)
            if return_code:
                raise HokusaiError(
                    "Post-deploy hook failed with return code %s" %
                    return_code,
                    return_code=return_code)

    def refresh(self):
        deployment_timestamp = datetime.datetime.utcnow().strftime("%s%f")
        for deployment in self.cache:
            patch = {
                "spec": {
                    "template": {
                        "metadata": {
                            "labels": {
                                "deploymentTimestamp": deployment_timestamp
                            }
                        }
                    }
                }
            }
            print_green("Refreshing %s..." % deployment['metadata']['name'])
            shout(
                self.kctl.command(
                    "patch deployment %s -p '%s'" %
                    (deployment['metadata']['name'], json.dumps(patch))))

        print_green("Waiting for refresh to complete...")

        rollout_commands = [
            self.kctl.command("rollout status deployment/%s" %
                              deployment['metadata']['name'])
            for deployment in self.cache
        ]
        return_code = shout_concurrent(rollout_commands)
        if return_code:
            raise HokusaiError("Refresh failed!", return_code=return_code)

    @property
    def names(self):
        return [deployment['metadata']['name'] for deployment in self.cache]

    @property
    def current_tag(self):
        images = []

        for deployment in self.cache:
            containers = deployment['spec']['template']['spec']['containers']
            container_names = [container['name'] for container in containers]
            container_images = [container['image'] for container in containers]

            if not all(x == container_images[0] for x in container_images):
                raise HokusaiError(
                    "Deployment's containers do not reference the same image tag.  Aborting."
                )

            images.append(containers[0]['image'])

        if not all(y == images[0] for y in images):
            raise HokusaiError(
                "Deployments do not reference the same image tag. Aborting.")

        return images[0].rsplit(':', 1)[1]
示例#14
0
class TestECR(HokusaiIntegrationTestCase):
    def setUp(self):
        self.ecr = ECR()

    @httpretty.activate
    def test_registry(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=self.fixture('sts-get-caller-identity-response.xml'),
            content_type="application/xml")
        httpretty.register_uri(
            httpretty.POST,
            "https://api.ecr.us-east-1.amazonaws.com/",
            body=self.fixture('ecr-repositories-response.json'),
            content_type="application/x-amz-json-1.1")
        repositories = [
            str(repo['repositoryName']) for repo in self.ecr.registry
        ]
        self.assertTrue('hello' in repositories)
        self.assertTrue('bar' in repositories)
        self.assertTrue('baz' in repositories)

    @httpretty.activate
    def test_project_repo_exists(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=self.fixture('sts-get-caller-identity-response.xml'),
            content_type="application/xml")
        httpretty.register_uri(
            httpretty.POST,
            "https://api.ecr.us-east-1.amazonaws.com/",
            body=self.fixture('ecr-repositories-response.json'),
            content_type="application/x-amz-json-1.1")
        self.assertTrue(self.ecr.project_repo_exists())

    @httpretty.activate
    def test_get_project_repo(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=self.fixture('sts-get-caller-identity-response.xml'),
            content_type="application/xml")
        httpretty.register_uri(
            httpretty.POST,
            "https://api.ecr.us-east-1.amazonaws.com/",
            body=self.fixture('ecr-repositories-response.json'),
            content_type="application/x-amz-json-1.1")
        self.assertEqual(self.ecr.project_repo,
                         '123456789012.dkr.ecr.us-east-1.amazonaws.com/hello')

    @httpretty.activate
    def test_create_project_repo(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=self.fixture('sts-get-caller-identity-response.xml'),
            content_type="application/xml")
        httpretty.register_uri(
            httpretty.POST,
            "https://api.ecr.us-east-1.amazonaws.com/",
            body=self.fixture('ecr-create-repository-response.json'),
            content_type="application/x-amz-json-1.1")
        httpretty.register_uri(
            httpretty.POST,
            "https://api.ecr.us-east-1.amazonaws.com/",
            body=self.fixture('ecr-empty-repositories-response.json'),
            content_type="application/x-amz-json-1.1")
        self.assertTrue(self.ecr.create_project_repo())

    @httpretty.activate
    def test_get_login(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=self.fixture('sts-get-caller-identity-response.xml'),
            content_type="application/xml")
        httpretty.register_uri(
            httpretty.POST,
            "https://api.ecr.us-east-1.amazonaws.com/",
            body=self.fixture('ecr-authorization-response.json'),
            content_type="application/x-amz-json-1.1")
        self.assertEqual(
            self.ecr.get_login(),
            'docker login -u AWS -p 76W8YEUFHDSAE98DFDHSFSDFIUHSDAJKGKSADFGKDF https://123456789012.dkr.ecr.us-east-1.amazonaws.com'
        )

    @httpretty.activate
    def test_images(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=self.fixture('sts-get-caller-identity-response.xml'),
            content_type="application/xml")
        httpretty.register_uri(httpretty.POST,
                               "https://api.ecr.us-east-1.amazonaws.com/",
                               body=self.fixture('ecr-images-response.json'),
                               content_type="application/x-amz-json-1.1")
        self.assertEqual(
            self.ecr.images[0]['imageTags'],
            ['a2605e5b93ec4beecde122c53a3fdea18807459c', 'latest'])
        self.assertEqual(
            self.ecr.images[0]['imageDigest'],
            'sha256:8sh968hsn205e8bff53ba8ed1006c7f41dacd17db164efdn6d346204f997shdn'
        )
        self.assertEqual(self.ecr.images[0]['registryId'], '123456789012')
        self.assertEqual(self.ecr.images[0]['repositoryName'], 'hello')

    @httpretty.activate
    def test_tag_exists(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=self.fixture('sts-get-caller-identity-response.xml'),
            content_type="application/xml")
        httpretty.register_uri(httpretty.POST,
                               "https://api.ecr.us-east-1.amazonaws.com/",
                               body=self.fixture('ecr-images-response.json'),
                               content_type="application/x-amz-json-1.1")
        self.assertTrue(
            self.ecr.tag_exists('a2605e5b93ec4beecde122c53a3fdea18807459c'))
        self.assertTrue(self.ecr.tag_exists('latest'))

    @httpretty.activate
    def test_tags(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=self.fixture('sts-get-caller-identity-response.xml'),
            content_type="application/xml")
        httpretty.register_uri(httpretty.POST,
                               "https://api.ecr.us-east-1.amazonaws.com/",
                               body=self.fixture('ecr-images-response.json'),
                               content_type="application/x-amz-json-1.1")
        self.assertIn('a2605e5b93ec4beecde122c53a3fdea18807459c',
                      self.ecr.tags())
        self.assertIn('latest', self.ecr.tags())

    @httpretty.activate
    def test_find_git_sha1_image_tag(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=self.fixture('sts-get-caller-identity-response.xml'),
            content_type="application/xml")
        httpretty.register_uri(httpretty.POST,
                               "https://api.ecr.us-east-1.amazonaws.com/",
                               body=self.fixture('ecr-images-response.json'),
                               content_type="application/x-amz-json-1.1")
        self.assertEqual(self.ecr.find_git_sha1_image_tag('latest'),
                         'a2605e5b93ec4beecde122c53a3fdea18807459c')

    @httpretty.activate
    def test_image_digest_for_tag(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=self.fixture('sts-get-caller-identity-response.xml'),
            content_type="application/xml")
        httpretty.register_uri(httpretty.POST,
                               "https://api.ecr.us-east-1.amazonaws.com/",
                               body=self.fixture('ecr-images-response.json'),
                               content_type="application/x-amz-json-1.1")
        self.assertEqual(
            self.ecr.image_digest_for_tag(
                'a2605e5b93ec4beecde122c53a3fdea18807459c'),
            'sha256:8sh968hsn205e8bff53ba8ed1006c7f41dacd17db164efdn6d346204f997shdn'
        )
        self.assertEqual(
            self.ecr.image_digest_for_tag('latest'),
            'sha256:8sh968hsn205e8bff53ba8ed1006c7f41dacd17db164efdn6d346204f997shdn'
        )
示例#15
0
class TestECR(HokusaiIntegrationTestCase):
    def setUp(self):
        self.ecr = ECR()

    @httpretty.activate
    def test_registry(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=open(
                os.path.join(os.getcwd(), 'test', 'fixtures',
                             'sts-get-caller-identity-response.xml')).read(),
            content_type="application/xml")
        httpretty.register_uri(
            httpretty.POST,
            "https://api.ecr.us-east-1.amazonaws.com/",
            body=open(
                os.path.join(os.getcwd(), 'test', 'fixtures',
                             'ecr-repositories-response.json')).read(),
            content_type="application/x-amz-json-1.1")
        repositories = [
            str(repo['repositoryName']) for repo in self.ecr.registry
        ]
        self.assertTrue('hello' in repositories)
        self.assertTrue('bar' in repositories)
        self.assertTrue('baz' in repositories)

    @httpretty.activate
    def test_project_repo_exists(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=open(
                os.path.join(os.getcwd(), 'test', 'fixtures',
                             'sts-get-caller-identity-response.xml')).read(),
            content_type="application/xml")
        httpretty.register_uri(
            httpretty.POST,
            "https://api.ecr.us-east-1.amazonaws.com/",
            body=open(
                os.path.join(os.getcwd(), 'test', 'fixtures',
                             'ecr-repositories-response.json')).read(),
            content_type="application/x-amz-json-1.1")
        self.assertTrue(self.ecr.project_repo_exists())

    @httpretty.activate
    def test_get_project_repo(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=open(
                os.path.join(os.getcwd(), 'test', 'fixtures',
                             'sts-get-caller-identity-response.xml')).read(),
            content_type="application/xml")
        httpretty.register_uri(
            httpretty.POST,
            "https://api.ecr.us-east-1.amazonaws.com/",
            body=open(
                os.path.join(os.getcwd(), 'test', 'fixtures',
                             'ecr-repositories-response.json')).read(),
            content_type="application/x-amz-json-1.1")
        self.assertEqual(self.ecr.project_repo,
                         '123456789012.dkr.ecr.us-east-1.amazonaws.com/hello')

    @httpretty.activate
    def test_create_project_repo(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=open(
                os.path.join(os.getcwd(), 'test', 'fixtures',
                             'sts-get-caller-identity-response.xml')).read(),
            content_type="application/xml")
        httpretty.register_uri(
            httpretty.POST,
            "https://api.ecr.us-east-1.amazonaws.com/",
            body=open(
                os.path.join(os.getcwd(), 'test', 'fixtures',
                             'ecr-create-repository-response.json')).read(),
            content_type="application/x-amz-json-1.1")
        httpretty.register_uri(
            httpretty.POST,
            "https://api.ecr.us-east-1.amazonaws.com/",
            body=open(
                os.path.join(os.getcwd(), 'test', 'fixtures',
                             'ecr-empty-repositories-response.json')).read(),
            content_type="application/x-amz-json-1.1")
        self.assertTrue(self.ecr.create_project_repo())

    @httpretty.activate
    def test_get_login(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=open(
                os.path.join(os.getcwd(), 'test', 'fixtures',
                             'sts-get-caller-identity-response.xml')).read(),
            content_type="application/xml")
        httpretty.register_uri(
            httpretty.POST,
            "https://api.ecr.us-east-1.amazonaws.com/",
            body=open(
                os.path.join(os.getcwd(), 'test', 'fixtures',
                             'ecr-authorization-response.json')).read(),
            content_type="application/x-amz-json-1.1")
        self.assertEqual(
            self.ecr.get_login(),
            'docker login -u AWS -p 76W8YEUFHDSAE98DFDHSFSDFIUHSDAJKGKSADFGKDF https://123456789012.dkr.ecr.us-east-1.amazonaws.com'
        )

    @httpretty.activate
    def test_get_images(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=open(
                os.path.join(os.getcwd(), 'test', 'fixtures',
                             'sts-get-caller-identity-response.xml')).read(),
            content_type="application/xml")
        httpretty.register_uri(
            httpretty.POST,
            "https://api.ecr.us-east-1.amazonaws.com/",
            body=open(
                os.path.join(os.getcwd(), 'test', 'fixtures',
                             'ecr-images-response.json')).read(),
            content_type="application/x-amz-json-1.1")
        self.assertEqual(
            self.ecr.get_images()[0]['imageTags'],
            ['7shdn4f0f34bb8shdkb313cbeccb2fc031808duho', 'latest'])
        self.assertEqual(
            self.ecr.get_images()[0]['imageDigest'],
            'sha256:8sh968hsn205e8bff53ba8ed1006c7f41dacd17db164efdn6d346204f997shdn'
        )
        self.assertEqual(self.ecr.get_images()[0]['registryId'],
                         '123456789012')
        self.assertEqual(self.ecr.get_images()[0]['repositoryName'], 'hello')

    @httpretty.activate
    def test_tag_exists(self):
        httpretty.register_uri(
            httpretty.POST,
            "https://sts.amazonaws.com/",
            body=open(
                os.path.join(os.getcwd(), 'test', 'fixtures',
                             'sts-get-caller-identity-response.xml')).read(),
            content_type="application/xml")
        httpretty.register_uri(
            httpretty.POST,
            "https://api.ecr.us-east-1.amazonaws.com/",
            body=open(
                os.path.join(os.getcwd(), 'test', 'fixtures',
                             'ecr-images-response.json')).read(),
            content_type="application/x-amz-json-1.1")
        self.assertTrue(
            self.ecr.tag_exists('7shdn4f0f34bb8shdkb313cbeccb2fc031808duho'))
        self.assertTrue(self.ecr.tag_exists('latest'))
示例#16
0
def check():
  return_code = 0

  def check_ok(check_item):
    print_green('\u2714 ' + check_item + ' found')

  def check_err(check_item):
    print_red('\u2718 ' + check_item + ' not found')

  try:
    config.project_name
    check_ok('Config project-name')
  except HokusaiError:
    check_err('Config project-name')

  try:
    shout('which docker')
    check_ok('docker')
  except CalledProcessError:
    check_err('docker')
    return_code += 1

  try:
    shout('which docker-compose')
    check_ok('docker-compose')
  except CalledProcessError:
    check_err('docker-compose')
    return_code += 1

  try:
    shout('which kubectl')
    check_ok('kubectl')
  except CalledProcessError:
    check_err('kubectl')
    return_code += 1

  try:
    shout('which git')
    check_ok('git')
  except CalledProcessError:
    check_err('git')
    return_code += 1

  try:
    boto3.client('sts', region_name=get_region_name()).get_caller_identity()
    check_ok('Valid AWS credentials')
  except (botoexceptions.ClientError, botoexceptions.NoCredentialsError):
    check_err('Valid AWS credentials')
    return_code += 1

  ecr = ECR()
  if ecr.project_repo_exists():
    check_ok("ECR repository '%s'" % config.project_name)
  else:
    check_err("ECR repository '%s'" % config.project_name)
    return_code += 1

  try:
    build_template = TemplateSelector().get(os.path.join(CWD, HOKUSAI_CONFIG_DIR, BUILD_YAML_FILE))
    check_ok(HOKUSAI_CONFIG_DIR + '/' + os.path.split(build_template)[-1])
  except HokusaiError:
    check_err('hokusai/build.*')
    return_code += 1

  try:
    development_template = TemplateSelector().get(os.path.join(CWD, HOKUSAI_CONFIG_DIR, DEVELOPMENT_YML_FILE))
    check_ok(HOKUSAI_CONFIG_DIR + '/' + os.path.split(development_template)[-1])
  except HokusaiError:
    check_err('hokusai/development.*')
    return_code += 1

  try:
    test_template = TemplateSelector().get(os.path.join(CWD, HOKUSAI_CONFIG_DIR, TEST_YML_FILE))
    check_ok(HOKUSAI_CONFIG_DIR + '/' + os.path.split(test_template)[-1])
  except HokusaiError:
    check_err('hokusai/test.*')
    return_code += 1

  for context in ['staging', 'production']:
    try:
      if context in Kubectl('staging').contexts():
        check_ok("kubectl context '%s'" % context)
      else:
        check_err("kubectl context '%s'" % context)
        return_code += 1
    except CalledProcessError:
      check_err('%s context' % context)
      return_code += 1

    try:
      context_template = TemplateSelector().get(os.path.join(CWD, HOKUSAI_CONFIG_DIR, context))
      check_ok(HOKUSAI_CONFIG_DIR + '/' + os.path.split(context_template)[-1])
    except HokusaiError:
      check_err("hokusai/%s.*" % context)
      return_code += 1

  return return_code
示例#17
0
class Deployment(object):
    def __init__(self, context, deployment_name=None, namespace=None):
        self.context = context
        self.namespace = namespace
        self.kctl = Kubectl(self.context, namespace=namespace)
        self.ecr = ECR()
        if deployment_name:
            self.cache = [
                self.kctl.get_object("deployment %s" % deployment_name)
            ]
        else:
            self.cache = self.kctl.get_objects(
                'deployment',
                selector="app=%s,layer=application" % config.project_name)

    def update(self,
               tag,
               constraint,
               git_remote,
               timeout,
               update_config=False,
               filename=None):
        if not self.ecr.project_repo_exists():
            raise HokusaiError("Project repo does not exist.  Aborting.")

        digest = self.ecr.image_digest_for_tag(tag)
        if digest is None:
            raise HokusaiError(
                "Could not find an image digest for tag %s.  Aborting." % tag)

        if self.namespace is None:
            print_green("Deploying %s to %s..." % (digest, self.context),
                        newline_after=True)
        else:
            print_green("Deploying %s to %s/%s..." %
                        (digest, self.context, self.namespace),
                        newline_after=True)
        """
    This logic should be refactored, but essentially if namespace and filename are provided, the caller is
    a review app, while if namespace is None it is either staging or production.  If filename is unset for staging
    or production it is targeting the 'canonical' app, i.e. staging.yml or production.yml while if it is set it is
    trageting a 'canary' app.

    For the canonical app, run deploy hooks and post-depoy steps creating deployment tags
    For a canary app, skip deploy hooks and post-deploy steps
    For review apps, run deploy hooks but skip post-deploy steps

    For all deployment rollouts, if update_config or filename targets a yml file, bust the
    deployment cache using k8s field selectors and get deployments to watch the rollout from
    the yml file spec
    """

        # Run the pre-deploy hook for the canonical app or a review app
        if config.pre_deploy and (filename is None or
                                  (filename and self.namespace)):
            print_green("Running pre-deploy hook '%s'..." % config.pre_deploy,
                        newline_after=True)
            return_code = CommandRunner(self.context,
                                        namespace=self.namespace).run(
                                            tag,
                                            config.pre_deploy,
                                            constraint=constraint,
                                            tty=False)
            if return_code:
                raise HokusaiError(
                    "Pre-deploy hook failed with return code %s" % return_code,
                    return_code=return_code)

        # Patch the deployments
        deployment_timestamp = datetime.datetime.utcnow().strftime("%s%f")

        if filename is None:
            kubernetes_yml = os.path.join(CWD, HOKUSAI_CONFIG_DIR,
                                          "%s.yml" % self.context)
        else:
            kubernetes_yml = filename

        # If a review app, a canary app or the canonical app while updating config,
        # bust the deployment cache and populate deployments from the yaml file
        if filename or update_config:
            self.cache = []
            for item in yaml.safe_load_all(open(kubernetes_yml, 'r')):
                if item['kind'] == 'Deployment':
                    self.cache.append(item)

        # If updating config, path the spec and apply
        if update_config:
            print_green(
                "Patching Deployments in spec %s with image digest %s" %
                (kubernetes_yml, digest),
                newline_after=True)
            payload = []
            for item in yaml.safe_load_all(open(kubernetes_yml, 'r')):
                if item['kind'] == 'Deployment':
                    item['spec']['template']['metadata']['labels'][
                        'deploymentTimestamp'] = deployment_timestamp
                    item['spec']['progressDeadlineSeconds'] = timeout
                    for container in item['spec']['template']['spec'][
                            'containers']:
                        if self.ecr.project_repo in container['image']:
                            container['image'] = "%s@%s" % (
                                self.ecr.project_repo, digest)
                payload.append(item)

            f = NamedTemporaryFile(delete=False)
            f.write(YAML_HEADER)
            f.write(yaml.safe_dump_all(payload, default_flow_style=False))
            f.close()

            print_green("Applying patched spec %s..." % f.name,
                        newline_after=True)
            try:
                shout(self.kctl.command("apply -f %s" % f.name),
                      print_output=True)
            finally:
                os.unlink(f.name)

        # If not updating config, patch the deployments in the cache and call kubectl patch to update
        else:
            for deployment in self.cache:
                containers = [(container['name'], container['image'])
                              for container in deployment['spec']['template']
                              ['spec']['containers']]
                deployment_targets = [{
                    "name":
                    name,
                    "image":
                    "%s@%s" % (self.ecr.project_repo, digest)
                } for name, image in containers
                                      if self.ecr.project_repo in image]
                patch = {
                    "spec": {
                        "template": {
                            "metadata": {
                                "labels": {
                                    "deploymentTimestamp": deployment_timestamp
                                }
                            },
                            "spec": {
                                "containers": deployment_targets
                            }
                        },
                        "progressDeadlineSeconds": timeout
                    }
                }

                print_green("Patching deployment %s..." %
                            deployment['metadata']['name'],
                            newline_after=True)
                shout(
                    self.kctl.command(
                        "patch deployment %s -p '%s'" %
                        (deployment['metadata']['name'], json.dumps(patch))))

        # Watch the rollouts in the cache and if any fail, roll back
        print_green("Waiting for deployment rollouts to complete...")
        rollout_commands = [
            self.kctl.command("rollout status deployment/%s" %
                              deployment['metadata']['name'])
            for deployment in self.cache
        ]
        return_codes = shout_concurrent(rollout_commands, print_output=True)
        if any(return_codes):
            print_red(
                "One or more deployment rollouts failed!  Rolling back...",
                newline_before=True,
                newline_after=True)
            rollback_commands = [
                self.kctl.command("rollout undo deployment/%s" %
                                  deployment['metadata']['name'])
                for deployment in self.cache
            ]
            shout_concurrent(rollback_commands, print_output=True)
            raise HokusaiError("Deployment failed!")

        post_deploy_success = True

        # Run the post-deploy hook for the canonical app or a review app
        if config.post_deploy and (filename is None or
                                   (filename and self.namespace)):
            print_green("Running post-deploy hook '%s'..." %
                        config.post_deploy,
                        newline_after=True)
            return_code = CommandRunner(self.context,
                                        namespace=self.namespace).run(
                                            tag,
                                            config.post_deploy,
                                            constraint=constraint,
                                            tty=False)
            if return_code:
                print_yellow(
                    "WARNING: Running the post-deploy hook failed with return code %s"
                    % return_code,
                    newline_before=True,
                    newline_after=True)
                print_yellow(
                    "The image digest %s has been rolled out.  However, you should run the post-deploy hook '%s' manually, or re-run this deployment."
                    % (digest, config.post_deploy),
                    newline_after=True)
                post_deploy_success = False

        # For the canonical app, create tags
        if filename is None:
            deployment_tag = "%s--%s" % (
                self.context,
                datetime.datetime.utcnow().strftime("%Y-%m-%d--%H-%M-%S"))
            print_green("Updating ECR deployment tags in %s..." %
                        self.ecr.project_repo,
                        newline_after=True)
            try:
                self.ecr.retag(tag, self.context)
                print_green("Updated ECR tag %s -> %s" % (tag, self.context))

                self.ecr.retag(tag, deployment_tag)
                print_green("Updated ECR tag %s -> %s" % (tag, deployment_tag),
                            newline_after=True)
            except (ValueError, ClientError) as e:
                print_yellow(
                    "WARNING: Updating ECR deployment tags failed due to the error: '%s'"
                    % str(e),
                    newline_before=True,
                    newline_after=True)
                print_yellow(
                    "The tag %s has been rolled out.  However, you should create the ECR tags '%s' and '%s' manually, or re-run this deployment."
                    % (tag, deployment_tag, self.context),
                    newline_after=True)
                post_deploy_success = False

            remote = git_remote or config.git_remote
            if remote:
                print_green("Pushing Git deployment tags to %s..." % remote,
                            newline_after=True)
                try:
                    shout("git fetch %s" % remote)
                    shout("git tag -f %s %s" % (self.context, tag),
                          print_output=True)
                    shout("git tag -f %s %s" % (deployment_tag, tag),
                          print_output=True)
                    shout("git push -f --no-verify %s refs/tags/%s" %
                          (remote, self.context),
                          print_output=True)
                    print_green("Updated Git tag %s -> %s" %
                                (tag, self.context))
                    shout("git push -f --no-verify %s refs/tags/%s" %
                          (remote, deployment_tag),
                          print_output=True)
                    print_green("Updated Git tag %s -> %s" %
                                (tag, deployment_tag),
                                newline_after=True)
                except CalledProcessError as e:
                    print_yellow(
                        "WARNING: Creating Git deployment tags failed due to the error: '%s'"
                        % str(e),
                        newline_before=True,
                        newline_after=True)
                    print_yellow(
                        "The tag %s has been rolled out.  However, you should create the Git tags '%s' and '%s' manually, or re-run this deployment."
                        % (tag, deployment_tag, self.context),
                        newline_after=True)
                    post_deploy_success = False

        if post_deploy_success:
            print_green("Deployment succeeded!")
        else:
            raise HokusaiError("One or more post-deploy steps failed!")

    def refresh(self):
        deployment_timestamp = datetime.datetime.utcnow().strftime("%s%f")
        for deployment in self.cache:
            patch = {
                "spec": {
                    "template": {
                        "metadata": {
                            "labels": {
                                "deploymentTimestamp": deployment_timestamp
                            }
                        }
                    }
                }
            }
            print_green("Refreshing %s..." % deployment['metadata']['name'],
                        newline_after=True)
            shout(
                self.kctl.command(
                    "patch deployment %s -p '%s'" %
                    (deployment['metadata']['name'], json.dumps(patch))))

        print_green("Waiting for refresh to complete...")

        rollout_commands = [
            self.kctl.command("rollout status deployment/%s" %
                              deployment['metadata']['name'])
            for deployment in self.cache
        ]
        return_codes = shout_concurrent(rollout_commands, print_output=True)
        if any(return_codes):
            raise HokusaiError("Refresh failed!")

    @property
    def names(self):
        return [deployment['metadata']['name'] for deployment in self.cache]