예제 #1
0
def get_platform(provider_name: str = None,
                 platform_name: str = None,
                 stdout: bool = False,
                 cloud_profile: str = None,
                 vars: Dict = {},
                 **kwargs) -> ModuleType:
    global PLATFORM_MODULE
    if PLATFORM_MODULE:
        return PLATFORM_MODULE

    state = _get_state()

    if not provider_name:
        provider_name = state.get(CLOUD_PROVIDER)
    if not platform_name:
        platform_name = state.get(CLOUD_PLATFORM)
    if not provider_name or not platform_name:
        raise Exception("You need to set provider_name and platform_name")

    PLATFORM_MODULE = _import_module("handoff.services.cloud." + provider_name)
    cred_keys = PLATFORM_MODULE.find_cred_keys(vars)
    response = PLATFORM_MODULE.login(cloud_profile, cred_keys=cred_keys)
    if not response:
        raise Exception(
            f"Login to {provider_name} failed. Credentials may not be set correctly."
        )

    return PLATFORM_MODULE
예제 #2
0
def role_update(project_dir: str,
                workspace_dir: str,
                vars: Dict = {},
                **kwargs) -> None:
    """`handoff cloud role update -p <project_directory> -v external_id=<id> grantee_account_id=<grantee_id>`
    Update the role privilege information.
    """
    state = _get_state()
    platform = get_platform()
    account_id = platform.get_account_id()
    if not account_id:
        raise Exception("Login failed")
    # state.validate_env()
    if not vars.get("grantee_account_id"):
        LOGGER.warn("grantee_account_id was not set." +
                    "The grantee will be set for the same account. To set: ")
        LOGGER.warn("-v grantee_account_id=xxxx")
    if not vars.get("external_id"):
        raise ValueError("external_id must be set. Do as:\n    " +
                         "handoff cloud create_role -p <project-vir> " +
                         "-v external_id=yyyy")

    return platform.update_role(grantee_account_id=str(
        vars.get("grantee_account_id", account_id)),
                                external_id=vars.get("external_id"))
예제 #3
0
def run(project_dir: str,
        workspace_dir: str,
        envs: Dict = {},
        vars: Dict = {},
        extras: str = None,
        **kwargs) -> None:
    """`handoff cloud run -v resource_group=<resource_group_name> task=<task_name> target_id=<target_id> -e vars='key1=val1 key2=val2...'`
    Run a task once in the platform

    If the environment variable vars is specified via -e option, it will be
    used as:
    `handoff run -v $(eval echo $vars)`
    """
    state = _get_state()
    platform = get_platform()
    config = admin._config_get_local(project_dir, workspace_dir)
    state.validate_env()

    extras_obj = None
    if extras:
        with open(extras, "r") as f:
            extras_obj = yaml.load(f)

    target_id = vars.get("target_id")
    target_envs = dict()
    if target_id:
        for s in config.get("schedules", []):
            if str(s["target_id"]) == target_id:
                for e in s.get("envs"):
                    target_envs[e["key"]] = e["value"]
                break
    target_envs.update(envs)
    target_envs[STAGE] = state[STAGE]
    return platform.run_task(env=target_envs, extras=extras_obj)
예제 #4
0
def _get_github(access_token=None):
    state = _get_state()
    if not access_token:
        access_token = state.get(GITHUB_ACCESS_TOKEN)
    global GITHUB
    GITHUB = Github(access_token)
    return GITHUB
예제 #5
0
def logs(project_dir: str,
         workspace_dir: str,
         vars: Dict = {},
         **kwargs) -> None:
    """`handoff cloud logs -v start_time=<start_time> end_time=<end_time> follow=<True/False>`
    Show logs
    Use --vars (-v) option to:
    - start_time: ISO 8086 formatted date time to indicate the start time
    - end_time
    - follow: If set, it waits for more logs until interrupted by ctrl-c
    - filter: Filter log term
    """
    state = _get_state()
    platform = get_platform()
    state.validate_env()
    close = False
    if vars.get("file"):
        file_descriptor = open(vars.pop("file"), "a")
        close = True
    else:
        file_descriptor = sys.stdout

    if vars.get("role_arn"):
        vars.pop("role_arn")
    platform.write_logs(file_descriptor, **vars)

    if close:
        file_descriptor.close()
