@arg('phase', help="lain cluster phase id, can be added by lain config save")
@arg('-t', '--target', help='The target appname to attach, if not set, will be the appname of the working dir')
def attach(phase, proc_name, instance_no, target=None):
    """
    Attach the stdout/stderr of the container
    """

    check_phase(phase)
    yml = lain_yaml(ignore_prepare=True)
    appname = target if target else yml.appname
    authorize_and_check(phase, appname)
    domain = get_domain(phase)
    access_token = SSOAccess.get_token(phase)
    endpoint = "wss://entry.%s/attach" % domain
    header_data = ["access-token: %s" % access_token,
                   "app-name: %s" % appname,
                   "proc-name: %s" % proc_name,
                   "instance-no: %s" % instance_no]
    try:
        client = EntryClient(endpoint, header=header_data)
        info("Start to attach the stdout/stderr of the container. Press <Ctrl+c> to stop...")
        client.attach_container()
    except KeyboardInterrupt:
        pass
    except:
        error("Server stops the connection. Ask admin for help.") @arg('phase', help="lain cluster phase id, can be added by lain config save")
@arg('-s', '--sort', choices=SORT_CHOICES, help="sort type when displaying available apps")
def dashboard(phase, sort='appname'):
    """
    Basic dashboard of Lain
    """

    check_phase(phase)
    print_welecome()
    print_workflows()
    console = "console.%s" % (get_domain(phase))
    access_token = SSOAccess.get_token(phase)
    auth_header = get_auth_header(access_token)

    print_available_repos(console, auth_header)
    print_available_apps(console, auth_header, sort) @arg('phase', help="lain cluster phase id, can be added by lain config save")
def reposit(phase):
    """
    Initialize a repository in lain
    """

    check_phase(phase)
    validate_only_warning()
    info("Repositing ...")

    yml = lain_yaml(ignore_prepare=True)
    authorize_and_check(phase, yml.appname)

    access_token = SSOAccess.get_token(phase)
    auth_header = get_auth_header(access_token)

    console = "console.%s" % get_domain(phase)
    result = reposit_app(phase, yml.appname, console, auth_header)

    info("Done, %s" % result) def prepare():
    """
    Build prepare image
    """

    validate_only_warning()
    info("Generating prepare image...")
    if not lain_yaml().build_prepare()[0]:
        error("Error lain prepare.")
        sys.exit(1)
    else:
        info("Done lain prepare.")
        sys.exit(0) @arg('--release', help="build from build image if it exists")
@arg('--push',    help="tag release and meta image with version and push to registry")
def build(push=False, release=False):
    """
    Build release and meta images
    """

    info("Building meta and release images ...")
    validate_only_warning()
    yml = lain_yaml()
    meta_version = yml.repo_meta_version()
    use_prepare = yml.build.prepare is not None
    use_build = release and docker.exist(yml.img_names['build'])
    release_suc, release_name = yml.build_release(use_prepare, use_build)
    (meta_suc, meta_name) = (False, '') if not release_suc else yml.build_meta()
    if not (release_suc and meta_suc):
        sys.exit(1)
    if meta_version is None:
        warn("please git commit.")
    if push:
        if meta_version is None:
            error("need git commit SHA1.")
            return None
        tag_release_name = yml.tag_meta_version(release_name)
        docker.push(tag_release_name)

        tag_meta_name = yml.tag_meta_version(meta_name)
        docker.push(tag_meta_name)
    info("Done lain build.") @arg('phase', help="lain cluster phase id, can be added by lain config save")
def tag(phase):
    """
    Tag release and meta images
    """

    check_phase(phase)
    info("Taging meta and relese image ...")
    yml = lain_yaml(ignore_prepare=True)
    meta_version = yml.repo_meta_version()
    if meta_version is None:
        error("please git commit.")
        return None
    domain = get_domain(phase)
    registry = "registry.%s" % domain
    meta_tag = "%s:meta-%s" % (yml.appname, meta_version)
    release_tag = "%s:release-%s" % (yml.appname, meta_version)
    phase_meta_tag = docker.gen_image_name(yml.appname, 'meta', meta_version, registry)
    phase_release_tag = docker.gen_image_name(yml.appname, 'release', meta_version, registry)
    meta_code = docker.tag(meta_tag, phase_meta_tag)
    release_code = docker.tag(release_tag, phase_release_tag)
    if meta_code or release_code:
        error("Error lain tag.")
    else:
        info("Done lain tag.") @arg('phase', help="lain cluster phase id, can be added by lain config save")
