Esempio n. 1
0
    def take_action(self, parsed_args):
        parsed_args = CreateTokenFormatOne.before_take_action(
            self, parsed_args)
        self.requests_client.setup(API_NAME, SERVICE_VERSION)
        self.take_action_defaults(parsed_args)

        # Allow prompt for password when not specified
        passwd = parsed_args.tapis_password
        if passwd is None:
            passwd = prompt('Password', passwd, secret=True)

        headers = SearchableCommand.headers(self, Token, parsed_args)
        try:
            self.tapis_client.token.password = passwd
            result = self.tapis_client.token.create()
            self.tapis_client.token.password = None
        except HTTPError as h:
            if str(h).startswith('400'):
                raise AgaveError(
                    'Failed to create a token pair: {0}'.format(h))
            else:
                raise AgaveError(str(h))
        result = list()
        for h in headers:
            result.append(self.tapis_client.token.token_info.get(h))
        return (tuple(headers), tuple(result))
Esempio n. 2
0
def interactive(parsed_args, headers, results, force=False):
    """Interactively solicit configuration values
    """
    context = _read_current(parsed_args)
    interactive = context['interactive'] or force

    if interactive:
        print('\nContainer registry access:')
        print('--------------------------')

    for iv in VARS:
        prompt_name = iv.replace('_', ' ').title()
        key_name = ENV_PREFIX + iv
        header_name = iv.lower()

        if interactive:
            if settings.redact.key_is_private(key_name):
                is_secret = True
            else:
                is_secret = False
            value = prompt(prompt_name, context[key_name], secret=is_secret)
        else:
            value = context[key_name]

        if value is not None and value != '':
            settings_set(key_name, value)

        headers.append(header_name)
        results.append(settings.redact.auto_redact(header_name, value))

    return (headers, results)
    def take_action(self, parsed_args):
        parsed_args = super(TokenCreate, self).preprocess_args(parsed_args)
        self.update_payload(parsed_args)

        # Allow prompt for password when not specified
        passwd = parsed_args.tapis_password
        if passwd is None:
            passwd = prompt('Password', passwd, secret=True)

        headers = super(TokenCreate, self).render_headers(Token, parsed_args)
        self.tapis_client.token.password = passwd

        result = list()
        try:
            if parsed_args.token_username is None:
                resp = self.tapis_client.token.create()
                self.tapis_client.token.password = None
                for h in headers:
                    # DERP
                    result.append(self.tapis_client.token.token_info.get(h))
            else:
                self.requests_client.setup(API_NAME, None)
                data = {
                    'token_username': parsed_args.token_username,
                    'username': self.tapis_client.username,
                    'password': passwd,
                    'scope': 'PRODUCTION',
                    'grant_type': 'admin_password'
                }
                resp = self.requests_client.post_data_basic(data)
                # Not returned by service
                headers.remove('expires_at')
                # Do not return - we want impersonation tokens to expire
                headers.remove('refresh_token')
                for h in headers:
                    # DERP
                    result.append(resp.get(h))

                # Manually insert token username into response
                headers.append('username')
                # Nice feature - highlight the username
                result.append(parsed_args.token_username)

        except HTTPError as h:
            if str(h).startswith('400'):
                raise AgaveError(
                    'Failed to create a token pair: {0}'.format(h))
            else:
                raise AgaveError(str(h))

        et.phone_home()
        return (tuple(headers), tuple(result))
Esempio n. 4
0
def interactive(parsed_args, headers, results, force=False):
    """Interactively solicit configuration values
    """
    context = _read_current(parsed_args)
    interactive = context['interactive'] or force

    if interactive:
        print('\nGit server access:')
        print('------------------')

    for iv in VARS:
        prompt_name = iv.replace('_', ' ').title()
        key_name = ENV_PREFIX + iv
        header_name = iv.lower()

        if interactive:

            if key_name == ENV_PREFIX + 'GIT_TOKEN':
                print(
                    'Learn about {} personal access tokens:'.format(GITHUBCOM))
                print(ACCESS_TOKEN_HELP_URLS[GITHUBCOM])

            if settings.redact.key_is_private(key_name):
                is_secret = True
            else:
                is_secret = False
            value = prompt(prompt_name, context[key_name], secret=is_secret)
        else:
            value = context[key_name]

        if value is not None and value != '':
            settings_set(key_name, value)

        headers.append(header_name)
        results.append(settings.redact.auto_redact(header_name, value))

    return (headers, results)
