def set_up_auth(self, mock_header):
        # set up authentication options for the tests
        temp_dir = tempfile.mkdtemp()
        secrets_file = temp_dir + 'fake_secrets.json'
        service_account_key = os.path.join(temp_dir, 'fake_key.json')
        username = "******"
        password = "******"
        url = "https://fake_url"
        auth = {"url": url, "username": username, "password": password}
        with open(secrets_file, 'w') as f:
            json.dump(auth, f)
        mock_header.return_value = CromwellAuth(
            url=url, header={"Authorization": "bearer fake_token"}, auth=None)

        auth_options = (
            CromwellAuth.harmonize_credentials(**auth),  # HTTPBasicAuth
            CromwellAuth.harmonize_credentials(
                **{"secrets_file": secrets_file}),  # Secret file
            CromwellAuth.harmonize_credentials(**{
                "service_account_key": service_account_key,
                "url": url
            }),  # OAuth
            CromwellAuth.harmonize_credentials(url=url),  # No Auth
        )
        return auth_options
Example #2
0
def test_harmonize_credentials_only_takes_one_auth_type(mock_header):
    url = 'https://cromwell.server.org'
    expected_auth = CromwellAuth(url=url,
                                 header={"Authorization": "bearer fake_token"},
                                 auth=None)
    mock_header.return_value = expected_auth
    with pytest.raises(ValueError):
        CromwellAuth.harmonize_credentials(**auth_types)
Example #3
0
def test_harmonize_credentials_from_service_account_key(mock_header):
    service_account_key = 'fake_key.json'
    url = 'https://cromwell.server.org'
    expected_auth = CromwellAuth(url=url,
                                 header={"Authorization": "bearer fake_token"},
                                 auth=None)
    mock_header.return_value = expected_auth
    auth = CromwellAuth.harmonize_credentials(
        url=url, service_account_key=service_account_key)
    assert auth == expected_auth
Example #4
0
def get_cromwell_auth(settings):
    cromwell_url = settings.get("cromwell_url")
    if settings.get("use_caas"):
        return CromwellAuth.harmonize_credentials(
            url=cromwell_url, service_account_key=settings.get("caas_key"))
    return CromwellAuth.harmonize_credentials(
        url=cromwell_url,
        username=settings.get("cromwell_user"),
        password=settings.get("cromwell_password"),
    )
Example #5
0
def test_harmonize_credentials_from_secrets_file():
    username = "******"
    password = "******"
    url = "https://fake_url"
    expected_auth = CromwellAuth(url=url,
                                 header=None,
                                 auth=requests.auth.HTTPBasicAuth(
                                     username, password))
    auth = CromwellAuth.harmonize_credentials(
        secrets_file=auth_types['secrets_file']['secrets_file'])
    assert auth.auth == expected_auth.auth
    assert auth.header == expected_auth.header
Example #6
0
def test_harmonize_credentials_from_service_account_key_content(mock_header):
    service_account_key = {
        'client_email': 'fake_email',
        'token_uri': 'fake_uri'
    }
    url = 'https://cromwell.server.org'
    expected_auth = CromwellAuth(url=url,
                                 header={"Authorization": "bearer fake_token"},
                                 auth=None)
    mock_header.return_value = expected_auth
    auth = CromwellAuth.harmonize_credentials(
        url=url, service_account_key=service_account_key)
    assert auth == expected_auth
