Пример #1
0
def push():
    pc = commands.get_pilot_client()
    pc.context.push()
    click.secho(
        'Global projects have been updated. Users will be notified '
        'within 24 hours.',
        fg='green')
Пример #2
0
def describe(path, output_json, limit, relative, path_is_sub):
    pc = commands.get_pilot_client()
    entry = pc.get_search_entry(path,
                                relative=relative,
                                path_is_sub=path_is_sub)
    if not entry:
        click.echo('Unable to find entry')
        return

    if output_json:
        click.echo(json.dumps(entry, indent=4))
        return

    single_file_entry = get_matching_file(pc.get_globus_http_url(path), entry)

    if single_file_entry:
        cols = [
            'title', 'authors', 'publisher', 'subjects', 'dates', 'data',
            'dataframe', 'rows', 'columns', 'formats', 'version', 'size',
            'description'
        ]
        output = '\n'.join(
            get_formatted_fields(entry, cols, limit=limit) + [''] +
            get_single_file_info(entry, single_file_entry) + ['', ''] +
            get_location_info(entry))
    else:
        cols = [
            'title', 'authors', 'publisher', 'subjects', 'dates', 'formats',
            'version', 'combined_size', 'description', 'files'
        ]
        output = '\n'.join(
            get_formatted_fields(entry, cols, limit=limit) + [''] +
            get_location_info(entry))

    click.echo(output)
Пример #3
0
def add():
    pc = commands.get_pilot_client()
    order = ['title', 'short_name', 'description']
    base_path = pc.context.get_value('projects_base_path')
    PROJECT_QUERIES['short_name']['prompt'] = \
        PROJECT_QUERIES['short_name']['prompt'].format(base_path)
    titles = [p['title'] for p in pc.project.load_all().values()]
    PROJECT_QUERIES['title']['current'] = titles

    iv = input_validation.InputValidator(queries=PROJECT_QUERIES, order=order)
    project = iv.ask_all()
    project.update({
        'search_index':
        pc.context.get_value('projects_default_search_index'),
        'resource_server':
        pc.context.get_value('projects_default_resource_server')
    })
    project['endpoint'] = pc.context.get_value('projects_endpoint')
    project['group'] = pc.context.get_value('projects_group')
    short_name = project.pop('short_name')
    project['base_path'] = os.path.join(base_path, short_name)

    click.secho('Updating global project list... ', nl=False)
    pc.project.add_project(short_name, project)
    tc = pc.get_transfer_client()
    tc.operation_mkdir(project['endpoint'], project['base_path'])
    pc.context.push()
    click.secho('Success', fg='green')
    pc.project.set_project(short_name)
    click.secho('Switched to project {}'.format(short_name))
    click.secho('Your new project "{}" has been added! '
                'Users will be notified within 24 hours next time they use '
                'this tool.'.format(project['title']),
                fg='green')
Пример #4
0
def edit(project=None):
    pc = commands.get_pilot_client()
    current = pc.project.current if pc.project.is_set() else None
    project = project or current
    if project is None:
        click.echo('Use "pilot project info <project>" to list info about a '
                   'project.')
        return
    info = pc.project.get_info(project)
    queries = {
        'title': PROJECT_QUERIES['title'].copy(),
        'description': PROJECT_QUERIES['description'].copy()
    }
    titles = [
        p['title'] for p in pc.project.load_all().values()
        if p['title'] != info['title']
    ]
    queries['title']['current'] = titles

    for key in queries:
        queries[key]['default'] = info[key]
    iv = input_validation.InputValidator(queries=queries,
                                         order=['title', 'description'])
    new_info = iv.ask_all()
    info.update(new_info)
    pc.project.set_project(project, info)
    pc.context.push()
    click.secho('Project "{}" has been updated'.format(project), fg='green')
Пример #5
0
def set_command(project):
    pc = commands.get_pilot_client()
    try:
        pc.project.current = project
        click.echo(f'Current project set to {project}')
    except ValueError as ve:
        click.secho(str(ve), fg='red')