def push(phase):
    """
    Push release and meta images
    """

    check_phase(phase)
    info("Pushing meta and release images ...")
    yml = lain_yaml(ignore_prepare=True)
    meta_version = yml.repo_meta_version()
    if meta_version is None:
        error("please git commit.")
        return None
    domain = get_domain(phase)

    registry = "registry.%s" % domain
    phase_meta_tag = docker.gen_image_name(
        yml.appname, 'meta', meta_version, registry)
    phase_release_tag = docker.gen_image_name(
        yml.appname, 'release', meta_version, registry)
    meta_code = docker.push(phase_meta_tag)
    release_code = docker.push(phase_release_tag)
    if meta_code or release_code:
        error("Error lain push.")
        sys.exit(1)
    else:
        info("Done lain push.")
        info("notifying lain push.")
        access_token = SSOAccess.get_token(phase)
        auth_header = get_auth_header(access_token)
        last_commit_id = fetch_last_commit_id(domain, yml.appname, auth_header)
        if last_commit_id is not None:
            notify_diffs(domain, yml.appname, last_commit_id, auth_header)
        else:
            warn("Notified Nothing!")
        info("Done notifying lain push.") @arg('src_phase',  help='The source registry domain for sync from, can not be None')
@arg('dst_phase', help="lain cluster phase id, can be added by lain config save")
@arg('appname', help="app name")
@arg('meta_version', help='The Meta version of some build')
def sync(src_phase, dst_phase, appname, meta_version):
    check_phase(src_phase)
    check_phase(dst_phase)

    src_domain = get_domain(src_phase)
    dst_domain = get_domain(dst_phase)

    src_registry = "registry.{domain}".format(domain=src_domain)
    dst_registry = "registry.{domain}".format(domain=dst_domain)

    src_phase_meta_tag = docker.gen_image_name(
        appname, 'meta', meta_version, src_registry)
    src_phase_release_tag = docker.gen_image_name(
        appname, 'release', meta_version, src_registry)

    dst_phase_meta_tag = docker.gen_image_name(
        appname, 'meta', meta_version, dst_registry)
    dst_phase_release_tag = docker.gen_image_name(
        appname, 'release', meta_version, dst_registry)

    return_code = transfer_to(src_phase_meta_tag, dst_phase_meta_tag)
    if return_code != 0:
        sys.exit(return_code)
    return_code = transfer_to(src_phase_release_tag, dst_phase_release_tag)
    if return_code != 0:
        sys.exit(return_code)

    access_token = SSOAccess.get_token(dst_phase)
    auth_header = get_auth_header(access_token)
    info("notifying lain push.")
    notify_pushs(dst_domain, appname, auth_header)
    info("notify lain push done.") @arg('phase', help="lain cluster phase id, can be added by lain config save")
@arg('-v', '--version', help='The specify version of the app to deploy')
@arg('-t', '--target', help='The target app to deploy, if not set, will be the appname of the working dir')
@arg('-p', '--proc', help='The proc need to deploy, should not be used with argh -v')
@arg('-o', '--output', choices=['pretty', 'json', 'json-pretty'], default='pretty')
def deploy(phase, version=None, target=None, proc=None, output='pretty'):
    """
    Deploy specific proc in the app or the whole app
    """

    check_phase(phase)
    yml = lain_yaml(ignore_prepare=True)
    appname = target if target else yml.appname
    authorize_and_check(phase, appname)

    console = "console.%s" % get_domain(phase)
    access_token = SSOAccess.get_token(phase)
    auth_header = get_auth_header(access_token)

    if proc:
        deploy_proc(proc, appname, console, auth_header, output)
    else:
        deploy_app(phase, appname, console, auth_header, version, output) @arg('phase', help="lain cluster phase id, can be added by lain config save")