Example #7
0
def test_harmonize_credentials_user_password():
    username = '******'
    password = '******'
    url = 'https://cromwell.server.org'
    expected_auth = CromwellAuth(url=url,
                                 header=None,
                                 auth=requests.auth.HTTPBasicAuth(
                                     username, password))
    auth = CromwellAuth.harmonize_credentials(username=username,
                                              password=password,
                                              url=url)
    assert auth.auth == expected_auth.auth
    assert auth.header == expected_auth.header
    def test_if_compose_oauth_options_for_jes_backend_cromwell_can_deal_with_null_workflow_options(
        self, ):
        test_url = 'https://fake_url'
        test_service_account_key = 'data/fake_account_key.json'
        with open(test_service_account_key, 'r') as f:
            test_service_account_key_content = json.load(f)

        test_auth = CromwellAuth(
            url=test_url,
            header={"Authorization": "bearer fake_token"},
            auth=None,
            service_key_content=test_service_account_key_content,
        )

        result_options = utils.compose_oauth_options_for_jes_backend_cromwell(
            test_auth)
        result_options_in_dict = json.loads(result_options.getvalue())

        assert (result_options_in_dict['google_project'] ==
                test_service_account_key_content['project_id'])
        assert (result_options_in_dict['google_compute_service_account'] ==
                test_service_account_key_content['client_email'])
        assert result_options_in_dict[
            'user_service_account_json'] == json.dumps(
                test_service_account_key_content)
Example #9
0
    def test_compose_oauth_options_for_jes_backend_cromwell_add_required_fields_to_workflow_options(
        self, ):
        test_url = 'https://fake_url'
        test_service_account_key = 'data/fake_account_key.json'
        with open(test_service_account_key, 'r') as f:
            test_service_account_key_content = json.load(f)

        test_auth = CromwellAuth(
            url=test_url,
            header={"Authorization": "bearer fake_token"},
            auth=None,
            service_key_content=test_service_account_key_content,
        )

        result_options = utils.compose_oauth_options_for_jes_backend_cromwell(
            test_auth, self.options_file_BytesIO)
        # use .decode('utf-8') for Python3.5 compatibility
        result_options_in_dict = json.loads(
            result_options.getvalue().decode('utf-8'))

        assert (
            # use .decode('utf-8') for Python3.5 compatibility
            result_options_in_dict['read_from_cache'] == json.loads(
                self.options_file_BytesIO.getvalue().decode('utf-8'))
            ['read_from_cache'])
        assert (result_options_in_dict['google_project'] ==
                test_service_account_key_content['project_id'])
        assert (result_options_in_dict['google_compute_service_account'] ==
                test_service_account_key_content['client_email'])
        assert result_options_in_dict[
            'user_service_account_json'] == json.dumps(
                test_service_account_key_content)
Example #10
0
def submit_to_cromwell(args):
    # Get authentication (as no authentication)
    auth = CromwellAuth.from_no_authentication(
        url="http://localhost:{}".format(args.webservice_port))

    response = api.submit(auth=auth,
                          wdl_file=args.workflow_source,
                          inputs_files=args.workflow_inputs,
                          dependencies=args.workflow_dependencies,
                          validate_labels=True)

    # FIXME - need to test this first a bit
    print(response)
Example #11
0
def test_cli_command_works_with_service_account_auth(mock_header,
                                                     service_account_auth):
    """Use the submit command as an example to prove CLI works with u/p auth."""
    expected_auth = CromwellAuth(
        url="https://fake-cromwell",
        header={"Authorization": "bearer fake_token"},
        auth=None,
    )
    mock_header.return_value = expected_auth
    user_inputs = [
        "submit",
        "--wdl-file",
        "fake.wdl",
        "--inputs-files",
        "fake.json",
    ] + service_account_auth
    command, args = cli_parser(user_inputs)
Example #12
0
def test_harmonize_credentials_from_no_authentication():
    url = "https://fake_url"
    expected_auth = CromwellAuth(url=url, header=None, auth=None)
    auth = CromwellAuth.harmonize_credentials(url=url)
    assert auth.auth == expected_auth.auth
    assert auth.header == expected_auth.header
