def assumed_role_to_role(caller_arn: str) -> str: """ If it's an assumed role, we expect the caller ARN to look something like: arn:aws:sts::000000000000:assumed-role/<assumed-role-name>/<actual.username> We want to turn this into something like: arn:aws:sts::000000000000:role/<assumed-role-name> TODO: Find out if there is a proper way to do this """ arn_parts = caller_arn.split(":") if arn_parts[:3] != ["arn", "aws", "sts"]: utils.fatal(f"Can't normalise caller ARN: {caller_arn}") user_parts = arn_parts[5].split("/") if user_parts[0] != "assumed-role": return caller_arn user_parts = ["role", user_parts[1]] user_str = "/".join(user_parts) arn_parts[5] = user_str return ":".join(arn_parts)
def get(_, name, version, profile=None): """ Show the release. """ release = get_release(utils.s3_client(profile), name, int(version)) if release: utils.printfmt(release) else: utils.fatal("Release does not exist")
def current(_, name, profile=None): """ Show current release. """ release = next(get_releases(utils.s3_client(profile), name), None) if release: utils.printfmt(release) else: utils.fatal("Release does not exist")
def check_perms(iam_client, bucket_name, project_names, profile): region = utils.get_region_name(profile) if region is None: utils.fatal( "Can't check permissions with no region set. Try setting in ~/.aws/credentials" ) caller_identity = utils.get_caller_identity(profile) caller_arn = caller_identity["Arn"] # We need to do this because you can't call SimulatePrincipalPolicy on an assumed role. # See https://stackoverflow.com/questions/52941478/simulate-principal-policy-using-assumed-role caller_arn = assumed_role_to_role(caller_arn) arn_to_project = { f"arn:aws:s3:::{bucket_name}/{project}": project for project in project_names } perms = iam_client.simulate_principal_policy( PolicySourceArn=caller_arn, ActionNames=["s3:PutObject"], ResourceArns=list(arn_to_project.keys()), ContextEntries=[ { "ContextKeyName": "aws:multifactorauthpresent", "ContextKeyType": "boolean", "ContextKeyValues": ["true"], }, { "ContextKeyName": "aws:requestedregion", "ContextKeyType": "string", "ContextKeyValues": [region], }, ], ) results = {} ev_results = perms["EvaluationResults"] for res in ev_results: arn = res["EvalResourceName"] project = arn_to_project[arn] if res["EvalDecision"] == "allowed": results[project] = True else: for rsr in res["ResourceSpecificResults"]: if rsr["EvalResourceName"] == arn: results[project] = rsr["EvalResourceDecision"] == "allowed" break return results
def current(_, name, env, bucket=None, profile=None): """ Show current running version. """ client = utils.s3_client(profile) if bucket is None: bucket = utils.get_config()["deploy"][env]["s3_bucket"] last_deploy = next(get_releases(client, name, bucket=bucket), None) if last_deploy: utils.printfmt(last_deploy) else: utils.fatal("Release does not exist")
def get_git() -> BaseGit: global GIT_HOST if GIT_HOST: return GIT_HOST git_type = utils.get_config().get("git", {}).get("provider") if git_type not in GIT_CLASSES: utils.fatal( f"Unsupported git provider type {git_type!r} - try one of {sorted(GIT_CLASSES)!r}" ) git_cls = GIT_CLASSES[git_type] GIT_HOST = git_cls() return GIT_HOST
def get_tracker() -> BaseTracker: global TRACKER if TRACKER: return TRACKER tracker_type = utils.get_config().get("issue_tracker", {}).get("provider") if tracker_type not in TRACKER_CLASSES: utils.fatal( f"Unsupported tracker type {tracker_type} - try one of {sorted(TRACKER_CLASSES)!r}" ) tracker_cls = TRACKER_CLASSES[tracker_type] TRACKER = tracker_cls() return TRACKER
def start( _, name, env, version=None, bucket=None, dry=False, yes=False, rollback=False, profile=None, ): """ Deploy a release on an environment. """ client = utils.s3_client(profile) repo = utils.git_repo() if version is None: release = next(get_releases(client, name), None) else: release = get_release(client, name, int(version)) if release is None: utils.fatal("Release not found") if bucket is None: bucket = utils.get_config()["deploy"][env]["s3_bucket"] last_deploy = next(get_releases(client, name, bucket=bucket), None) last_deployed_version = int(last_deploy.version) if last_deploy else 0 if version is not None: since = min(int(version), last_deployed_version) else: since = last_deployed_version releases = list(get_releases(client, name, since=since)) # the field `commits` is not present in all documents as it was introduced # in a later version. if any of the releases doesn't track them, we'll # skip the commit filtering to avoid not showing commits in the changelog. if any(rel.commits is None for rel in releases): commits = None else: commits = [ commit for rel in releases if rel.commits for commit in rel.commits ] if last_deploy is None: # first deploy is always None changelog = utils.changelog(repo, release.commit, None, keep_only_commits=commits) changelog_text = changelog.short_text is_rollback = release.rollback else: # create a changelog from the latest deploy commit changelog = utils.changelog( repo, git.Oid(hex=release.commit), git.Oid(hex=last_deploy.commit), keep_only_commits=commits, ) changelog_text = changelog.short_text is_rollback = changelog.rollback action_type = ActionType.automated if config.IS_CONCOURSE else ActionType.manual release = dataclasses.replace( release, changelog=changelog_text, timestamp=datetime.now(), author=utils.get_author(repo, git.Oid(hex=release.commit)), rollback=is_rollback, action_type=action_type, commits=commits, ) utils.printfmt(release) if dry: return if release.rollback: commit_count = len(changelog.logs) utils.warning(":warning: This is a rollback! :warning:\n") utils.warning( f":warning: You are rolling back from {name} v{last_deployed_version} to v{version} :warning:\n" ) utils.warning( f":warning: This will remove the above {commit_count} commits from {env} :warning:\n" ) if not rollback: utils.error("Missing flag --rollback\n") utils.fatal("Aborted!") if not yes: if release.rollback: ok = utils.confirm( "Are you sure you want to start a rollback deployment?", style=utils.TextStyle.yellow, ) if not ok: utils.fatal("Aborted!") ok = utils.confirm("Are you sure you want to start this deployment?") if not ok: utils.fatal("Aborted!") put_release(client, bucket, name, release) utils.success("Started new deployment :rocket:\n")
def new( ctx, name, commit=None, version=None, dry=False, yes=False, image_name=None, image_id=None, rollback=False, filter_files_path=None, profile=None, ): """ Create a new release. """ repo = utils.git_repo() client = utils.s3_client(profile) latest = next(get_releases(client, name), None) latest_oid = git.Oid(hex=latest.commit) if latest else None if commit is None: commit = "HEAD" commit_oid = utils.revparse(repo, commit) if version is None: # create next version version = 1 if latest is None else latest.version + 1 else: version = int(version) if image_id is None: image_id = _get_image_id(ctx, commit_oid, name=name, image_name=image_name) if image_id is None: utils.fatal("Image not found") keep_only_files = None if filter_files_path: with open(filter_files_path) as fp: keep_only_files = [line.strip() for line in fp] changelog = utils.changelog(repo, commit_oid, latest_oid, keep_only_files=keep_only_files) action_type = ActionType.automated if config.IS_CONCOURSE else ActionType.manual release = Release( version=version, commit=commit_oid.hex, changelog=changelog.short_text, version_id="", image=image_id, timestamp=datetime.now(), author=utils.get_author(repo, commit_oid), rollback=changelog.rollback, action_type=action_type, commits=[commit.hex for commit in changelog.logs], ) utils.printfmt(release) if dry: return if release.rollback: utils.warning("This is a rollback! :warning:\n") if not rollback: utils.warning("Missing flag --rollback\n") utils.fatal("Aborted!") if not yes: if release.rollback: ok = utils.confirm( "Are you sure you want to create a rollback release?", style=utils.TextStyle.yellow, ) if not ok: utils.fatal("Aborted!") ok = utils.confirm("Are you sure you want to create this release?") if not ok: utils.fatal("Aborted!") put_release(client, _get_bucket(), name, release) utils.success("Created new release :tada:\n")
def start( _, name, env, version=None, bucket=None, dry=False, yes=False, rollback=False ): """ Deploy a release on an environment. """ client = utils.s3_client() repo = utils.git_repo() if version is None: release = next(get_releases(client, name), None) else: release = get_release(client, name, int(version)) if release is None: utils.fatal("Release not found") if bucket is None: bucket = utils.get_config()["deploy"][env]["s3_bucket"] last_deploy = next(get_releases(client, name, bucket=bucket), None) if last_deploy is None: # first deploy is always None changelog_text = release.changelog is_rollback = release.rollback else: # create a changelog from the latest deploy commit changelog = utils.changelog( repo, git.Oid(hex=release.commit), git.Oid(hex=last_deploy.commit) ) changelog_text = changelog.text is_rollback = changelog.rollback action_type = ActionType.automated if config.IS_CONCOURSE else ActionType.manual release = dataclasses.replace( release, changelog=changelog_text, timestamp=datetime.now(), author=utils.get_author(repo, git.Oid(hex=release.commit)), rollback=is_rollback, action_type=action_type, ) utils.printfmt(release) if dry: return if release.rollback: utils.warning("This is a rollback! :warning:\n") if not rollback: utils.warning("Missing flag --rollback\n") utils.fatal("Aborted!") if not yes: if release.rollback: ok = utils.confirm( "Are you sure you want to start a rollback deployment?", style=utils.TextStyle.yellow, ) if not ok: utils.fatal("Aborted!") ok = utils.confirm("Are you sure you want to start this deployment?") if not ok: utils.fatal("Aborted!") put_release(client, bucket, name, release) utils.success("Started new deployment :rocket:\n")