Пример #6
0
def status_command(number):
    pc = get_pilot_client()

    ordered_tlogs = []
    tlog_order = ['id', 'dataframe', 'status', 'start_time', 'task_id']
    # Fetch a limmited set of logs by the most recent entries
    tlogs = pc.transfer_log.get_log()[:number]

    pending_tasks = [t for t in tlogs if t['status'] not in INACTIVE_STATES]
    if pending_tasks:
        click.secho('Updating tasks...', fg='green')
        update_tasks(pending_tasks)
        tlogs = pc.transfer_log.get_log()[:number]

    for tlog in tlogs:
        tlog['id'] = str(tlog['id'])
        tlog['start_time'] = tlog['start_time'].strftime('%Y-%m-%d %H:%M')
        ordered_tlogs.append([tlog[item] for item in tlog_order])

    fmt = '{:4.3}{:30.29}{:10.11}{:18.17}{:37.36}'
    header_names = ['ID', 'Dataframe', 'Status', 'Start Time', 'Task ID']
    headers = fmt.format(*header_names)
    output = '\n'.join([fmt.format(*items) for items in ordered_tlogs])

    click.echo(headers)
    click.echo(output)
Пример #7
0
def get_location_info(entry):
    short_path = os.path.basename(get_common_path(entry))
    pc = commands.get_pilot_client()
    return [
        'Location Information',
        '{:21.20}{}'.format('Subject', pc.get_subject_url(short_path)),
        '{:21.20}{}'.format('Portal', pc.get_portal_url(short_path))
    ]
Пример #8
0
def validate_is_globus_endpoint(v, entity):
    pc = commands.get_pilot_client()
    tc = pc.get_transfer_client()
    log.debug(f'Checking ep: {entity}')
    try:
        tc.get_endpoint(entity)
    except globus_sdk.exc.TransferAPIError:
        log.debug(f'Failed to fetch endpoint {entity}', exc_info=True)
        raise exc.PilotValidator(
            f'Failed to get endpoint {entity}, please choose '
            'a valid Globus Endpoint') from None
Пример #9
0
def list_command(path, output_json, limit, relative, all_recs):
    # Should require login if there are publicly visible records
    pc = commands.get_pilot_client()
    project = pc.project.current
    search_params = {'limit': limit}
    if all_recs:
        search_params['filters'], relative = [], False
    search_results = pc.search(project=project, custom_params=search_params)
    log.debug(search_results)
    if output_json:
        click.echo(json.dumps(search_results, indent=4))
        return
    path_sub = pc.get_subject_url(path) if relative else path
    curated_results = [
        r for r in search_results['gmeta'] if path_sub in r['subject']
    ]

    results = 'Showing {}/{} of total results for "{}"'.format(
        len(curated_results), search_results['total'], project)
    items = ['title', 'data', 'dataframe', 'rows', 'columns', 'size']
    titles = get_titles(items) + ['Path']
    fmt = '{:21.20}{:11.10}{:10.9}{:7.6}{:7.6}{:7.6}{}'
    output = []
    for result in curated_results:
        # If this path refers to a result in a different base location, skip
        # it, it isn't part of this project
        if relative and pc.get_path('') not in result['subject']:
            log.debug('Skipping result {}'.format(result['subject']))
            continue

        data = dict(parse_result(result['content'][0], items))
        parsed = [data.get(name) for name in items]
        parsed += [get_short_path(result) if relative else result['subject']]
        parsed = [str(p) for p in parsed]

        output.append(fmt.format(*parsed))
    if not output:
        click.secho('No results to display for "{}"'.format(path or '/'),
                    fg='yellow')
    else:
        output = [results, fmt.format(*titles)] + output
        click.echo('\n'.join(output))

    result_names = [os.path.basename(r['subject']) for r in curated_results]
    path_info = pc.ls(path, extended=True)
    dirs = [
        name for name, info in path_info.items()
        if info['type'] == 'dir' and name not in result_names
    ]
    if dirs:
        click.echo('\nDirectories:\n\t{}'.format('\n\t'.join(dirs)))
    else:
        click.echo('\nNo Directories in {}'.format(path or '/'))