Example #13
0
def parser(arguments=None):
    # TODO: dynamically walk through the commands and automatcally create parsers here

    main_parser = DefaultHelpParser()

    # Check the installed version of Cromwell-tools
    main_parser.add_argument(
        '-V',
        '--version',
        action='version',
        version=f'%(prog)s {cromwell_tools_version}',
    )

    subparsers = main_parser.add_subparsers(help='sub-command help',
                                            dest='command')

    # sub-commands of cromwell-tools
    submit = subparsers.add_parser(
        'submit',
        help='submit help',
        description='Submit a WDL workflow on Cromwell.')
    wait = subparsers.add_parser(
        'wait',
        help='wait help',
        description='Wait for one or more running workflow to finish.',
    )
    status = subparsers.add_parser(
        'status',
        help='status help',
        description='Get the status of one or more workflows.',
    )
    abort = subparsers.add_parser(
        'abort',
        help='abort help',
        description='Request Cromwell to abort a running workflow by UUID.',
    )
    release_hold = subparsers.add_parser(
        'release_hold',
        help='release_hold help',
        description='Request Cromwell to release the hold on a workflow.',
    )
    metadata = subparsers.add_parser(
        'metadata',
        help='metadata help',
        description=
        'Retrieve the workflow and call-level metadata for a specified workflow by UUID.',
    )
    query = subparsers.add_parser(
        'query',
        help='query help',
        description='[NOT IMPLEMENTED IN CLI] Query for workflows.',
    )
    health = subparsers.add_parser(
        'health',
        help='health help',
        description=
        'Check that cromwell is running and that provided authentication is valid.',
    )
    task_runtime = subparsers.add_parser(
        'task_runtime',
        help='task_runtime help',
        description=
        'Output tsv breakdown of task runtimes by execution event categories',
    )

    # cromwell url and authentication arguments apply to all sub-commands
    cromwell_sub_commands = (
        submit,
        wait,
        status,
        abort,
        release_hold,
        metadata,
        query,
        health,
        task_runtime,
    )
    auth_args = {
        'url':
        'The URL to the Cromwell server. e.g. "https://cromwell.server.org/"',
        'username':
        '******',
        'password':
        '******',
        'secrets_file':
        'Path to the JSON file containing username, password, and url fields.',
        'service_account_key':
        'Path to the JSON key file for authenticating with CaaS.',
    }

    def add_auth_args(subcommand_parser):
        for arg_dest, help_text in auth_args.items():
            subcommand_parser.add_argument(
                '--{arg}'.format(arg=arg_dest.replace('_', '-')),
                dest=arg_dest,
                default=None,
                type=str,
                help=help_text,
            )

    # TODO: this should be a group which is called authentication
    for p in cromwell_sub_commands:
        add_auth_args(p)

    # submit arguments
    submit.add_argument(
        '-w',
        '--wdl-file',
        dest='wdl_file',
        type=str,
        required=True,
        help='Path to the workflow source file to submit for execution.',
    )
    submit.add_argument(
        '-i',
        '--inputs-files',
        dest='inputs_files',
        nargs='+',
        type=str,
        required=True,
        help=
        'Path(s) to the input file(s) containing input data in JSON format, separated by space.',
    )
    submit.add_argument(
        '-d',
        '--deps-file',
        dest='dependencies',
        nargs='+',
        type=str,
        help=
        'Path to the Zip file containing dependencies, or a list of raw dependency files to '
        'be zipped together separated by space.',
    )
    submit.add_argument(
        '-o',
        '--options-file',
        dest='options_file',
        type=str,
        help='Path to the Cromwell configs JSON file.',
    )
    # TODO: add a mutually exclusive group to make it easy to add labels for users
    submit.add_argument(
        '-l',
        '--label-file',
        dest='label_file',
        type=str,
        default=None,
        help=
        'Path to the JSON file containing a collection of key/value pairs for workflow labels.',
    )
    submit.add_argument(
        '-c',
        '--collection-name',
        dest='collection_name',
        type=str,
        default=None,
        help=
        'Collection in SAM that the workflow should belong to, if use CaaS.',
    )
    submit.add_argument(
        '--on-hold',
        dest='on_hold',
        type=bool,
        default=False,
        help='Whether to submit the workflow in "On Hold" status.',
    )
    submit.add_argument(
        '--validate-labels',
        dest='validate_labels',
        type=bool,
        default=False,
        help='Whether to validate cromwell labels.',
    )

    # wait arguments
    wait.add_argument('workflow_ids', nargs='+')
    wait.add_argument(
        '--timeout-minutes',
        dest='timeout_minutes',
        type=int,
        default=120,
        help='number of minutes to wait before timeout.',
    )
    wait.add_argument(
        '--poll-interval-seconds',
        dest='poll_interval_seconds',
        type=int,
        default=30,
        help='seconds between polling cromwell for workflow status.',
    )
    wait.add_argument(
        '--silent',
        dest='verbose',
        action='store_false',
        help=
        'whether to silently print verbose workflow information while polling cromwell.',
    )

    # status arguments
    status.add_argument(
        '--uuid',
        required=True,
        help='A Cromwell workflow UUID, which is the workflow identifier.',
    )

    # abort arguments
    abort.add_argument(
        '--uuid',
        required=True,
        help='A Cromwell workflow UUID, which is the workflow identifier.',
    )

    # release_hold arguments
    release_hold.add_argument(
        '--uuid',
        required=True,
        help='A Cromwell workflow UUID, which is the workflow identifier.',
    )

    # metadata arguments
    metadata.add_argument(
        '--uuid',
        required=True,
        help='A Cromwell workflow UUID, which is the workflow identifier.',
    )
    # TODO: add a mutually exclusive group to make it fail early
    metadata.add_argument(
        '--includeKey',
        nargs='+',
        default=None,
        help=
        'When specified key(s) to include from the metadata. Matches any key starting with the value. May not be used with excludeKey.',
    )
    metadata.add_argument(
        '--excludeKey',
        nargs='+',
        default=None,
        help=
        'When specified key(s) to exclude from the metadata. Matches any key starting with the value. May not be used with includeKey.',
    )
    metadata.add_argument(
        '--expandSubWorkflows',
        default=False,
        help=
        'When true, metadata for sub workflows will be fetched and inserted automatically in the metadata response.',
    )
    either_runtime = task_runtime.add_mutually_exclusive_group(required=True)
    either_runtime.add_argument(
        '--metadata',
        dest='metadata',
        help='Metadata json file to calculate cost on',
    )
    either_runtime.add_argument(
        '--uuid',
        dest='uuid',
        help='A Cromwell workflow UUID, which is the workflow identifier.',
    )
    # query arguments
    # TODO: implement CLI entry for query API.

    # group all of the arguments
    args = vars(main_parser.parse_args(arguments))

    # Return help messages if no arguments provided
    if not args['command']:
        main_parser.error("No commands/arguments provided!")

    # TODO: see if this can be moved or if the commands can be populated from above
    if args['command'] in (
            'submit',
            'wait',
            'status',
            'abort',
            'release_hold',
            'health',
            'metadata',
            'task_runtime',
    ):
        auth_arg_dict = {k: args.get(k) for k in auth_args.keys()}
        auth = CromwellAuth.harmonize_credentials(**auth_arg_dict)
        args['auth'] = auth
        for k in auth_args:
            if k in args:
                del args[k]
    command = getattr(CromwellAPI, args['command'], False)
    if not command:
        try:
            command = diagnostic_index[args['command']]
        except KeyError:
            raise KeyError(f"{args['command']} is not a valid command.")
    del args['command']
    return command, args
