Example #1
0
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)
Example #2
0
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")
Example #3
0
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")
Example #4
0
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
Example #5
0
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")
Example #6
0
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
Example #7
0
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
Example #8
0
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")
Example #9
0
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")
Example #10
0
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")