@arg('proc', help="proc name")
@arg('-t', '--target', help='The target app to deploy, if not set, will be the appname of the working dir')
@arg('-o', '--output', choices=['pretty', 'json', 'json-pretty'], default='pretty')
def scale(phase, proc, target=None, cpu=None, memory=None, numinstances=None, output='pretty'):
    """
    Scale proc with cpu/memory/num_instances
    """

    check_phase(phase)
    yml = lain_yaml(ignore_prepare=True)
    appname = target if target else yml.appname
    authorize_and_check(phase, appname)

    domain = get_domain(phase)
    console = "console.%s" % domain
    url = "http://{}/api/v1/apps/{}/procs/{}/".format(
        console, appname, proc
    )

    access_token = SSOAccess.get_token(phase)
    auth_header = get_auth_header(access_token)

    cpu, memory, numinstances = validate_parameters(cpu, memory, numinstances)

    proc_status = requests.get(url, headers=auth_header)
    if proc_status.status_code == 200:
        # proc exists
        info(
            "Start to scale Proc {} of App {} in Lain Cluster {} with domain {}".format(
                proc, appname, phase, domain
            )
        )
    elif proc_status.status_code == 404:
        # proc does not exist
        warn(
            "Proc {} of App {} does not exists in Lain Cluster {} with domain {}".format(
                proc, appname, phase, domain
            )
        )
        info("Please deploy it first")
        exit(1)
    else:
        error("shit happend: %s"%proc_status.content)
        exit(1)

    scale_results = {}

    payload1 = {}
    payload2 = {}

    if cpu is not None:
        payload1['cpu'] = cpu
    if memory is not None:
        payload1['memory'] = memory
    if len(payload1) > 0:
        info("Scaling......")
        info(str(payload1))
        scale_r = requests.patch(url, headers=auth_header,
                                     data=json.dumps(payload1), timeout=120)
        scale_results['cpu_or_memory'] = {
            'payload': payload1,
            'success': scale_r.status_code < 300
        }
        render_scale_result(scale_r, output)

    if numinstances is not None:
        payload2['num_instances'] = numinstances
    if len(payload2) > 0:
        info("Scaling...")
        info(str(payload2))
        scale_r = requests.patch(url, headers=auth_header,
                                    data=json.dumps(payload2), timeout=120)
        scale_results['num_instances'] = {
            'payload': payload2,
            'success': scale_r.status_code < 300
        }
        render_scale_result(scale_r, output)

    info("Outline of scale result: ")
    for k, v in scale_results.iteritems():
        success = v['success']
        output_func = None
        if success:
            output_func = info
        else:
            output_func = error
        output_func("  scale of {} {}".format(k, "success" if success else "failed"))
        output_func("    params: {}".format(v['payload'])) @arg('phase', help="lain cluster phase id, can be added by lain config save")
@arg('-o', '--output', choices=['pretty', 'json', 'json-pretty'], default='pretty')
def ps(phase, output='pretty'):
    """
    Show basic deploy messages of app
    """

    check_phase(phase)
    yml = lain_yaml(ignore_prepare=True)
    authorize_and_check(phase, yml.appname)
    console = "console.%s" % get_domain(phase)

    access_token = SSOAccess.get_token(phase)
    auth_header = get_auth_header(access_token)

    repo_url = "http://%s/api/v1/repos/%s/" % (console, yml.appname)
    repo_r = requests.get(repo_url, headers=auth_header)
    if repo_r.status_code == 404:
        error('app {} has not been reposited at {}'.format(
            yml.appname, phase
        ))
        exit(1)
    elif repo_r.status_code == 200:
        pass
    else:
        error("shit happend: %s" % repo_r.content)
        exit(1)

    app_url = "http://%s/api/v1/apps/%s/" % (console, yml.appname)
    app_r = requests.get(app_url, headers=auth_header)
    if app_r.status_code == 200:
        app_status = app_r.json()["app"]
        render_app_status(app_status, output)
    elif app_r.status_code == 404:
        error('app {} has not been deployed at {}'.format(
            yml.appname, phase
        ))
        exit(1)
    else:
        error("shit happend: %s" % repo_r.content)
        exit(1) @arg('phase', help="lain cluster phase id, can be added by lain config save")
@arg('-t', '--target', help='The target app to deploy, if not set, will be the appname of the working dir')
@arg('-p', '--proc', help='The proc wanted to undeploy')
def undeploy(phase, target=None, proc=None):
    """
    Undeploy specific proc in the app or the whole app
    """

    check_phase(phase)
    yml = lain_yaml(ignore_prepare=True)
    appname = target if target else yml.appname
    authorize_and_check(phase, appname)

    console = "console.%s" % get_domain(phase)
    access_token = SSOAccess.get_token(phase)
    auth_header = get_auth_header(access_token)
    if proc:
        undeploy_proc(proc, appname, console, auth_header)
    else:
        undeploy_app(appname, console, auth_header) @arg('phase', help="lain cluster phase id, can be added by lain config save")
