Source code for evergreen.cli.main

"""Command line driver for evergreen API."""

from __future__ import absolute_import

import json
from enum import Enum
from itertools import islice
from typing import Optional

import click
import yaml

from evergreen import EvergreenApi
from evergreen.resource_type_permissions import PermissionableResourceType, RemovablePermission

DATE_FORMAT = "%Y-%m-%d"

DisplayFormat = Enum("DisplayFormat", "human json yaml")


[docs] def fmt_output(fmt, data): """ Convert the given data into the specified format. :param fmt: DisplayFormat to use. :param data: Data to convertn. :return: Data is specified format. """ if fmt == DisplayFormat.json: return json.dumps(data, indent=4) if fmt == DisplayFormat.yaml: return yaml.safe_dump(data) return data
@click.group() @click.option( "--json", "display_format", type=DisplayFormat, flag_value=DisplayFormat.json, help="Write output in json.", ) @click.option( "--yaml", "display_format", type=DisplayFormat, flag_value=DisplayFormat.yaml, help="Write output in yaml.", ) @click.option( "--human-readable", "display_format", type=DisplayFormat, flag_value=DisplayFormat.human, default=True, help="Write output in a human readable format.", ) @click.pass_context def cli(ctx, display_format): """Create common CLI options.""" ctx.ensure_object(dict) ctx.obj["api"] = EvergreenApi.get_api(use_config_file=True) ctx.obj["format"] = display_format @cli.command() @click.pass_context def list_hosts(ctx): """List the hosts running in evergreen.""" api = ctx.obj["api"] fmt = ctx.obj["format"] host_list = api.all_hosts() click.echo(fmt_output(fmt, [host.json for host in host_list])) @cli.command() @click.pass_context @click.option("-p", "--project", required=True) @click.option("-l", "--limit", type=int) def list_patches(ctx, project, limit): """Get the patches for the given project.""" api = ctx.obj["api"] fmt = ctx.obj["format"] patches = [] for i, p in enumerate(api.patches_by_project(project)): if limit and i >= limit: break patches.append(p.json) click.echo(fmt_output(fmt, patches)) @cli.command() @click.pass_context def list_projects(ctx): """List the evergreen projects.""" api = ctx.obj["api"] fmt = ctx.obj["format"] project_list = api.all_projects() projects = [project.json for project in project_list] click.echo(fmt_output(fmt, projects)) @cli.command() @click.pass_context @click.option("--project", required=True) @click.option("--start", type=int) @click.option("--limit", type=int) @click.option("--revision_start", type=int) @click.option("--revision_end", type=int) def list_versions( ctx, project: str, start: Optional[int], limit: Optional[int], revision_start: Optional[int], revision_end: Optional[int], ) -> None: """Get the versions for the given project.""" api = ctx.obj["api"] fmt = ctx.obj["format"] version_list = api.versions_by_project( project, start=start, limit=limit, revision_start=revision_start, revision_end=revision_end ) versions_to_display = [version.json for version in islice(version_list, None, limit)] click.echo(fmt_output(fmt, versions_to_display)) @cli.command() @click.pass_context @click.option("--target", required=True) @click.option("--msg", required=True) def send_slack_message(ctx, target: str, msg: str) -> None: """Send a Slack message to the specified target.""" api = ctx.obj["api"] api.send_slack_message(target, msg) @cli.command() @click.pass_context @click.option( "-a", "--after-date", required=True, type=click.DateTime(formats=[DATE_FORMAT]), help="The earliest date to use 'YYYY-MM-DD'.", ) @click.option( "-b", "--before-date", required=True, type=click.DateTime(formats=[DATE_FORMAT]), help="The latest date to use 'YYYY-MM-DD'.", ) @click.option("-p", "--project", required=True) @click.option("-d", "--distros", multiple=True) @click.option("--group-by") @click.option("-g", "--group-num-days") @click.option("-r", "--requesters", multiple=True) @click.option("-s", "--sort") @click.option("--tests", multiple=True) @click.option("-t", "--tasks", multiple=True) @click.option("-v", "--variants", multiple=True) def test_stats( ctx, after_date, before_date, project, distros, group_by, group_num_days, requesters, sort, tests, tasks, variants, ): """Get the test stats specified.""" api = ctx.obj["api"] fmt = ctx.obj["format"] test_stat_list = api.test_stats_by_project( project, after_date, before_date, group_num_days, requesters, tests, tasks, variants, distros, group_by, sort, ) test_statistics = [t.json for t in test_stat_list] click.echo(fmt_output(fmt, test_statistics)) @cli.command() @click.pass_context @click.option( "-a", "--after-date", required=True, type=click.DateTime(formats=[DATE_FORMAT]), help="The earliest date to use 'YYYY-MM-DD'.", ) @click.option( "-b", "--before-date", required=True, type=click.DateTime(formats=[DATE_FORMAT]), help="The latest date to use 'YYYY-MM-DD'.", ) @click.option("-p", "--project", required=True) @click.option("-d", "--distros", multiple=True) @click.option("--group-by") @click.option("-g", "--group-num-days") @click.option("-r", "--requesters", multiple=True) @click.option("-s", "--sort") @click.option("-t", "--tasks", multiple=True) @click.option("-v", "--variants", multiple=True) def task_stats( ctx, after_date, before_date, project, distros, group_by, group_num_days, requesters, sort, tasks, variants, ): """Get the specified task stats.""" api = ctx.obj["api"] fmt = ctx.obj["format"] task_stat_list = api.task_stats_by_project( project, after_date, before_date, group_num_days, requesters, tasks, variants, distros, group_by, sort, ) task_statistics = [t.json for t in task_stat_list] click.echo(fmt_output(fmt, task_statistics)) RELIABILITY_GROUP_MAPPING = { "task": "task", "variant": "task_variant", "distro": "task_variant_distro", } @cli.command() @click.pass_context @click.option( "-a", "--after-date", required=True, type=click.DateTime(formats=[DATE_FORMAT]), help="The earliest date to use 'YYYY-MM-DD'.", ) @click.option( "-b", "--before-date", required=True, type=click.DateTime(formats=[DATE_FORMAT]), help="The latest date to use 'YYYY-MM-DD'.", ) @click.option( "-p", "--project", required=True, help="The evergreen project, eg 'mongodb-mongo-master'" ) @click.option("-d", "--distros", multiple=True, help="The list of distributions.") @click.option( "--group-by", type=click.Choice(list(RELIABILITY_GROUP_MAPPING.keys())), default="task", help="Group the results by 'task', 'variant' or 'distro'. Defaults to 'task'", ) @click.option( "-g", "--group-num-days", default=28, help="The number of days to group results by. Defaults to 28.", ) @click.option("-r", "--requesters", multiple=True, help="The requesters.") @click.option("-s", "--sort", help="The sort order, can be earliest or latest.") @click.option( "-t", "--tasks", multiple=True, required=True, help="The list of tasks, e.g. 'lint' , 'compile'. Required, no default.", ) @click.option("-v", "--variants", multiple=True, help="The list of build variants.") def task_reliability( ctx, after_date, before_date, project, distros, group_by, group_num_days, requesters, sort, tasks, variants, ): """ Get the Task Reliability scores for the matching tasks. \b Examples: \b # Get the scores for mongodb-mongo-master project, compile task (grouped by task) # for today (1 day, grouped by days 1). $> evg-api --json task-reliability -p mongodb-mongo-master -t compile OR $> evg-api --json task-reliability -p mongodb-mongo-master -t compile \\ --group-by task \b # Get the scores for mongodb-mongo-master project, compile task (grouped by variant) # for today (1 day, grouped by days 1). $> evg-api --json task-reliability -p mongodb-mongo-master -t compile \\ --group-by variant \b # Get the scores for mongodb-mongo-master project, compile and lint tasks (grouped by distro) # for today (1 day, grouped by days 1). $> evg-api --json task-reliability -p mongodb-mongo-master -t compile -t lint \\ --group-by distro \b # Get the scores for mongodb-mongo-master project, lint and compile tasks (grouped by distro) # for the last 28 days. $> evg-api --json task-reliability -p mongodb-mongo-master -t lint -t compile -g 1 \\ -a $(date -I --date="27 days ago") OR $> evg-api --json task-reliability -p mongodb-mongo-master -t lint -t compile -g 1 \\ -a $(date -I --date="27 days ago") -b $(date -I) OR $> evg-api --json task-reliability -p mongodb-mongo-master -t lint -t compile -g 1 \\ --group_num_days 28 \b # Get the scores for mongodb-mongo-master project, lint and compile tasks (grouped by distro) # for each day for the last 28 days. $> evg-api --json task-reliability -p mongodb-mongo-master -t lint -t compile \\ --group_by distro -a $(date -I --date="27 days ago") \b # Get the scores for mongodb-mongo-master project, lint task (grouped by task) , grouped in # batches of 28 days for all dates after 168 days ago. $> evg-api --json task-reliability -p mongodb-mongo-master -t lint --group-by task \\ -g 28 -a $(date -I --date="$((28 * 6 - 1)) days ago") \f :see: 'task reliability <https://github.com/evergreen-ci/evergreen/wiki/REST-V2-Usage#taskreliability>' """ api = ctx.obj["api"] fmt = ctx.obj["format"] task_reliability_list = api.task_reliability_by_project( project, after_date, before_date, group_num_days, requesters, tasks, variants, distros, RELIABILITY_GROUP_MAPPING[group_by], sort, ) task_reliability_scores = [t.json for t in task_reliability_list] click.echo(fmt_output(fmt, task_reliability_scores)) @cli.command() @click.pass_context @click.option("-v", "--version", "version_id", required=True) @click.option("--builds", is_flag=True, default=False, help="Include builds of version in output") def version_stats(ctx, version_id, builds): """ Collect stats for the given evergreen version. :param ctx: Command context. :param version_id: Id of version to analyze. :param builds: Include builds of version in output. """ api = ctx.obj["api"] fmt = ctx.obj["format"] version = api.version_by_id(version_id) if fmt == DisplayFormat.human: click.echo(version.get_metrics()) else: click.echo(fmt_output(fmt, version.get_metrics().as_dict(include_children=builds))) @cli.command() @click.pass_context @click.option("-b", "--build", "build_id", required=True) @click.option("--tasks", is_flag=True, default=False, help="Include tasks of build in output") def build_stats(ctx, build_id, tasks): """ Collect stats for the given evergreen build. :param ctx: Command context. :param build_id: Id of build to analyze. :param tasks: If true include tasks in output. """ api = ctx.obj["api"] fmt = ctx.obj["format"] build = api.build_by_id(build_id) if fmt == DisplayFormat.human: click.echo(build.get_metrics()) else: click.echo(fmt_output(fmt, build.get_metrics().as_dict(include_children=tasks))) @cli.command() @click.pass_context @click.option("--project", required=True, help="The project name") @click.option("--commit", required=True, help="The full 40-char commit hash") def manifest(ctx, project, commit): """ Get a manifest for the given project and commit. Example: use jq to get a module version associated with a main-repo version: evg-api --json manifest --project <PROJECT> --commit <MAIN-REPO-HASH> \\ | jq --raw-output .modules.<MODULE-NAME>.revision """ api = ctx.obj["api"] fmt = ctx.obj["format"] manifest = api.manifest(project, commit) click.echo(fmt_output(fmt, manifest.json)) @cli.command() @click.pass_context @click.option("--user-id", required=False, help="User whose permissions to fetch") def user_permissions(ctx, user_id): """ Get the evergreen permissions for a given user. Gets permissions for the current user if --user-id is not explicitly specified. """ api = ctx.obj["api"] fmt = ctx.obj["format"] if not user_id: user_id = api._auth.username permissions = api.permissions_for_user(user_id) click.echo(fmt_output(fmt, [p.json for p in permissions])) @cli.command() @click.pass_context @click.option("--user-id", required=True, help="User whose permissions to remove") @click.option( "--resource-type", required=True, type=click.Choice(["project", "distro", "superuser", "all"], case_sensitive=False), help="Type of resource for which to delete permissions.", ) @click.option( "--resource-id", required=False, help="Id of the resource for which to delete permissions. Required unless deleting all permissions.", ) def delete_user_permissions(ctx, user_id, resource_type, resource_id): """Delete all permissions of a given type for a user.""" api = ctx.obj["api"] api.delete_user_permissions(user_id, RemovablePermission(resource_type), resource_id) if resource_id: click.echo( f"Sucessfully deleted {resource_type} permissions for user {user_id} on resource id {resource_id}" ) else: click.echo(f"Sucessfully deleted {resource_type} permissions for user {user_id}") @cli.command() @click.pass_context @click.option("--user-id", required=True, help="User to grant roles to.") @click.option( "--role", required=True, multiple=True, help="Role to grant the user.", ) def give_roles_to_user(ctx, user_id, role): """Grant roles to a user.""" api = ctx.obj["api"] api.give_roles_to_user(user_id, list(role)) click.echo(f"Successfully granted roles {role} to user {user_id}") @cli.command() @click.pass_context @click.option( "--role", required=True, help="Role to fetch users for.", ) def get_users_for_role(ctx, role): """Get users having an evergreen role.""" api = ctx.obj["api"] users = api.get_users_for_role(role) click.echo(users.users) @cli.command() @click.pass_context @click.option("--resource-id", required=True, help="Resource id to fetch user permissions for.") @click.option( "--resource-type", required=True, type=click.Choice(["project", "distro", "superuser"], case_sensitive=False), help="Type of resource.", ) def all_user_permissions_for_resource(ctx, resource_id, resource_type): """Get all user permissions to a resource.""" api = ctx.obj["api"] user_permissions = api.all_user_permissions_for_resource( resource_id, PermissionableResourceType(resource_type) ) click.echo(user_permissions) @cli.command() @click.pass_context @click.option("--patch-id", required=True, help="Patch id to request diff for.") def patch_diff(ctx, patch_id): """Get patch diff for a given patch.""" api = ctx.obj["api"] diff = api.get_patch_diff(patch_id) click.echo(diff) @cli.command() @click.pass_context @click.option("--diff-file", required=True, help="The path to the diff file.") @click.option("--description", required=True, help="The description of the build.") @click.option("--param", required=True, help="The params to pass to the build.") @click.option("--base", required=True, help="The base commit of the build.") @click.option("--project", required=True, help="The project of the build.") @click.option("--tasks", required=True, help="The tasks to execute.") @click.option("--variants", required=True, help="The variants to build against.") @click.option("--author", required=False, default=None, help="Indicate the author of the patch.") def patch_from_diff(ctx, diff_file, description, param, base, project, tasks, variants, author): """Start a patch build based on the diff.""" api = ctx.obj["api"] response = api.patch_from_diff( diff_file, param, base, tasks, project, description, variants, author ) click.echo(response)
[docs] def main(): """Create command line application.""" return cli(obj={})
if __name__ == "__main__": main()