예제 #6
0
def clone(
    project_dir: str,
    workspace_dir: str,
    vars: Dict = {},
    **kwargs) -> None:
    """`handoff github clone -v organization=<github_org> repository=<github_repo> local_dir=<local_dir> force=False`
    Clone remote repository. If local_dir is omitted, ./<repository> is used.
    If force is set to True, an existing directory will be deleted before cloning.
    """
    state = _get_state()
    access_token = vars.get("access_token", state.get(GITHUB_ACCESS_TOKEN))
    github = _get_github(access_token)

    repo_name = vars["repository"]
    org_name = vars.get("organization")
    local_dir = vars.get("local_dir", "./" + repo_name)
    if os.path.exists(local_dir):
        if not vars.get("force", False):
            raise Exception("The directory already exists.")
        shutil.rmtree(local_dir)
    git_url = vars.get("url", "https://github.com/" + str(org_name) + "/" + repo_name + ".git")

    if vars.get("use_cli", False):
        LOGGER.debug("Running git CLI")
        git_url = git_url.replace("https://", f"https://{access_token}:x-oauth-basic@")
        git_path = os.environ.get("GIT_PATH", "git")
        os.system(f"{git_path} clone {git_url}")
    else:
        repo_clone = pygit2.clone_repository(
                git_url, local_dir, callbacks=_get_callbacks(access_token))

    return {"status": "success", "repository": repo_name}
예제 #7
0
def task_delete(project_dir: str, workspace_dir: str, **kwargs) -> None:
    """`handoff cloud task delete -p <project_directory>`
    Delete the task
    """
    state = _get_state()
    platform = get_platform()
    state.validate_env()
    return platform.delete_task()
예제 #8
0
def bucket_delete(project_dir: str, workspace_dir: str, **kwargs) -> None:
    """`handoff cloud bucket delete -p <project_directory>`
    Delete remote storage bucket.
    """
    state = _get_state()
    platform = get_platform()
    state.validate_env()
    return platform.delete_bucket()
예제 #9
0
def bucket_create(project_dir: str, workspace_dir: str, **kwargs) -> None:
    """`handoff cloud bucket create -p <project_directory>`
    Create remote storage bucket. Bucket is attached to the resource group.
    """
    state = _get_state()
    platform = get_platform()
    state.validate_env()
    return platform.create_bucket()
예제 #10
0
def get(project_dir: str,
        workspace_dir: str,
        vars: Dict = {},
        **kwargs) -> None:
    """`handoff envs get -p <project_dir> -d key=<env_var_key>`
    Get the value of an evirohment varaible specified by <env_var_key>
    """
    state = _get_state()
    print(state[vars["key"]])
예제 #11
0
def resources_delete(project_dir: str, workspace_dir: str, **kwargs) -> None:
    """`handoff cloud resources delete -p <project_directory>`
    Delete the resources

    The resources are shared among the tasks under the same resource group.
    """
    state = _get_state()
    platform = get_platform()
    state.validate_env()
    return platform.delete_resources()
예제 #12
0
def task_update(project_dir: str,
                workspace_dir: str,
                vars: Dict = {},
                **kwargs) -> None:
    """`handoff cloud task update -p <project_directory>`
    Update the task
    Optionally,
    -v cpu=256, memory=512
    """
    state = _get_state()
    platform = get_platform()
    state.validate_env([IMAGE_VERSION])
    return platform.update_task(**vars)
예제 #13
0
def task_stop(project_dir: str,
              workspace_dir: str,
              vars: Dict = {},
              **kwargs) -> None:
    """`handoff cloud task stop -p <project_directory> -v id=<task_id> reason=<reason>`
    stop a running task
    Options:
    - reason
    """
    state = _get_state()
    platform = get_platform()
    state.validate_env()
    return platform.stop_task(**vars)
예제 #14
0
def task_status(project_dir: str,
                workspace_dir: str,
                vars: Dict = {},
                **kwargs) -> None:
    """`handoff cloud task status -p <project_directory> -v full=False running=True stopped=True resource_group_level=False`
    list task status

    AWS options:
    - full: When true, print the full description (default: false)
    - running: When true, include desired status = RUNNING (default: true)
    - stopped: When true, include desired status = STOPPED (default: true)
    - resource_group_level: When true, list all the tasks under the same resource groups (default: false)
    """
    state = _get_state()
    platform = get_platform()
    state.validate_env()
    return platform.list_tasks(**vars)