def login(phase, cid=None, secret=None, redirect_uri=None):
    """
    Login specific phase, need open auth first

    cid: Client id get from the sso system, default: 3
    secret: Client secret get from the sso system, default: lain-cli_admin
    redirect_uri: Redirect uri get from the sso system, default: https://example.com/
    """

    check_phase(phase)
    username = raw_input('SSO Username:')
    password = getpass.getpass('SSO Password:')
    sso_login_success = sso_login(phase, cid, secret, redirect_uri, username, password)
    if not sso_login_success:
        error('sso Login failed, Please try again!')
        exit(1)

    docker_login_success = docker_login(phase, username, password)
    if not docker_login_success:
        error('docker Login failed, Please try again!')
        exit(1)

    info("Login successfully!") @arg('phase', help="lain cluster phase id, can be added by lain config save")
def refresh(phase):
    """
    Refresh sso token
    """

    check_phase(phase)
    refresh_success = sso_refresh(phase)
    if refresh_success:
        info("Refresh successfully!")
    else:
        warn('Refresh failed, Please try again!') @arg('phase', help="lain cluster phase id, can be added by lain config save")
def logout(phase):
    """
    Logout specific phase
    """

    check_phase(phase)
    domain = get_domain(phase)
    logout_success = SSOAccess.clear_token(phase)
    if logout_success:
    	docker.logout('registry.%s'%domain)
        info("Logout successfully!")
    else:
        warn('Logout failed!') @arg('phase', help="lain cluster phase id, can be added by lain config save")
def rmi(phase):
    """
    Remove app images in the local host
    """

    check_phase(phase)
    repo_tags = get_repo_tags_to_remove(phase)
    for image in repo_tags:
        docker.remove_image(image) def meta():
    """
    Show current meta version
    """

    meta_version = lain_yaml(ignore_prepare=True).repo_meta_version()
    if meta_version is None:
        error("please git commit.")
    else:
        info("meta version : %s" % lain_yaml(ignore_prepare=True).repo_meta_version()) @arg('phase', help="lain cluster phase id, can be added by lain config save")
@arg('-t', '--target', help='The target appname to enter. If it\'s not set, it will be the appname of the working dir')
def enter(phase, proc_name, instance_no, target=None):
    """
    Enter the container of specific proc
    """

    check_phase(phase)
    yml = lain_yaml(ignore_prepare=True)
    appname = target if target else yml.appname
    authorize_and_check(phase, appname)
    domain = get_domain(phase)
    access_token = SSOAccess.get_token(phase)

    term_type = os.environ.get("TERM", "xterm")
    endpoint = "wss://entry.%s/enter" % domain
    header_data = ["access-token: %s" % access_token,
                   "app-name: %s" % appname,
                   "proc-name: %s" % proc_name,
                   "instance-no: %s" % instance_no,
                   "term-type: %s" % term_type]
    try:
        client = EntryClient(endpoint, header=header_data)
        client.invoke_shell()
    except:
        error("Server stops the connection. Ask admin for help.") @arg('--without', help="a string of items separated by ',', \
                        denoting images phases not to be cleared. \
                        like 'build,prepare' or 'prepare'")
def clear(without=""):
    """
    Clear images of all phases, can specify --without as filter
    """

    yml = lain_yaml(ignore_prepare=True)
    without_phases = without.split(',')
    for name in yml.img_names.values():
        if docker.get_phase(name) not in without_phases:
            docker.remove_image(name) @arg('phase', help="lain phase, can be added by lain config save")
def check(phase):    
    """
    Check current version of release and meta images in the remote registry
    """

    check_phase(phase)
    tag_ok = _check_phase_tag(phase)
    if tag_ok:
        info("Image Tag OK in registry")
    else:
        error("Image Tag not OK in registry") def validate():
    """
    Validate lain.yaml
    """

    valid, msg = _validate()
    if valid:
        info('valid lain.yaml.')
    else:
        error('invalid lain.yaml.')
        warn('error message:')
        info(msg)
        # TODO : show related doc url
        warn('for details of lain.yaml schema please check related docs.')
        sys.exit(1) @arg('phase', help="lain cluster phase id, can be added by lain config save")
def appversion(phase):
    """
    Show available app versions in remote registry of lain
    """

    check_phase(phase)
    yml = lain_yaml(ignore_prepare=True)
    authorize_and_check(phase, yml.appname)

    version_list = get_version_lists(phase, yml.appname)
    print_available_version(version_list) def prepare_update():
    """
    Update prepare image based on existed prepare image
    """

    validate_only_warning()
    if not lain_yaml().update_prepare()[0]:
        sys.exit(1)
    else:
        sys.exit(0) def test():
    """
    Build test image and run test scripts defined in lain.yaml
    """

    if not lain_yaml().build_test()[0]:
        sys.exit(1)
    else:
        sys.exit(0) @arg('proc_name')
