def terminal_message(message,
                     subject=None,
                     color="green",
                     footer=None,
                     indent=True):
    """
    Print a nicely formatted message as output to the user using a ``rich`` ``Panel``.

    :param: message: The message to print out
    :param: subject: An optional subject line to add in the header of the ``Panel``
    :param: color:   An optional color to style the ``subject`` header with
    :param: footer:  An optional message to display in the footer of the ``Panel``
    :param: indent:  Adds padding to the left of the message
    """
    panel_kwargs = dict(padding=1)
    if subject is not None:
        panel_kwargs["title"] = f"[{color}]{subject}"
    if footer is not None:
        panel_kwargs["subtitle"] = f"[dim italic]{footer}[/dim italic]"
    text = dedent(message)
    if indent:
        text = indent_text(text, prefix="  ")
    console = Console()
    console.print()
    console.print(Panel(text, **panel_kwargs))
    console.print()
def test_dump_full_config(tmp_path):
    application_path = tmp_path / "dummy"
    application_path.mkdir()
    template_root_path = application_path / "templates"
    template_root_path.mkdir()
    file1 = template_root_path / "file1"
    file1.write_text("foo")
    file2 = template_root_path / "file2"
    file2.write_text("bar")
    config_path = application_path / JOBBERGATE_APPLICATION_CONFIG_FILE_NAME
    config_path.write_text(
        dedent("""
            jobbergate_config:
              default_template: test-job-script.py.j2
              output_directory: .
            application_config:
              partition: debug
            """))

    assert yaml.safe_load(dump_full_config(application_path)) == dict(
        jobbergate_config=dict(
            default_template="test-job-script.py.j2",
            output_directory=".",
            template_files=[
                "templates/file1",
                "templates/file2",
            ],
        ),
        application_config=dict(partition="debug", ),
    )
def test_validate_application_files__success(tmp_path):
    application_path = tmp_path / "dummy"
    application_path.mkdir()
    application_module = application_path / JOBBERGATE_APPLICATION_MODULE_FILE_NAME
    application_module.write_text(
        dedent("""
            import sys

            print(f"Got some args, yo: {sys.argv}")
            """))
    application_config = application_path / JOBBERGATE_APPLICATION_CONFIG_FILE_NAME
    application_config.write_text(
        dedent("""
            foo:
              bar: baz
            """))
    validate_application_files(application_path)
def dummy_template_source():
    return dedent("""
        #!/bin/python3

        #SBATCH -J dummy_job
        #SBATCH -t 60
        print("I am a very, very dumb job script")
        print(f"foo='{{foo}}'")
        print(f"bar='{{bar}}'")
        print(f"baz='{{baz}}'")
        """)
def dummy_module_source():
    return dedent("""
        from jobbergate_cli.subapps.applications.application_base import JobbergateApplicationBase
        from jobbergate_cli.subapps.applications.questions import Text


        class JobbergateApplication(JobbergateApplicationBase):

            def mainflow(self, data):
                data["nextworkflow"] = "subflow"
                return [Text("foo", message="gimme the foo!"), Text("bar", message="gimme the bar!")]

            def subflow(self, data):
                return [Text("baz", message="gimme the baz!", default="zab")]
        """)
def dummy_config_source():
    return dedent("""
        jobbergate_config:
          default_template: test-job-script.py.j2
          template_files:
            - test-job-script.py.j2
          output_directory: .
          supporting_files_output_name:
          supporting_files:
          job_script_name:
        application_config:
          foo: foo
          bar: bar
          baz: baz
        """)
def test_load_application_data__fails_if_application_config_is_not_valid_JobbergateApplicationConfig(
    dummy_module_source, ):
    app_data = ApplicationResponse(
        id=13,
        application_name="dummy",
        application_owner_email="*****@*****.**",
        application_uploaded=True,
        application_file=dummy_module_source,
        application_config=dedent("""
            foo: bar
            """),
    )

    with pytest.raises(
            Abort,
            match="The application config fetched from the API is not valid"):
        load_application_data(app_data)
    def wrapper(*args, **kwargs):
        try:
            func(*args, **kwargs)
        except Abort as err:
            if not err.warn_only:
                if err.log_message is not None:
                    logger.error(err.log_message)

                if err.original_error is not None:
                    logger.error(f"Original exception: {err.original_error}")

                if settings.SENTRY_DSN:
                    with sentry_sdk.push_scope() as scope:
                        if err.sentry_context is not None:
                            scope.set_context(**err.sentry_context)
                        sentry_sdk.capture_exception(err.original_error if err.original_error is not None else err)
                        sentry_sdk.flush()

            panel_kwargs = dict()
            if err.subject is not None:
                panel_kwargs["title"] = f"[red]{err.subject}"
            message = dedent(err.message)
            if err.support:
                support_message = unwrap(
                    f"""
                    [yellow]If the problem persists,
                    please contact [bold]{OV_CONTACT}[/bold]
                    for support and trouble-shooting
                    """
                )
                message = f"{message}\n\n{support_message}"

            console = Console()
            console.print()
            console.print(Panel(message, **panel_kwargs))
            console.print()
            raise typer.Exit(code=1)
Provide a stub module to maintain compatibility with previous versions.