Пример #10
0
def index_command(ctx):
    pc = commands.get_pilot_client()

    if ctx.invoked_subcommand is None:
        click.echo('Set index with "pilot index set <index_uuid>|'
                   '<index_name>"')
        contexts = pc.context.load_all()
        fmt = '{} {}'
        for context in contexts:
            if context == pc.context.current:
                click.secho(fmt.format('*', context), fg='green')
            else:
                click.echo(fmt.format(' ', context))
Пример #11
0
def setup():
    """Setup a brand new pilot config for a new index.

    NOTE: Paths with tilde '~' DO NOT WORK. These cause problems for resolving
    paths that look like /~/foo/bar, which sometimes translate as ~/foo/bar
    instead. These are disabled to prevent that from happening.
    """
    PROJECT_QUERIES = {
        'projects_endpoint': {
            'prompt': 'Set a Globus UUID where your data should be stored.',
            'default': '',
            'help': 'visit "https://app.globus.org/file-manager/collections" '
                    'to find Globus endpoint to store your data.',
            'validation': [input_validation.validate_is_uuid,
                           input_validation.validate_is_globus_endpoint],
        },
        'projects_base_path': {
            'prompt': 'Pick a base path.',
            'default': '/',
            'help': 'All data will be saved under this directory',
            'validation': [input_validation.validate_no_spaces,
                           input_validation.validate_absolute_path,
                           input_validation.validate_no_tilde],
        },
        'projects_group': {
            'prompt': 'Pick a Globus Group to secure Globus Search records',
            'default': 'public',
            'help': 'The group determines who can view records in search. '
                    'People not in this group will not see records in Globus '
                    'Search. "public" allows anyone to see these records.',
            'validation': [input_validation.validate_is_valid_globus_group],
        },
    }
    pc = commands.get_pilot_client()
    projects = pc.project.load_all().keys()
    if projects:
        click.secho(
            f'Index is already setup with the following projects: {projects}. '
            f'Please delete them before setting up your index.', fg='red')
        return
    order = ['projects_endpoint', 'projects_base_path', 'projects_group']

    iv = input_validation.InputValidator(queries=PROJECT_QUERIES, order=order)
    new_ctx = iv.ask_all()
    pc.context.update_context(new_ctx)
    pc.context.push()
    click.secho('Your index has been setup successfully. Now see '
                '`pilot project add`.', fg='green')
    return
Пример #12
0
def validate_slug_to_path_unique(v, slug):
    pc = commands.get_pilot_client()
    tc = pc.get_transfer_client()
    try:
        ctx = pc.context.get_context()
        ep, path = ctx.get('projects_endpoint'), ctx.get('projects_base_path')
        log.debug('Checking ep: {} path: {}'.format(ep, path))
        response = tc.operation_ls(ep, path=path)
        existing = [f['name'] for f in response.data['DATA']]
        if slug in existing:
            raise exc.PilotValidator('"{}" is not available, please choose '
                                     'another'.format(slug))
    except globus_sdk.exc.TransferAPIError as tapie:
        log.exception(tapie.message)
        raise exc.PilotValidator('An error occurred, please try a different '
                                 'value or notify a system administrator.')
Пример #13
0
def info(index=None):
    pc = commands.get_pilot_client()
    index = index or pc.context.current
    if index is None:
        click.echo('Use "pilot index info <context>" to list info about a '
                   'project.')
        return
    try:
        info = pc.context.get_context(index)
    except exc.PilotInvalidProject as pip:
        click.secho(str(pip), fg='red')
        return

    fmt = '{:40.39}{}'
    log.debug(info)
    output = '\n'.join([fmt.format(*i) for i in info.items()])
    click.echo(output)
    click.echo()