Example #14
0
def run_wdl(ls: Server, params: Tuple[RunWDLParams]):
    wdl_uri = params[0].wdl_uri
    wdl_path = urlparse(wdl_uri).path

    _, wdl = _parse_wdl(ls, wdl_uri)
    if not wdl:
        return ls.show_message('Unable to submit: WDL contains error(s)', MessageType.Error)

    config = _get_client_config(ls)
    auth = CromwellAuth.from_no_authentication(config.cromwell.url)
    workflow = cromwell_api.submit(
        auth, wdl_path, raise_for_status=True,
    ).json()
    id = workflow['id']

    title = 'Workflow {} for {}'.format(id, wdl_path)
    _progress(ls, 'start', {
        'id': id,
        'title': title,
        'cancellable': True,
        'message': workflow['status'],
    })

    status: str = ''
    while True:
        if status != workflow['status']:
            status = workflow['status']
            if status == 'Succeeded':
                message_type = MessageType.Info
            elif status in ('Aborting', 'Aborted'):
                message_type = MessageType.Warning
            elif status == 'Failed':
                message_type = MessageType.Error
            else:
                _progress(ls, 'report', {
                    'id': id,
                    'message': status,
                })
                continue

            _progress(ls, 'done', {
                'id': id,
            })
            message = '{}: {}'.format(title, status)
            ls.show_message(message, message_type)

            diagnostics = _parse_failures(wdl, wdl_uri, id, auth)
            return ls.publish_diagnostics(wdl_uri, diagnostics)

        sleep(config.cromwell.pollSec)

        if id in ls.aborting_workflows:
            workflow = cromwell_api.abort(
                id, auth, raise_for_status=True,
            ).json()
            ls.aborting_workflows.remove(id)
            continue

        try:
            workflow = cromwell_api.status(
                id, auth, raise_for_status=True,
            ).json()
        except HTTPError as e:
            ls.show_message_log(str(e), MessageType.Error)
