def gitlog(): ecr = ECR() staging_deployment = Deployment('staging') staging_tag = staging_deployment.current_tag if staging_tag is None: raise HokusaiError("Could not find a tag 'staging'. Aborting.") if staging_tag == 'staging': staging_tag = ecr.find_git_sha1_image_tag('staging') if staging_tag is None: print_red( "Could not find a git SHA1 tag for 'staging'. Aborting.") return -1 production_deployment = Deployment('production') production_tag = production_deployment.current_tag if production_tag is None: raise HokusaiError("Could not find a tag 'production'. Aborting.") if production_tag == 'production': production_tag = ecr.find_git_sha1_image_tag('production') if production_tag is None: print_red( "Could not find a git SHA1 tag for 'production'. Aborting.") return -1 print_green("Comparing %s to %s" % (production_tag, staging_tag)) shout("git log --right-only %s..%s" % (production_tag, staging_tag), print_output=True)
def gitdiff(): ecr = ECR() staging_deployment = Deployment('staging') staging_tag = staging_deployment.current_tag if staging_tag is None: raise HokusaiError("Could not find a tag for staging. Aborting.") staging_tag = ecr.find_git_sha1_image_tag(staging_tag) if staging_tag is None: raise HokusaiError( "Could not find a git SHA1 tag for tag %s. Aborting." % staging_tag) production_deployment = Deployment('production') production_tag = production_deployment.current_tag if production_tag is None: raise HokusaiError("Could not find a tag for production. Aborting.") production_tag = ecr.find_git_sha1_image_tag(production_tag) if production_tag is None: raise HokusaiError("Could not find a git SHA1 for tag %s. Aborting." % production_tag) print_green("Comparing %s to %s" % (production_tag, staging_tag)) for remote in shout('git remote').splitlines(): shout("git fetch %s" % remote) shout("git diff %s %s" % (production_tag, staging_tag), print_output=True)
def gitlog(): ecr = ECR() staging_deployment = Deployment('staging') staging_tag = staging_deployment.current_tag if staging_tag is None: raise HokusaiError("Could not find a tag for staging. Aborting.") staging_tag = ecr.find_git_sha1_image_tag(staging_tag) if staging_tag is None: raise HokusaiError( "Could not find a git SHA1 tag for tag %s. Aborting." % staging_tag) production_deployment = Deployment('production') production_tag = production_deployment.current_tag if production_tag is None: raise HokusaiError("Could not find a tag for production. Aborting.") production_tag = ecr.find_git_sha1_image_tag(production_tag) if production_tag is None: raise HokusaiError("Could not find a git SHA1 for tag %s. Aborting." % production_tag) print_green("Comparing %s to %s" % (production_tag, staging_tag)) shout("git log --right-only %s..%s" % (production_tag, staging_tag), print_output=True)
def gitcompare(org_name, git_compare_link): ecr = ECR() staging_tag = ecr.find_git_sha1_image_tag('staging') if staging_tag is None: raise HokusaiError("Could not find a tag for staging. Aborting.") production_tag = ecr.find_git_sha1_image_tag('production') if production_tag is None: raise HokusaiError("Could not find a git SHA1 tag for production. Aborting.") print_green(git_compare_link % (org_name, config.project_name, production_tag, staging_tag))
def promote(migration, constraint, git_remote, timeout): ecr = ECR() deploy_from = Deployment('staging') tag = deploy_from.current_tag if tag is None: raise HokusaiError("Could not find a tag for staging. Aborting.") tag = ecr.find_git_sha1_image_tag(tag) if tag is None: print_red("Could not find a git SHA1 for tag %s. Aborting." % tag) return 1 if migration is not None: print_green("Running migration '%s' on production..." % migration, newline_after=True) return_code = CommandRunner('production').run(tag, migration, constraint=constraint, tty=False) if return_code: raise HokusaiError("Migration failed with return code %s" % return_code, return_code=return_code) deploy_to = Deployment('production').update(tag, constraint, git_remote, timeout) print_green("Promoted staging to production at %s" % tag)
def gitlog(): ecr = ECR() staging_tag = ecr.find_git_sha1_image_tag('staging') if staging_tag is None: raise HokusaiError("Could not find a tag for staging. Aborting.") production_tag = ecr.find_git_sha1_image_tag('production') if production_tag is None: raise HokusaiError( "Could not find a git SHA1 tag for production. Aborting.") print_green("Comparing %s to %s" % (production_tag, staging_tag)) for remote in shout('git remote').splitlines(): shout("git fetch %s" % remote) shout("git log --right-only %s..%s" % (production_tag, staging_tag), print_output=True)
def gitcompare(org_name, git_compare_link): ecr = ECR() staging_deployment = Deployment('staging') staging_tag = staging_deployment.current_tag if staging_tag is None: raise HokusaiError("Could not find a tag for staging. Aborting.") staging_tag = ecr.find_git_sha1_image_tag(staging_tag) if staging_tag is None: raise HokusaiError( "Could not find a git SHA1 tag for tag %s. Aborting." % staging_tag) production_deployment = Deployment('production') production_tag = production_deployment.current_tag if production_tag is None: raise HokusaiError("Could not find a tag for production. Aborting.") production_tag = ecr.find_git_sha1_image_tag(production_tag) if production_tag is None: raise HokusaiError("Could not find a git SHA1 for tag %s. Aborting." % production_tag) print_green(git_compare_link % (org_name, config.project_name, production_tag, staging_tag))
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]
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]
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_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.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=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('a2605e5b93ec4beecde122c53a3fdea18807459c')) self.assertTrue(self.ecr.tag_exists('latest')) @httpretty.activate def test_tags(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.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=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.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=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.image_digest_for_tag( 'a2605e5b93ec4beecde122c53a3fdea18807459c'), 'sha256:8sh968hsn205e8bff53ba8ed1006c7f41dacd17db164efdn6d346204f997shdn' ) self.assertEqual( self.ecr.image_digest_for_tag('latest'), 'sha256:8sh968hsn205e8bff53ba8ed1006c7f41dacd17db164efdn6d346204f997shdn' )