Пример #14
0
def cli(ctx):
    pc = commands.get_pilot_client()

    if not pc.config.is_migrated():
        click.secho('Old config detected, upgrading... ', fg='yellow',
                    nl=False)
        try:
            pc.config.migrate()
            click.secho('Success!', fg='green')
        except Exception:
            click.secho(f'Failed! Try removing '
                        f'{pc.config_file} and logging in '
                        f'again.', fg='red')
    if pc.is_logged_in():
        if pc.context.is_cache_stale():
            log.debug('Cache is stale! Updating...')
            try:
                if any(pc.context.update_with_diff(dry_run=True).values()):
                    click.secho('Projects have updated. Use '
                                '"pilot project update"'
                                ' to get the newest changes.', fg='yellow')
                log.debug('Update was successful.')
            except globus_sdk.exc.SearchAPIError as sapie:
                if not sapie.code == 'NotFound.Generic':
                    log.error('This error is unexpected!')
                    log.exception(sapie)
                click.secho(
                    'No manifest exists on this index. You can add '
                    'one by running `pilot context push`', fg='red')
        pcommands = ['delete', 'describe', 'download', 'list', 'mkdir',
                     'upload']
        if not pc.project.is_set() and ctx.invoked_subcommand in pcommands:
            click.secho('No project set, use "pilot project" to list projects '
                        'and "pilot project set <myproject>" '
                        'to set your current project.', fg='yellow')
            sys.exit(exc.ExitCodes.INVALID_CLIENT_CONFIGURATION)
    else:
        if (ctx.invoked_subcommand and
                ctx.invoked_subcommand not in INVOKABLE_WITHOUT_LOGIN):
            click.echo('You are not logged in.')
            sys.exit(exc.ExitCodes.NOT_LOGGED_IN)

    if ctx.invoked_subcommand is None:
        click.echo(ctx.get_help())
Пример #15
0
def update(dry_run, update_groups_cache):
    pc = commands.get_pilot_client()
    try:
        output = pc.context.update_with_diff(
            dry_run=dry_run, update_groups_cache=update_groups_cache)
        if not any(output.values()):
            click.secho('Project is up to date', fg='green')
            return
        for group, changes in output.items():
            if not changes:
                continue
            click.echo('{}:'.format(group))
            for change_type, items in changes.items():
                click.echo('\t{}:'.format(change_type))
                for name, value in items.items():
                    click.echo('\t\t{} -> {}'.format(name, value))
    except globus_sdk.exc.SearchAPIError as sapie:
        click.secho(str(sapie), fg='red')
        click.secho(
            'You can create the manifest for this index with `pilot '
            'context push`',
            fg='blue')
Пример #16
0
def project_command(ctx):
    pc = commands.get_pilot_client()
    if not pc.is_logged_in():
        click.echo('You are not logged in.')
        return
    invalid_with_pending_update = ['delete', 'add']
    if ctx.invoked_subcommand in invalid_with_pending_update:
        if any(pc.context.update_with_diff(dry_run=True).values()):
            click.secho(
                'There is an update for projects, please update '
                '("pilot project update") before adding a new project',
                fg='red')
            sys.exit()
    if ctx.invoked_subcommand is None:
        click.echo('Set project with "pilot project set <myproject>"')
        projects = pc.project.load_all()
        current = pc.project.current if pc.project.is_set() else None
        fmt = '{} {}'
        for project in projects:
            if project == current:
                click.secho(fmt.format('*', project), fg='green')
            else:
                click.echo(fmt.format(' ', project))
Пример #17
0
def update_tasks(transfer_tasks):
    """
    Update pending Globus Transfer tasks, and save the resulting status to
    the config. transfer tasks is a list of dicts as returned by
    config.get_pc.transfer_log()

    User must be logged in!
    """
    pc = get_pilot_client()
    auth = pc.get_authorizers()['transfer.api.globus.org']
    tc = globus_sdk.TransferClient(authorizer=auth)
    user_tasks = {
        r.data['task_id']: r.data
        for r in tc.task_list(num_results=100).data
    }
    for task in transfer_tasks:
        task_data = user_tasks.get(task['task_id'])
        if not task_data:
            click.secho('Unable to update status for {}'.format(task['id']),
                        fg='yellow')
        else:
            status = task_data.get('nice_status') or task_data.get('status')
            pc.transfer_log.update_log(task['task_id'], status)