Esempio n. 5
0
    def take_action(self, parsed_args):
        # Load what we can from credentials cache. Ultimately, if no
        # overrides are specified, the cached contents will be used to
        # populate the Tapis client.

        firstrun()

        try:
            logger.debug('Read from local Tapis cache...')
            ag_context = Agave._read_current(agave_kwargs=True)
        except FileNotFoundError:
            # A very common case (all new tapis_cli installs, for instance), will
            # find NO cache file on disk. This must be recoverable, so set the
            # context to an empty dict
            ag_context = {}

        # Inject a password field (which may be filled later) into the context
        ag_context['password'] = None

        # Process overrides to ag_context provided by parsed_args
        mandate_username = False
        mandate_password = False

        # Process tenant override
        parsed_tenant_id = getattr(parsed_args, 'tapis_tenant_id', None)
        if parsed_tenant_id is not None:
            if ag_context.get('tenant_id', None) != parsed_tenant_id:
                mandate_username = True
                mandate_password = True
                logger.warning(
                    'Tenant changed. Username and password must be specified.')
            ag_context['tenant_id'] = parsed_tenant_id

        # Process username override
        parsed_username = getattr(parsed_args, 'tapis_username', None)
        if parsed_username is not None:
            if ag_context.get('username', None) != parsed_username:
                mandate_password = True
                logger.warning('Username changed. Password must be specified.')
            ag_context['username'] = parsed_username

        # If interactive OR cannot establish tenant_id, prompt for it
        temp_tenant_id = ag_context.get('tenant_id', None)
        if temp_tenant_id is None or parsed_args.interactive:
            if temp_tenant_id is None:
                temp_tenant_id = TAPIS_DEFAULT_TENANT_ID
            tl = [t.get('code') for t in agavepy.tenants.list_tenants()]
            print('Available Tenants\n=================')
            print(fmtcols(tl, 5))
            ag_context['tenant_id'] = prompt('Enter a tenant name',
                                             temp_tenant_id)

        ag_context['api_server'] = agavepy.tenants.api_server_by_id(
            ag_context['tenant_id'])

        # Prompt when interactive or username is reset or unavailable
        if mandate_username or ag_context.get(
                'username', None) is None or parsed_args.interactive:
            mandate_password = True
            ag_context['username'] = prompt('Username',
                                            ag_context.get('username', None))

        # Set client name now that we have tenant and user
        ag_context['client_name'] = '{0}-{1}-{2}-{3}'.format(
            CLIENT_PREFIX, ag_context['tenant_id'], ag_context['username'],
            get_hostname())

        # Prompt when interactive or tenant/username is reset or unavailable
        if mandate_password or parsed_args.interactive:
            temp_password = getattr(parsed_args, 'tapis_password', None)
            if temp_password is None or parsed_args.interactive:
                temp_password = prompt('Password for {0}'.format(
                    ag_context['username']),
                                       temp_password,
                                       secret=True)

            # Remove extant api_key, api_secret, and tokens to ensure the
            # HTTP Basic Auth processor will be loaded when the context is
            # sent to an Agave instance.
            ag_context['password'] = temp_password
            ag_context['api_key'] = None
            ag_context['api_secret'] = None
            ag_context['access_token'] = None
            ag_context['refresh_token'] = None

        api_key = ag_context.get('api_key', None)
        ag = None

        # No client was loadable from the local system
        if api_key is None or api_key == '':
            logger.debug('clients.create: {0}'.format(
                ag_context['client_name']))
            create_context = {
                'api_server': ag_context['api_server'],
                'username': ag_context['username'],
                'password': ag_context['password'],
                'verify': True
            }
            ag = Agave(**create_context)

            # Preflight activity: Get rid of existing client
            try:
                logger.debug('clients.delete()...')
                ag.clients.delete(clientName=ag_context['client_name'])
            except Exception:
                logger.warning('Client was not deleted')
                pass

            logger.info('clients.create: {0}'.format(
                ag_context['client_name']))
            ag.clients.create(
                body={
                    'clientName':
                    ag_context['client_name'],
                    'description':
                    'Generated by {0}@{1} at {2}'.format(
                        get_local_username(), get_public_ip(),
                        datetime.datetime.utcnow().strftime(
                            "%Y-%m-%dT%H:%M:%SZ"))
                })
        else:
            # Client was cached - load it up
            logger.debug('Loading client from cache...')
            ag = Agave(**ag_context)

        # Hit profiles service to check client
        try:
            logger.debug('Verify Tapis client is active...')
            ag.profiles.listByUsername(username=ag_context['username'])
            logger.debug('Verified')
        except Exception as err:
            raise AgaveError('Tapis client appears invalid: {0}'.format(err))

        # Formulate a table view of key values for current session
        headers = [
            'tenant_id', 'username', 'client_name', 'api_key', 'access_token',
            'expires_at'
        ]
        data = [
            # Coerce to string to avoid failures where a deepcopy
            # operation in Python's implementation of tuple() is
            # unable to accomodate copying properties of an Agave object
            str(ag.tenant_id),
            str(ag.username),
            str(ag_context['client_name']),
            str(ag.api_key),
            str(ag._token),
            str(ag.expires_at)
        ]

        et.phone_home()
        return (tuple(headers), tuple(data))