Issue a deprecation warning when this module is imported from if JOBBERGATE_COMPATIBILITY_MODE is enabled.

If JOBBERGATE_COMPATIBILITY_MODE is not enabled, raise an import error when this module is imported.
"""

import warnings

from jobbergate_cli.config import settings
from jobbergate_cli.text_tools import dedent, unwrap

if settings.JOBBERGATE_COMPATIBILITY_MODE:

    from jobbergate_cli.subapps.applications.application_base import JobbergateApplicationBase  # noqa

    warnings.warn(
        dedent("""
            Importing application_base from jobbergate_cli is deprecated.
            The module has been moved.
            Import 'application_base' from 'jobbergate_cli.subapps.applications' instead",
            """),
        DeprecationWarning,
    )
else:
    raise ImportError(
        unwrap("""
            JobbergateApplicationBase has been moved to
            'jobbergate_cli.subapps.applications.application_base.JobbergateApplicationBase'
            """))
Beispiel #10
0
def create(
    ctx: typer.Context,
    name: str = typer.Option(
        ...,
        help="The name of the job script to create.",
    ),
    application_id: Optional[int] = typer.Option(
        None,
        help="The id of the application from which to create the job script.",
    ),
    application_identifier: Optional[str] = typer.Option(
        None,
        help=
        "The identifier of the application from which to create the job script.",
    ),
    sbatch_params: Optional[List[str]] = typer.Option(
        None,
        help="Optional parameter to submit raw sbatch parameters.",
    ),
    param_file: Optional[pathlib.Path] = typer.Option(
        None,
        help=dedent("""
            Supply a yaml file that contains the parameters for populating templates.
            If this is not supplied, the question asking in the application is triggered.
            """),
    ),
    fast: bool = typer.Option(
        False,
        help="Use default answers (when available) instead of asking the user.",
    ),
    submit: Optional[bool] = typer.Option(
        None,
        help="Do not ask the user if they want to submit a job.",
    ),
):
    """
    Create a new job script.
    """
    jg_ctx: JobbergateContext = ctx.obj

    # Make static type checkers happy
    assert jg_ctx.client is not None

    app_data = fetch_application_data(jg_ctx,
                                      id=application_id,
                                      identifier=application_identifier)
    (app_config, app_module) = load_application_data(app_data)

    request_data = JobScriptCreateRequestData(
        application_id=app_data.id,
        job_script_name=name,
        sbatch_params=sbatch_params,
        param_dict=app_config,
    )

    supplied_params = validate_parameter_file(
        param_file) if param_file else dict()
    execute_application(app_module,
                        app_config,
                        supplied_params,
                        fast_mode=fast)

    if app_config.jobbergate_config.job_script_name is not None:
        request_data.job_script_name = app_config.jobbergate_config.job_script_name

    job_script_result = cast(
        JobScriptResponse,
        make_request(
            jg_ctx.client,
            "/job-scripts",
            "POST",
            expected_status=201,
            abort_message="Couldn't create job script",
            support=True,
            request_model=request_data,
            response_model_cls=JobScriptResponse,
        ),
    )
    render_single_result(
        jg_ctx,
        job_script_result,
        hidden_fields=HIDDEN_FIELDS,
        title="Created Job Script",
    )

    # `submit` will be `None` --submit/--no-submit flag was not set
    if submit is None:

        # If not running in "fast" mode, ask the user what to do.
        if not fast:
            submit = typer.confirm(
                "Would you like to submit this job immediately?")

        # Otherwise, assume that the job script should be submitted immediately
        else:
            submit = True

    if not submit:
        return

    try:
        job_submission_result = create_job_submission(
            jg_ctx, job_script_result.id, job_script_result.job_script_name)
    except Exception as err:
        raise Abort(
            "Failed to immediately submit the job after job script creation.",
            subject="Automatic job submission failed",
            support=True,
            log_message=
            f"There was an issue submitting the job immediately job_script_id={job_script_result.id}.",
            original_error=err,
        )

    render_single_result(
        jg_ctx,
        job_submission_result,
        hidden_fields=JOB_SUBMISSION_HIDDEN_FIELDS,
        title="Created Job Submission (Fast Mode)",
    )
Beispiel #11
0
Provide a stub module to maintain compatibility with previous versions.

Issue a deprecation warning when this module is imported from if JOBBERGATE_COMPATIBILITY_MODE is enabled.

If JOBBERGATE_COMPATIBILITY_MODE is not enabled, raise an import error when this module is imported.
"""

import warnings

from jobbergate_cli.config import settings
from jobbergate_cli.text_tools import dedent, unwrap

if settings.JOBBERGATE_COMPATIBILITY_MODE:

    from jobbergate_cli.subapps.applications.application_helpers import *  # noqa

    warnings.warn(
        dedent("""
            Importing jobberappslib from jobbergate_cli is deprecated.
            The module has been moved.
            Import the helper functions from 'jobbergate_cli.subapps.applications.application_helpers' instead",
            """),
        DeprecationWarning,
    )
else:
    raise ImportError(
        unwrap("""
            The 'jobberappslib' module has been renamed to 'application_helpers' and has been moved to
            'jobbergate_cli.subapps.applications.application_helpers'
            """))