Пример #18
0
def info(project=None):
    pc = commands.get_pilot_client()
    current = pc.project.current if pc.project.is_set() else None
    project = project or current
    if project is None:
        click.echo('Use "pilot project info <project>" to list info about a '
                   'project.')
        return
    try:
        info = pc.project.get_info(project)
    except exc.PilotInvalidProject as pip:
        click.secho(str(pip), fg='red')
        return

    ep_name = info['endpoint']
    try:
        tc = pc.get_transfer_client()
        ep = tc.get_endpoint(info['endpoint']).data
        ep_name = ep['display_name'] or ep['canonical_name'] or ep_name
    except globus_sdk.exc.TransferAPIError:
        click.echo(
            'Failed to lookup endpoint {}, please ensure it is active.'.format(
                info['endpoint']))

    dinfo = [
        (info['title'], ''),
        ('Endpoint', ep_name),
        ('Group', pc.project.lookup_group(info['group'])),
        ('Base Path', info['base_path']),
    ]

    fmt = '{:25.24}{}'
    log.debug(info)
    output = '\n'.join([fmt.format(*i) for i in dinfo])
    click.echo(output)
    click.echo()
    click.echo(info['description'])
Пример #19
0
def validate_project_path_unique(v, path):
    pc = commands.get_pilot_client()
    paths = [p['base_path'] for p in pc.project.load_all().values()]
    if path in paths:
        raise exc.PilotValidator('Path must be unique. (Other paths include '
                                 f'{", ".join(paths)})')
Пример #20
0
def set_index(ctx, index_name):
    pc = commands.get_pilot_client()
    if not pc.context.get_context(index_name):
        log.debug(f'No local context "{index_name}", attempting lookup...')
        try:
            # Ensure we're using the index UUID, lookup by name is no longer
            # possible
            uuid.UUID(index_name)
            index_uuid = index_name

            click.secho('looking up {}...'.format(index_uuid))
            sc = pc.get_search_client()
            index_info = sc.get_index(index_uuid)
            display_name = index_info['display_name']
            pc.context.add_context(display_name, {
                'client_id': 'e4d82438-00df-4dbd-ab90-b6258933c335',
                'app_name': '{} app'.format(display_name),
                'manifest_index': index_uuid,
                'manifest_subject': 'globus://project-manifest.json',
                'scopes': pc.DEFAULT_SCOPES,
                'projects_cache_timeout': DEFAULT_PROJECTS_CACHE_TIMEOUT,
                'projects_endpoint': '',
                'projects_base_path': '',
                'projects_group': '',
                'projects_default_search_index': index_uuid,
                'projects_default_resource_server': 'petrel_https_server',
            })
            index_name = display_name
        except ValueError:
            click.secho(f'"{index_name}": must be a UUID when adding a new '
                        f'un-tracked search index.', fg='red')
            sys.exit(1)
        except Exception as e:
            log.exception(e)
            click.secho('Unable to find index {}'.format(index_name))
            sys.exit(2)
    pc.context.set_context(index_name)
    try:
        log.debug('Updating index...')
        pc.context.update()
    except exc.NoManifestException:
        click.secho('Pilot has not been setup for this index. Run '
                    '"pilot index setup" to do so.', fg='red')
        sys.exit(3)

    # If there is only one project, automatically set pilot to that one
    if len(pc.project.load_all()) == 1:
        projects = pc.project.load_all()
        pc.project.current = list(projects.keys())[0]
    log.debug('Context set! Fetching general info for user...')
    ctx.invoke(info, index=index_name)
    ctx.invoke(project_command)
    try:
        pc.load_tokens(requested_scopes=pc.context.get_value('scopes'))
    except ScopesMismatch:
        click.secho('Scopes do not match for this index, please login '
                    'again.', fg='red')
    log.debug(pc.context.get_value('projects_default_search_index'))
    if not pc.project.load_all():
        example = '''
        [[default-project]]
        base_path = /
        description =
        endpoint =
        group =
        resource_server = petrel_https_server
        search_index = {}
        title = default-project
        '''.format(pc.context.get_value('projects_default_search_index'))
        click.secho('No projects in this index, you can bootstrap one in the '
                    'config under [projects]\n{}'.format(example))