Esempio n. 6
0
    def take_action(self, parsed_args):
        parsed_args = self.preprocess_args(parsed_args)
        self.requests_client.setup(API_NAME, SERVICE_VERSION)
        app_id = AppIdentifier.get_identifier(self, parsed_args)
        interactive = parsed_args.interactive

        app_def = {}
        exc_def = {}
        app_def = self.tapis_client.apps.get(appId=app_id)
        exc_def = self.tapis_client.systems.get(
            systemId=app_def.get('executionSystem'))

        # Intrepret parsed_args in light of contents of app and exec system definiitions
        # 1. allow --queue to over-ride app['defaultQueue']
        exc_queue_names = [q['name'] for q in exc_def['queues']]
        queue_name = getattr(parsed_args, 'queue_name', None)
        if queue_name is None:
            queue_name = app_def.get('defaultQueue', None)

        # Get queue details for app execution system
        #
        # 1. Select either named queue -or- default queue
        # 2. ValueError if queue named and not found
        sys_queue = None
        if queue_name is not None:
            for q in exc_def['queues']:
                if q['name'] == queue_name:
                    sys_queue = q
                    break
        else:
            for q in exc_def['queues']:
                if q['default'] is True:
                    sys_queue = q
                    queue_name = sys_queue['name']
                    break
        if sys_queue is None:
            raise ValueError(
                'Job queue "{0}" does not exist on system "{1}"'.format(
                    queue_name, exc_def['id']))
        # TODO - Rewire so that we can check the queue name after prompting
        if interactive:
            print('Job configuration')
            print('-----------------')

        if interactive:
            queue_name = prompt('Queue ({0})'.format(
                '|'.join(exc_queue_names)),
                                queue_name,
                                allow_empty=False)

        # Continue interpreting parsed_args
        #
        # Normally, we could just chain the getattr on parsed_args
        # and the successive chained get() to the app definition and
        # preferred system queue, but Tapis apps will actually return
        # an 'null' value for app.PROPERTY, which translates
        # to a value of None for job.PROPERTY.
        mem_per_node = getattr(parsed_args, 'memory_per_node',
                               app_def.get('defaultMemoryPerNode', None))
        if mem_per_node is None:
            mem_per_node = sys_queue['maxMemoryPerNode']
        # if interactive:
        #     queue_name = int(
        #         prompt('Memory (GB)', mem_per_node, allow_empty=False))
        if isinstance(mem_per_node, int):
            mem_per_node = str(mem_per_node) + 'GB'

        cpu_per_node = getattr(parsed_args, 'cpu_per_node',
                               app_def.get('defaultProcessorsPerNode', None))
        if cpu_per_node is None:
            cpu_per_node = sys_queue['maxProcessorsPerNode']
        # if interactive:
        #     cpu_per_node = int(
        #         prompt('CPU/Node', cpu_per_node, allow_empty=False))

        node_count = getattr(parsed_args, 'node_count',
                             app_def.get('defaultNodeCount', None))
        if node_count is None:
            # There is no default node count in a system queue definition
            node_count = 1
        if interactive:
            node_count = int(prompt('Nodes', node_count, allow_empty=False))

        # TODO - Validate that max_run_time is LTE sys_queue.maxRequestedTime
        max_run_time = getattr(parsed_args, 'max_run_time',
                               app_def.get('defaultMaxRunTime', None))
        if max_run_time is None:
            # max_run_time = sys_queue['maxRequestedTime']
            max_run_time = DEFAULT_JOB_RUNTIME
        if interactive:
            max_run_time = prompt('Run Time (max {0})'.format(
                sys_queue['maxRequestedTime']),
                                  max_run_time,
                                  allow_empty=False)
        # validate max_run_time
        if not re.search('[0-9][0-9]:[0-9][0-9]:[0-9][0-9]', max_run_time):
            raise ValueError(
                '{0} is not a valid job duration. Format must be HH:MM:SS'.
                format(max_run_time))

        # Safen provided job name or synthesize one if not provided
        job_name = getattr(parsed_args, 'job_name', None)
        if job_name is not None:
            job_name = slugify(job_name, separator='_')
        else:
            job_name = '{0}-job-{1}'.format(app_def['name'], milliseconds())
        if interactive:
            job_name = prompt('Job Name', job_name, allow_empty=False)

        # Build out the job definition
        job = JOB_TEMPLATE

        # Populate notifications config
        notify_job = not (parsed_args.no_notifications)
        if interactive:
            notify_job = prompt_boolean('Send status notifications',
                                        notify_job)
        if notify_job is True:
            try:
                if parsed_args.notifications_uri is not None:
                    nuri = parsed_args.notifications_uri
                else:
                    nuri = self.tapis_client.profiles.get()['email']
                if interactive:
                    nuri = prompt('Status notifications URI',
                                  nuri,
                                  allow_empty=False)
                notification = {'event': '*', 'persistent': True, 'url': nuri}
                job['notifications'].append(notification)
            except Exception:
                pass

        # Populate archiving config
        archive_job = not (parsed_args.no_archive)
        if interactive:
            archive_job = prompt_boolean('Archive job outputs', archive_job)
        job['archive'] = archive_job
        if archive_job:
            aui = getattr(parsed_args, 'archive_uri', None)
            if interactive:
                aui = prompt('Archive destination (Agave URI or leave empty)',
                             aui,
                             allow_empty=True)
                if aui == '':
                    aui = None
            if aui is not None:
                asys, apath = parse_uri(parsed_args.archive_uri)
                job['archiveSystem'] = asys
                job['archivePath'] = apath

        # Populate name and resource requirements
        job['name'] = job_name
        job['appId'] = app_id
        job['batchQueue'] = queue_name
        job['maxRunTime'] = max_run_time
        job['nodeCount'] = node_count
        job['processorsPerNode'] = cpu_per_node
        job['memoryPerNode'] = mem_per_node

        # Populate Inputs
        if interactive:
            print('Inputs')
            print('------')

        for inp in app_def.get('inputs', {}):
            if inp['value']['visible']:
                if inp['value']['required'] or parsed_args.all_fields is True:
                    job['inputs'][inp['id']] = inp['value'].get('default', '')
                    if interactive:
                        inp_label = inp['details']['label']
                        if inp_label is None or inp_label == '':
                            inp_label = inp['id']
                        resp = prompt(inp_label,
                                      job['inputs'][inp['id']],
                                      allow_empty=False)
                        # Validate URI
                        if re.search('^(agave://|http://|https://|ftp://)',
                                     resp):
                            job['inputs'][inp['id']] = resp
                        else:
                            raise ValueError(
                                'Input value {0} must be a URI'.format(resp))

        # Populate Parameters
        #
        # The behavior implemented here is different than the original bash
        # jobs-template in that we make no attempt to fake values for
        # parameters that don't have a default value
        if interactive:
            print('Parameters')
            print('----------')

        for prm in app_def.get('parameters', {}):
            if prm['value']['visible']:
                if prm['value']['required'] or parsed_args.all_fields is True:
                    job['parameters'][prm['id']] = prm['value'].get(
                        'default', '')
                    if job['parameters'][prm['id']] is None:
                        job['parameters'][prm['id']] = ''
                    if interactive:
                        prm_label = prm['details']['label']
                        if prm_label is None or prm_label == '':
                            prm_label = prm['id']
                        # Typecast and validate response
                        resp = prompt(prm_label,
                                      job['parameters'][prm['id']],
                                      allow_empty=False)
                        try:
                            if prm['value']['type'] in ('string',
                                                        'enumeration'):
                                resp = str(resp)
                            elif prm['value']['type'] in ('number'):
                                resp = num(resp)
                            elif prm['value']['type'] in ('bool', 'flag'):
                                resp = parse_boolean(resp)
                        except Exception:
                            raise ValueError(
                                'Unable to typecast {0} to type {1}'.format(
                                    resp, prm['value']['type']))
                        job['parameters'][prm['id']] = resp

        # Raw output
        outfile_dest = parsed_args.output
        if interactive:
            outfile_dest = prompt('Output destination',
                                  outfile_dest,
                                  allow_empty=True)
        if outfile_dest == '':
            of = sys.stdout
        else:
            of = open(outfile_dest, 'w')

        json.dump(job, fp=of, indent=2)
        sys.exit(0)