예제 #15
0
def role_delete(project_dir: str,
                workspace_dir: str,
                vars: Dict = {},
                **kwargs) -> None:
    """`handoff cloud role delete -p <project_directory> -v grantee_account_id=<grantee_id>`
    Delete the role.
    """
    state = _get_state()
    platform = get_platform()
    account_id = platform.get_account_id()
    # state.validate_env()
    if not vars.get("grantee_account_id"):
        LOGGER.warn("grantee_account_id was not set." +
                    "The grantee will be set for the same account. To set: ")
        LOGGER.warn("-v grantee_account_id=xxxx")

    return platform.delete_role(
        grantee_account_id=str(vars.get("grantee_account_id", account_id)))
예제 #16
0
def commit (
    project_dir: str,
    workspace_dir: str,
    vars: Dict = {},
    **kwargs) -> None:
    """`handoff github commit -v local_dir=<local_dir> commit_msg=<msg> branch=<branch>`
    Commit all the outstanding changes to the remote branch. If the branch does not exist,
    it will be created.
    """
    state = _get_state()
    access_token = vars.get("access_token", state.get(GITHUB_ACCESS_TOKEN))
    github = _get_github(access_token)

    local_dir = vars["local_dir"]  # typically the "./{repository_name}"
    repo = pygit2.Repository(local_dir + "/.git")

    user = github.get_user()

    branch = vars.get("branch", "master")
    commit_msg = vars["commit_msg"]

    # repo.remotes.set_url("origin", repo.clone_url)
    index = repo.index
    index.add_all()
    index.remove_all("*.secrets*")
    index.write()
    author = pygit2.Signature(user.name, user.email)
    commiter = pygit2.Signature(user.name, user.email)
    tree = index.write_tree()
    oid = repo.create_commit(
            "refs/heads/" + branch,
            author,
            commiter,
            commit_msg,
            tree,
            [repo.head.target],
            # [repo.head.get_object().hex],
            )

    credentials = pygit2.UserPass(access_token, "x-oauth-basic")
    # callbacks = pygit2.RemoteCallbacks(credentials=credentials)
    remote = repo.remotes["origin"]
    remote.credentials = credentials
    remote.push(["refs/heads/" + branch], callbacks=_get_callbacks(access_token))
예제 #17
0
def schedule_list(project_dir: str,
                  workspace_dir: str,
                  vars: Dict = {},
                  **kwargs) -> None:
    """`handoff cloud schedule list`
    List the scheduled tasks
    """
    state = _get_state()
    platform = get_platform()
    state.validate_env()
    schedules = platform.list_schedules(**vars)
    target_ids = []
    for s in schedules["schedules"]:
        target_ids.append(str(s["target_id"]))
        s["status"] = "scheduled"

    if vars.get("include_unpublished"):
        config = admin._config_get_local(project_dir, workspace_dir)
        local_schedules = config.get("schedules", [])
        for s in local_schedules:
            status = ""
            try:
                index = target_ids.index(str(s["target_id"]))
            except ValueError:
                index = len(target_ids)
                target_ids.append(s["target_id"])
                schedules["schedules"].append({})
                status = "draft"

            remote = {}
            remote.update(schedules["schedules"][index])
            if remote.get("status"):
                remote.pop("status")
            if remote.get("name"):
                remote.pop("name")
            if s.get("description"):
                s.pop("description")
            if s != remote:
                schedules["schedules"][index] = s
                status = status or "edited"
                schedules["schedules"][index]["status"] = status

    return schedules
예제 #18
0
def resources_create(project_dir: str,
                     workspace_dir: str,
                     vars: Dict = {},
                     **kwargs) -> None:
    """`handoff cloud resources create -p <project_directory>`
    Create resources necessary for task execution.
    The resources are shared among the tasks under the same resource group.

    Optionally:
    -v static_ip=True: [AWS] Create Elastic IP and NAT Gateway to obtain a static IP. This [costs more](https://aws.amazon.com/vpc/pricing/).

    AWS:
    - Please refer to the
      [CloudFormation template](https://github.com/anelenvars/handoff/blob/master/handoff/services/cloud/aws/cloudformation_templates/resources.yml) for the resources created with this command.
    """
    state = _get_state()
    platform = get_platform()
    state.validate_env()
    return platform.create_resources(**vars)
예제 #19
0
def schedule_delete(project_dir: str,
                    workspace_dir: str,
                    vars: Dict = {},
                    **kwargs) -> None:
    """`handoff cloud schedule delete -v target_id=<target_id>`
    Unschedule a task named <target_id>
    """
    state = _get_state()
    platform = get_platform()
    state.validate_env()
    if not vars.get("target_id"):
        return {
            "status": "error",
            "message": "Forgot to set '-v target_id=<ID>' ?",
        }
    target_id = str(vars["target_id"])
    try:
        response = platform.unschedule_task(target_id)
    except Exception as e:
        response = str(e)
    return response