def run(proc_name):
    """
    Run proc instance in the local host
    """

    container_name, image, working_dir, port, cmd, envs, volumes, _ = gen_run_ctx(proc_name)
    docker.proc_run(container_name, image, working_dir, port, cmd, envs, volumes)
    info("container name: {}".format(container_name))
    if port:
        docker.inspect_port(container_name) @arg('proc_name')
def debug(proc_name):
    """
    Debug proc instance in the local host
    """

    container_name, _, _, _, _, _, _, _ = gen_run_ctx(proc_name)
    docker.proc_debug(container_name) @arg('proc_name')
def stop(proc_name):
    """
    Stop proc instance in the local host
    """

    container_name, _, _, _, _, _, _, _ = gen_run_ctx(proc_name)
    docker.proc_stop(container_name) @arg('proc_name')
def rm(proc_name):
    """
    Remove proc instance in the local host
    """

    container_name, _, _, _, _, _, _, local_proc_volume_base = gen_run_ctx(proc_name)
    docker.proc_rm(container_name, local_proc_volume_base) def update():
    """
    Update Lain SDK and CLI
    """

    update_url = 'git+https://github.com/laincloud/{}.git'
    update_pkgs = ['entry', 'lain-cli', 'lain-sdk']
    for pkg in update_pkgs:
        cmd = ['sudo', 'pip', 'install', '--process-dependency-links', update_url.format(pkg)]
        subprocess.call(cmd) def version():
    """
    Semantic Version x.x.x
    """

    import lain_sdk
    import lain_cli
    print("lain sdk version: {}".format(lain_sdk.__version__))
    print("lain cli version: {}".format(lain_cli.__version__)) class ConfigCommands(TwoLevelCommandBase):

    @classmethod
    def subcommands(self):
        return [self.show, self.save, self.save_global]

    @classmethod
    def namespace(self):
        return "config"

    @classmethod
    def help_message(self):
        return "config operating"

    @classmethod
    def show(cls):
        """
        show configs of different phase, also some global configs
        """
        configs = user_config.get_config()
        hide_keys = [SSO_TOKEN_KEY, SSO_REFRESH_TOKEN_KEY]
        for k, v in configs.iteritems():
            if not isinstance(v, dict):
                continue
            for key in hide_keys:
                if v.has_key(key):
                    v.pop(key)
        print json.dumps(configs, sort_keys=True, indent=4)

    @classmethod
    @arg('prop', help="property for the special phase, e.g: domain, sso_url")
    def save(cls, phase, prop, value):
        """
        create new phase, save new config for different phase.
        e.g: lain config save local domain lain.local
        """
        save_config(phase, prop, value)
        cls.show()

    @classmethod
    def save_global(cls, prop, value):
        """
        save global config, mainly private_docker_registry
        """
        save_global_config(prop, value)
        cls.show() class SecretCommands(TwoLevelCommandBase):
    '''
    allow add secret files for app, lain will add the secret file into 
    image of the proc when deploying.
    '''

    @classmethod
    def subcommands(self):
        return [self.show, self.add, self.delete]

    @classmethod
    def namespace(self):
        return "secret"

    @classmethod
    def help_message(self):
        return "secret operation: including add, delete or show secret files of the app"

    @classmethod
    @arg('phase', help="lain cluster phase id, can be added by lain config save")
    @arg('procname', help="proc name of the app")
    def show(cls, phase, procname, path=None):
        """
        show secret file of special procname in different phase

        path: absolute path of config file, eg : /lain/app/config
        """

        check_phase(phase)
        yml = lain_yaml(ignore_prepare=True)
        authorize_and_check(phase, yml.appname)
        auth_header = get_auth_header(SSOAccess.get_token(phase))
        proc = yml.procs.get(procname, None)
        if proc is None:
            error('proc {} does not exist'.format(procname))
            exit(1)

        podgroup_name = "{}.{}.{}".format(yml.appname, proc.type.name, proc.name)       
        lvault_url = "http://lvault.%s/v2/secrets?app=%s&proc=%s" % (
            get_domain(phase), yml.appname, podgroup_name)
        if path:
            lvault_url += "&path=%s" % path
        
        show_response = requests.get(lvault_url, headers=auth_header)
        if show_response.status_code < 300:
            info("secret file detail:")
            print(json.dumps(show_response.json(), encoding="utf-8", ensure_ascii=False, indent=2))
        else:
            error("shit happened : %s" % show_response.text)

    @classmethod
    @arg('phase', help="lain cluster phase id, can be added by lain config save")
    @arg('procname', help="proc name of the app")
    @arg('path', help='absolute path of config file, eg : /lain/app/config')
    def add(cls, phase, procname, path, content=None, file=None):
        """
        add secret file for different phase

        content: content of the secret file
        file: read secret content from a specify file
        """

        if file is None and content is None:
            error("need specify the content use -c or -f parameter")
            exit(1)

        if file is not None:
            try:
                f = open(file)
                content = f.read()
            except Exception, e:
                error("error read file %s : %s" % (file, str(e)))
                exit(1)

        check_phase(phase)
        yml = lain_yaml(ignore_prepare=True)
        authorize_and_check(phase, yml.appname)
        auth_header = get_auth_header(SSOAccess.get_token(phase))
        proc = yml.procs.get(procname, None)
        if proc is None:
            error('proc {} does not exist'.format(procname))
            exit(1)

        podgroup_name = "{}.{}.{}".format(yml.appname, proc.type.name, proc.name)
        lvault_url = "http://lvault.%s/v2/secrets?app=%s&proc=%s&path=%s" % (
            get_domain(phase), yml.appname, podgroup_name, path)
        payload = {"content": content}

        add_response = requests.put(lvault_url, headers=auth_header, json=payload)
        if add_response.status_code < 300:
            info("add successfully.")
        else:
            error("shit happened : %s" % add_response.text)

    @classmethod
    @arg('phase', help="lain cluster phase id, can be added by lain config save")
    @arg('procname', help="proc name of the app")
    @arg('path', help='absolute path of config file, eg : /lain/app/config')
    def delete(cls, phase, procname, path):
        """
        delete secret file for different phase
        """

        check_phase(phase)
        yml = lain_yaml(ignore_prepare=True)
        authorize_and_check(phase, yml.appname)
        auth_header = get_auth_header(SSOAccess.get_token(phase))
        proc = yml.procs.get(procname, None)
        if proc is None:
            error('proc {} does not exist'.format(procname))
            exit(1)

        podgroup_name = "{}.{}.{}".format(yml.appname, proc.type.name, proc.name)
        lvault_url = "http://lvault.%s/v2/secrets?app=%s&proc=%s&path=%s" % (
            get_domain(phase), yml.appname, podgroup_name, path)

        delete_response = requests.delete(lvault_url, headers=auth_header)
        if delete_response.status_code < 300:
            info("delete successfully.")
        else:
            error("shit happened : %s" % delete_response.text) class BackupCommands(TwoLevelCommandBase):

    @classmethod
    def subcommands(self):
        return [self.jobs, self.list, self.get, self.recover, self.migrate, self.records, self.run, self.delete]

    @classmethod
    def namespace(self):
        return "backup"

    @classmethod
    def help_message(self):
        return "operations for backup"

    @classmethod
    @arg('phase', help="lain cluster phase id, can be added by lain config save")
    def jobs(cls, phase):
        """
        list backup jobs for this lain app
        """
        check_phase(phase)
        appname = lain_yaml_data()['appname']
        route = "api/v2/app/%s/cron/jobs" % appname
        data = cls._request('GET', phase, route, None)
        if data:
            print json.dumps(data, indent=2)

    @classmethod
    @arg('phase', nargs=1, help="lain cluster phase id, can be added by lain config save")
    @arg('proc', nargs=1, help="proc name")
    @arg('path', nargs=1, help="the path of incremental backup, if full-backup given, will show backup's file info")
    @aliases('ls')
    def list(cls, phase, proc, path):
        """
        list files in the incremental backup direcotry
        """
        check_phase(phase[0])
        appname = lain_yaml_data()['appname']
        route = "api/v2/app/%s/proc/%s/backups/%s?open=true" % (appname, proc[0], path[0])
        data = cls._request('GET', phase[0], route, None)
        if data:
            print "%-10sFILENAME" % "SIZE"
            for item in data:
                print "%-10s%s%s" % (item['size'], item['name'], '/' if item['dir'] else '')

    @classmethod
    @arg('phase', nargs=1, help="lain cluster phase id, can be added by lain config save")
    @arg('proc', nargs=1, help="proc name")
    @arg('volume', nargs=1, help="volume defined in lain.yaml")
    def get(cls, phase, proc, volume):
        """
        get all the backups of the given proc's volume
        """
        check_phase(phase[0])
        appname = lain_yaml_data()['appname']
        route = "api/v2/app/%s/proc/%s/backups?volume=%s" % (appname, proc[0], volume[0])
        data = cls._request('GET', phase[0], route, None)
        if data:
            print json.dumps(data, indent=2)

    @classmethod
    @arg('phase', nargs=1, help="lain cluster phase id, can be added by lain config save")
    @arg('proc', nargs=1, help="proc name")
    @arg('files', nargs='+', help="backup files")
    def delete(cls, phase, proc, files):
        """
        delete a backup of a proc
        """
        check_phase(phase[0])
        appname = lain_yaml_data()['appname']
        route = "api/v2/app/%s/proc/%s/backups/actions/delete" % (appname, proc[0])
        data = cls._request('POST', phase[0], route, {'files': files})
        if data:
            print data

    @classmethod
    @arg('phase', nargs=1, help="lain cluster phase id, can be added by lain config save")
    @arg('proc', nargs=1, help="proc name")
    @arg('backup', nargs=1, help="backup file")
    @arg('files', nargs=argparse.REMAINDER, help="files in incremental backup directory, if this is given, backup must be a incremental backup")
    def recover(cls, phase, proc, backup, files):
        """
        recover the volume from given backup
        """
        check_phase(phase[0])
        appname = lain_yaml_data()['appname']
        if not files:
            route = "api/v2/app/%s/proc/%s/backups/%s/actions/recover" % (appname, proc[0], backup[0])
            data = cls._request('POST', phase[0], route, None)
        else:
            route = "api/v2/app/%s/proc/%s/backups/%s/actions/recover" % (appname, proc[0], backup[0])
            data = cls._request('POST', phase[0], route, {"files": files})
        if data:
            data

    @classmethod
    @arg('phase', nargs=1, help="lain cluster phase id, can be added by lain config save")
    @arg('proc', nargs=1, help="proc name")
    @arg('backup', nargs=1, help="backup file")
    @arg('files', nargs=argparse.REMAINDER, help="files in incremental backup directory, if this is given, backup must be a incremental backup")
    @arg('-v', '--volume', required=True, help="the volume which backup will move to", default="")
    @arg('-t', '--to', required=True, help="the instance number wish backup move to", default=0)
    def migrate(cls, phase, proc, backup, files, volume="", to=0):
        """
        recover a instance's volume from other instance's backup
        """
        check_phase(phase[0])
        appname = lain_yaml_data()['appname']
        if not files:
            route = "api/v2/app/%s/proc/%s/backups/%s/actions/migrate" % (appname, proc[0], backup[0])
            data = cls._request('POST', phase[0], route, {"volume": volume, "to": to})
        else:
            route = "api/v2/app/%s/proc/%s/backups/%s/actions/migrate" % (appname, proc[0], backup[0])
            data = cls._request('POST', phase[0], route, {"volume": volume, "to": to, "files": files})
        if data:
            data

    @classmethod
    @arg('phase', nargs=1, help="lain cluster phase id, can be added by lain config save")
    @arg('rid', nargs='?', default="", help="record id")
    @arg('-n', '--num', default=10, help="total records count, only used when rid is not given")
    def records(cls, phase, rid, num=10):
        """
        list job records of this lain app
        """
        check_phase(phase[0])
        appname = lain_yaml_data()['appname']
        route = "api/v2/app/%s/cron/records" % appname
        if rid:
            route = "%s/%s" % (route, rid)
        else:
            route += "?total=%d" % num
        data = cls._request('GET', phase[0], route, None)
        if data:
            print json.dumps(data, indent=2)

    @classmethod
    @arg('phase', nargs=1, help="lain cluster phase id, can be added by lain config save")
    @arg('id', nargs=1, help="backup job id")
    def run(cls, phase, id):
        """
        run a job right now
        """
        check_phase(phase[0])
        appname = lain_yaml_data()['appname']
        route = "api/v2/app/%s/cron/jobs/%s/actions/run" % (appname, id[0])
        data = cls._request('POST', phase[0], route, None)
        if data:
            print data

    @classmethod
    def _request(cls, method, phase, url, data):
        uri = "http://backupctl.%s/%s" % (get_domain(phase), url)

        if method.lower() == 'get':
            resp = requests.get(uri)
        elif method.lower() == 'post':
            resp = requests.post(uri, data)

        if resp.status_code >= 300:
            warn('backupctl return error: %s' % resp.text)
            return None
        if not resp.text:
            return None
        try:
            return json.loads(resp.text)
        except:
            warn("not valid json text: %s" % resp.text)
            return "" class MaintainerCommands(TwoLevelCommandBase):
    '''
    allow maintain the maintainer of app in lain, only used when the auth is open
    '''

    @classmethod
    def subcommands(self):
        return [self.show, self.add, self.delete]

    @classmethod
    def namespace(self):
        return "maintainer"

    @classmethod
    def help_message(self):
        return "maintainer operation: including add, delete or show maintainers of the app"

    @classmethod
    @arg('phase', help="lain cluster phase id, can be added by lain config save")
    def show(cls, phase, username=None):
        """
        show maintainers list or specical maitainer message of app in different phase

        username: sso username
        """
        
        check_phase(phase)
        yml = lain_yaml(ignore_prepare=True)
        authorize_and_check(phase, yml.appname)
        auth_header = get_auth_header(SSOAccess.get_token(phase))
        console = "console.%s" % get_domain(phase)

        maintainer_url = "http://%s/api/v1/repos/%s/maintainers/" % (
            console, yml.appname)
        if username:
            maintainer_url += '%s/' % username
        
        show_response = requests.get(maintainer_url, headers=auth_header)
        if show_response.status_code < 300:
            info("maintainer detail:")
            pprint.pprint(show_response.json())
        else:
            error("shit happened : %s" % show_response.text)

    @classmethod
    @arg('phase', help="lain cluster phase id, can be added by lain config save")
    @arg('username', help="sso username to add")
    @arg('role', help='role to assigned to the user', choices=['admin', 'normal'])
    def add(cls, phase, username, role):
        """
        add maintianer for different phase
        """
        
        check_phase(phase)
        yml = lain_yaml(ignore_prepare=True)
        authorize_and_check(phase, yml.appname)
        auth_header = get_auth_header(SSOAccess.get_token(phase))
        console = "console.%s" % get_domain(phase)

        maintainer_url = "http://%s/api/v1/repos/%s/maintainers/" % (
            console, yml.appname)
        payload = {"username": username,
                   "role": role}

        add_response = requests.post(maintainer_url, headers=auth_header, json=payload)
        if add_response.status_code < 300:
            info("add successfully.")
        else:
            error("shit happened : %s" % add_response.text)

    @classmethod
    @arg('phase', help="lain cluster phase id, can be added by lain config save")
    @arg('username', help="sso username")
    def delete(cls, phase, username):
        """
        delete maintianer for different phase
        """

        check_phase(phase)
        yml = lain_yaml(ignore_prepare=True)
        authorize_and_check(phase, yml.appname)
        auth_header = get_auth_header(SSOAccess.get_token(phase))
        console = "console.%s" % get_domain(phase)
        
        maintainer_url = "http://%s/api/v1/repos/%s/maintainers/%s/" % (
            console, yml.appname, username)

        delete_response = requests.delete(maintainer_url, headers=auth_header)
        if delete_response.status_code < 300:
            info("delete successfully.")
        else:
            error("shit happened : %s" % delete_response.text) def exit_gracefully(signal, frame):
    warn("You pressed Ctrl + C, and I will exit...")
    sys.exit(130)