def calculate_metric(template_values, scan_values):
    merged_values = {
        k: v
        for (k, v) in (template_values.items() + scan_values.items())
    }

    merged_json_file, merged_json_path = tempfile.mkstemp()
    with open(merged_json_path, 'w') as f:
        json.dump(merged_values, f)

    cromwell_auth = CromwellAuth(url=args.cromwell_server,
                                 header={'Authorization': 'bearer fake_token'},
                                 auth=None)
    with open(args.workflow_wdl, 'r') as w, open(merged_json_path, 'r') as j:
        submit = CromwellAPI.submit(cromwell_auth, w, j)

    workflow_id = submit.json()['id']
    logger.info('Submitted workflow: ' + workflow_id)

    time.sleep(5)
    logger.info('Waiting for workflow to complete...')

    # Query workflow status indefinitely until success or failure returned.
    # If success returned, attempt to retrieve objective_value from metadata and return.
    # If failure returned or if exception raised during metadata retreival, return bad_value.
    try:
        while True:
            try:
                CromwellAPI.wait([workflow_id],
                                 cromwell_auth,
                                 timeout_minutes=600,
                                 poll_interval_seconds=20,
                                 verbose=False)
                response = CromwellAPI.status(workflow_id, cromwell_auth)
                status = response.json()['status']
                if status == 'Succeeded':
                    logger.info('Workflow succeeded...')
                    break
            except WorkflowFailedException:
                logger.info('Workflow failed, returning bad value...')
                return bad_value
            except Exception as e:
                logger.info(e)
                logger.info(
                    'Cromwell exception, retrying wait and status check...')
        logger.info('Getting metadata...')
        session = retry_session(retries=10)
        metadata = session.post(
            url=cromwell_auth.url +
            CromwellAPI._metadata_endpoint.format(uuid=workflow_id),
            auth=cromwell_auth.auth,
            headers=cromwell_auth.header)
        workflow_name = metadata.json()['workflowName']
        objective_value = metadata.json()['outputs'][
            '{}.objective_value'.format(workflow_name)]
        return objective_value
    except Exception as e:
        logger.info(e)
        logger.info(
            'Cromwell exception during metadata retrieval, returning bad value...'
        )
        return bad_value
def get_cromwell_auth(url):
    # Provides cromwell authentication to be consumed by all API functions
    # Right now we only implement default authorization (no auth other than server URL)
    # Can expand upon this later but for now it really doesn't matter
    return CromwellAuth.harmonize_credentials(url=url)