Пример #21
0
def delete(project, keep_context):
    pc = commands.get_pilot_client()
    if project not in pc.project.load_all():
        click.secho('{} is not a valid project'.format(project), fg='red')
        return 1
    pc.project.current = project
    results = pc.search(project=project)
    pinfo = pc.project.get_info(project)
    search_query = {
        'q': '*',
        'filters': {
            'field_name': 'project_metadata.project-slug',
            'type': 'match_all',
            'values': [project or pc.project.current]
        }
    }
    project_base_path = pc.get_path('', project=project)
    search_client = pc.get_search_client()
    transfer_client = pc.get_transfer_client()
    log.debug('Base path for delete is: {}'.format(project_base_path))
    dz = '\n{}\nDANGER ZONE\n{}'.format('/' * 80, '/' * 80)
    click.secho('{dz}\n'
                'This will delete all data and search results in your '
                'project.\n{tot} datasets will be deleted for {project}\n\n'
                'Base Directory "{project_base_path}" will be deleted.'
                '{dz}'
                ''.format(dz=dz,
                          tot=results['total'],
                          project=project,
                          project_base_path=project_base_path),
                bg='red')
    click.echo(
        'Please type the name ({}) of your project to delete it> '.format(
            project),
        nl=False)
    if input() != project:
        click.secho('Names do not match, aborting...')
        return 1
    click.echo(f'Deleting Data: {project_base_path}')
    try:
        ddata = globus_sdk.DeleteData(transfer_client,
                                      pinfo['endpoint'],
                                      recursive=True,
                                      notify_on_succeeded=False)
        ddata.add_item(project_base_path)
        transfer_client.submit_delete(ddata)
    except globus_sdk.exc.TransferAPIError as tapie:
        log.debug('Error deleting base folder', exc_info=tapie)
        click.secho(
            f'Error deleting project base folder {project_base_path}: '
            f'{str(tapie)}',
            fg='red')

    try:
        click.echo(f'Deleting Search Records: {pinfo["search_index"]}')
        log.debug(f'Search Query: {search_query}')
        search_client.delete_by_query(pinfo['search_index'], search_query)
    except globus_sdk.exc.SearchAPIError as sapie:
        log.debug('Error deleting test data', exc_info=sapie)
        click.secho(
            f'Error deleting search data {pinfo["search_index"]}: '
            f'{str(sapie)}',
            fg='red')
    if keep_context:
        click.secho('Keeping now empty pilot project {}'.format(project),
                    fg='blue')
        pc.mkdir('/')
    else:
        click.echo('Removing project...')
        pc.project.delete_project(project)
        pc.context.push()
    click.secho('Project {} has been deleted successfully.'.format(project),
                fg='green')
Пример #22
0
def validate_project_endpoint(v, ep):
    pc = commands.get_pilot_client()
    if ep not in pc.project.ENDPOINTS.keys():
        raise exc.PilotValidator('Endpoint must be one of: "{}"'.format(
            ', '.join(pc.project.ENDPOINTS.keys())))
Пример #23
0
def get_short_path(result):
    pc = commands.get_pilot_client()
    sub = urllib.parse.urlparse(result['subject'])
    return sub.path.replace(pc.get_path(''), '').lstrip('/')
Пример #24
0
def validate_project_slug_unique(v, slug):
    pc = commands.get_pilot_client()
    slugs = pc.project.load_all().keys()
    if slug in slugs:
        raise exc.PilotValidator(f'Slug must be unique from {", ".join(slug)}')