import logging
import argh
import signal
from lain_cli.attach import attach
from lain_cli.dashboard import dashboard
from lain_cli.reposit import reposit
from lain_cli.prepare import prepare
from lain_cli.build import build
from lain_cli.tag import tag
from lain_cli.push import push
from lain_cli.sync import sync
from lain_cli.deploy import deploy
from lain_cli.scale import scale
from lain_cli.ps import ps
from lain_cli.undeploy import undeploy
from lain_cli.login import login, refresh
from lain_cli.logout import logout
from lain_cli.rmi import rmi
from lain_cli.meta import meta
from lain_cli.enter import enter
from lain_cli.clear import clear
from lain_cli.imagecheck import check
from lain_cli.validate import validate
from lain_cli.appversion import appversion
from lain_cli.prepare_update import prepare_update
from lain_cli.test import test
from lain_cli.run import run, debug, stop, rm
from lain_cli.update import update
from lain_cli.version import version
from lain_cli.config import ConfigCommands
from lain_cli.secret import SecretCommands
from lain_cli.backup import BackupCommands
from lain_cli.maintainer import MaintainerCommands
from lain_cli.utils import exit_gracefully
#!/usr/bin/env python
# -*- coding: utf-8 -*-








logging.getLogger("requests").setLevel(logging.WARNING)
logging.getLogger("docker").setLevel(logging.WARNING)
logging.getLogger("urllib3").setLevel(logging.WARNING)


one_level_commands = [

    appversion, attach, build, check, clear, dashboard, debug,