예제 #20
0
def pull(
    project_dir: str,
    workspace_dir: str,
    vars: Dict = {},
    **kwargs) -> None:
    """`handoff github commit -v local_dir=<local_dir> commit_msg=<msg> branch=<branch>`
    Commit all the outstanding changes to the remote branch. If the branch does not exist,
    it will be created.
    """
    state = _get_state()
    access_token = vars.get("access_token", state.get(GITHUB_ACCESS_TOKEN))
    github = _get_github(access_token)

    local_dir = vars["local_dir"]  # typically the "./{repository_name}"
    repo = pygit2.Repository(local_dir + "/.git")
    remote_name = "origin"

    # Adopted from https://github.com/MichaelBoselowitz/pygit2-examples/blob/68e889e50a592d30ab4105a2e7b9f28fac7324c8/examples.py#L48
    for remote in repo.remotes:
        if remote.name == remote_name:
            remote.fetch()
            remote_master_id = repo.lookup_reference('refs/remotes/origin/master').target
            merge_result, _ = repo.merge_analysis(remote_master_id)
            # Up to date, do nothing
            if merge_result & pygit2.GIT_MERGE_ANALYSIS_UP_TO_DATE:
                return {
                    "status": "success",
                    "message": "Repository is up-to-date",
                }

            # We can just fastforward
            elif merge_result & pygit2.GIT_MERGE_ANALYSIS_FASTFORWARD:
                repo.checkout_tree(repo.get(remote_master_id))
                master_ref = repo.lookup_reference('refs/heads/master')
                master_ref.set_target(remote_master_id)
                repo.head.set_target(remote_master_id)
            elif merge_result & pygit2.GIT_MERGE_ANALYSIS_NORMAL:
                repo.merge(remote_master_id)

                if repo.index_conflicts:
                    return {
                        "status": "error",
                        "mesage": repo.index.conflicts,
                    }

                user = repo.default_signature
                tree = repo.index.write_tree()
                commit = repo.create_commit('HEAD',
                                            user,
                                            user,
                                            'Merge!',
                                            tree,
                                            [repo.head.target, remote_master_id])
                repo.state_cleanup()
                return {
                    "status": "success",
                    "message": "successfully fast forwarded the repository",
                }
            else:
                return {
                    "status": "error",
                    "mesage": "Unknown merge analysis result"
                }
예제 #21
0
def schedule_create(project_dir: str,
                    workspace_dir: str,
                    envs: Dict = {},
                    vars: Dict = {},
                    extras: str = None,
                    **kwargs) -> None:
    """`handoff cloud schedule create -v target_id=<target_id> cron="<cron_format>" -e vars='key1=val1 key2=val2...'`
    Schedule a task named <target_id> at the recurring scheduled specified
    as <cron_format>. An example of cron-format string is `10 01 * * ? *`
    for every day at 01:10 (1:10AM)

    If the environment variable vars is specified via -e option, it will be
    used as:
    `handoff run -v $(eval echo $vars)`
    """
    state = _get_state()
    platform = get_platform()
    config = admin._config_get_local(project_dir, workspace_dir)
    state.validate_env()
    schedules = config.get("schedules")

    target_id = vars.get("target_id")
    cronexp = vars.get("cron")
    extras_obj = None
    if extras:
        with open(extras, "r") as f:
            extras_obj = yaml.load(f)
    if not schedules:
        schedules = [{
            "target_id": target_id,
            "cron": cronexp,
            "extras_obj": extras_obj
        }]

    if not schedules and (not target_id or not cronexp):
        return {
            "status":
            "error",
            "message":
            "Schedules not found in project.yml. You can also set "
            "'-v target_id=<ID> cron=<CRON>'",
        }

    envs[STAGE] = state[STAGE]

    responses = []
    for s in schedules:
        if target_id is not None and str(s["target_id"]) != str(target_id):
            continue
        if target_id is not None and cronexp:
            s["cron"] = cronexp

        e = dict()
        for e1 in s.get("envs", list()):
            e[e1["key"]] = e1["value"]
        # priority of the values schedule section < envs section < command line
        e.update(envs)
        r = platform.schedule_task(str(s["target_id"]),
                                   "cron(" + s["cron"] + ")",
                                   env=e,
                                   extras=s.get("extras_obj"))
        responses.append(r)
    return responses