Esempio n. 7
0
    def take_action(self, parsed_args):
        # Load what we can from credentials cache. Ultimately, if no
        # overrides are specified, the cached contents will be used to
        # populate the Tapis client.

        firstrun()
        interactive = parsed_args.interactive
        # Process overrides to ag_context provided by parsed_args
        mandate_username = False
        mandate_password = False
        mandate_git_reg = interactive

        if interactive:
            print('Configure Tapis API access:')
            print('===========================')

        # Fetch current values stored on disk into ag_context
        try:
            logger.debug('Reading from local Tapis environment')
            ag_context = Agave._read_current(agave_kwargs=True)
        except FileNotFoundError:
            # A very common case (all new tapis_cli installs, for instance), will
            # find NO cache file on disk. This must be recoverable, so set the
            # context to an empty dict
            logger.debug('Read from environment was unsuccessful')
            ag_context = {}
            mandate_username = True
            mandate_password = True

        # Inject optional password key
        ag_context['password'] = None

        # Ensure context has a setting for 'verify'. Default to env setting
        if 'verify' not in ag_context:
            ag_context['verify'] = TAPIS_CLI_VERIFY_SSL

        # Read in from parsed_args, updating ag_context

        # Process tenant override
        # Allow this to happen by either specifying --tenant-id or
        # --api-server. This will trigger the CLI to require user
        # credentials, which can be passed by argument or entered
        # interactively
        parsed_tenant_id = getattr(parsed_args, 'tenant_id', None)
        parsed_api_server = getattr(parsed_args, 'api_server', None)
        parsed_username = getattr(parsed_args, 'username', None)
        parsed_password = getattr(parsed_args, 'password', None)

        # Allow tenant id OR api server to be provided, updating ag_context as appropriate
        # Regarding SSL verification: If the default behavior is to not verify, we will
        # not do verification when accessing the tenants API
        if parsed_tenant_id is not None:
            if ag_context.get('tenant_id', None) != parsed_tenant_id:
                # mandate_git_reg = True
                mandate_username = True
                mandate_password = True
                logger.info('Tenant changed. Credentials will be requested.')
            ag_context['tenant_id'] = parsed_tenant_id
            ag_context['api_server'] = agavepy.tenants.api_server_by_id(
                ag_context['tenant_id'], verify_ssl=TAPIS_CLI_VERIFY_SSL)
        elif parsed_api_server is not None:
            if ag_context.get('api_server', None) != parsed_api_server:
                # mandate_git_reg = True
                mandate_username = True
                mandate_password = True
                logger.info(
                    'API server changed. Credentials will be requested.')
            ag_context['api_server'] = parsed_api_server
            ag_context['tenant_id'] = agavepy.tenants.id_by_api_server(
                ag_context['api_server'], verify_ssl=TAPIS_CLI_VERIFY_SSL)

        # If interactive OR cannot establish tenant_id from cache
        # or args, prompt user to select one
        temp_tenant_id = ag_context.get('tenant_id', None)
        if temp_tenant_id is None or interactive:
            # Pick up default value for tenant_id from settings.TAPIS_DEFAULT_TENANT_ID
            if temp_tenant_id is None:
                temp_tenant_id = TAPIS_DEFAULT_TENANT_ID

            # Get list of tenants
            # tl = [t.get('code') for t in agavepy.tenants.list_tenants()]
            th = ['Name', 'Description', 'URL']
            tr = [[t.get('code'),
                   t.get('name'),
                   t.get('baseUrl')] for t in agavepy.tenants.list_tenants(
                       verify_ssl=TAPIS_CLI_VERIFY_SSL)]
            tl = [t[0] for t in tr]
            pt = PrettyTable()
            pt.field_names = ['Name', 'Description', 'URL']
            for rec in tr:
                pt.add_row(rec)
            print(pt)

            ag_context['tenant_id'] = prompt('Enter a tenant name',
                                             temp_tenant_id)
            if ag_context['tenant_id'] not in tl:
                raise ValueError(
                    'Error: "{0}" is not a valid tenant name'.format(
                        ag_context['tenant_id']))
            # Look up API server from tenant ID
            ag_context['api_server'] = agavepy.tenants.api_server_by_id(
                ag_context['tenant_id'], verify_ssl=TAPIS_CLI_VERIFY_SSL)

        # Prompt for SSL verification
        # From here on out in the workflow the user has indiciated a
        # specific preference re: SSL verification. So, we will use
        # their preference when communicating with APIs (clients, profiles)
        #
        # NOTE: Switching SSL behaviors might cause timeouts due to stale sessions
        if interactive:
            verify_ssl = ag_context.get('verify', TAPIS_CLI_VERIFY_SSL)
            ag_context['verify'] = prompt_boolean('Verify SSL connections',
                                                  verify_ssl)

        # Process --username argument
        if parsed_username is not None:
            # Force re-capture of password via argument or
            # interactive entry if username differs
            if ag_context.get(
                    'username',
                    None) != parsed_username and parsed_args.password is None:
                mandate_password = True
                logger.info('Username changed. Password will be required.')
            ag_context['username'] = parsed_username

        # Process --password argument
        if parsed_password is not None:
            ag_context['password'] = parsed_password

        # Prompt when interactive or username is reset or unavailable
        if interactive or (mandate_username and parsed_username is None):
            mandate_password = True
            ag_context['username'] = prompt('{0} username'.format(
                ag_context['tenant_id'], ag_context.get('username', None)),
                                            secret=False,
                                            allow_empty=False)

        # Prompt when interactive or username is reset or unavailable
        if interactive or (mandate_password and parsed_password is None):
            temp_password = prompt('{0} password for {1}'.format(
                ag_context['tenant_id'], ag_context['username']),
                                   parsed_password,
                                   secret=True,
                                   allow_empty=False)
            ag_context['password'] = temp_password

        # Password was provided. This indicates the Agave client should NOT
        # pass in any cached api_key, api_secret, and token, as it will be
        # interacting with the /clients service, which accepts Basic auth
        if ag_context['password'] is not None:
            ag_context['api_key'] = None
            ag_context['api_secret'] = None
            ag_context['access_token'] = None
            ag_context['refresh_token'] = None

        api_key = ag_context.get('api_key', None)
        ag = None

        # Generate client name
        ag_context['client_name'] = '{0}-{1}-{2}-{3}'.format(
            CLIENT_PREFIX, ag_context['tenant_id'], ag_context['username'],
            get_hostname())

        # No client was loadable from the local system
        if api_key is None or api_key == '':
            logger.debug('clients.create: {0}'.format(
                ag_context['client_name']))
            create_context = {
                'api_server': ag_context['api_server'],
                'username': ag_context['username'],
                'password': ag_context['password'],
                'verify': ag_context['verify']
            }
            ag = Agave(**create_context)

            # Preflight activity: Get rid of existing client
            try:
                ag.clients.delete(clientName=ag_context['client_name'])
            except Exception:
                logger.debug('Tapis client was not deleted')
                pass

            try:
                ag.clients.create(
                    body={
                        'clientName':
                        ag_context['client_name'],
                        'description':
                        'Generated by {0}@{1} at {2}'.format(
                            get_local_username(), get_public_ip(),
                            datetime.datetime.utcnow().strftime(
                                "%Y-%m-%dT%H:%M:%SZ"))
                    })
            except Exception as err:
                logger.error('Tapis client was not created')
                raise AgaveError(TAPIS_AUTH_REJECT)
        else:
            # Client was cached - load it up
            logger.debug('Loading client from cache...')
            try:
                ag = Agave(**ag_context)
            except Exception as err:
                logger.error(
                    'Tapis client was not loaded from cache: {0}'.format(err))
                raise AgaveError(TAPIS_AUTH_FAIL)

        # Hit profiles service to check client
        try:
            ag.profiles.get()
        except Exception as err:
            logger.error(
                'Tapis client was unable to make an authenticated API call.')
            raise AgaveError(TAPIS_AUTH_FAIL)

        # Formulate a table view of key values for current session
        headers = [
            'tenant_id', 'username', 'client_name', 'api_key', 'access_token',
            'expires_at', 'verify'
        ]
        data = [
            # Coerce to string to avoid failures where a deepcopy
            # operation in Python's implementation of tuple() is
            # unable to accomodate copying properties of an Agave object
            str(ag.tenant_id),
            str(ag.username),
            str(ag_context['client_name']),
            str(ag.api_key),
            str(ag._token),
            str(ag.expires_at),
            str(ag.verify)
        ]

        # Extend headers and data with docker and git workflows
        (headers, data) = registry.init.interactive(parsed_args, headers, data,
                                                    mandate_git_reg)
        (headers, data) = gitserver.init.interactive(parsed_args, headers,
                                                     data, mandate_git_reg)

        et.phone_home()
        return (tuple(headers), tuple(data))