diff --git a/CHANGES.rst b/CHANGES.rst index a9117778..49ea1c7f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,7 @@ Version 0.9.0 (UNRELEASED) - Adds support for Python 3.11. - Adds support for ``.reanaignore`` during file upload. Files that match ``.reanaignore`` will not be uploaded. - Adds support for ``.gitignore`` during file upload. Files that match ``.gitignore`` will not be uploaded. +- Adds ``retention-rules-list`` command to list the retention rules of a workflow. - Changes ``delete`` CLI command to always delete workspace. - Changes ``delete_workflow`` API method to always delete workspace. - Changes ``list`` to hide deleted workflows by default. diff --git a/docs/cmd_list.txt b/docs/cmd_list.txt index c6e41433..6623ab7d 100644 --- a/docs/cmd_list.txt +++ b/docs/cmd_list.txt @@ -42,6 +42,9 @@ Workspace file management commands: rm Delete files from workspace. upload Upload files and directories to workspace. +Workspace file retention commands: + retention-rules-list List the retention rules for a workflow. + Secret management commands: secrets-add Add secrets from literal string or from file. secrets-delete Delete user secrets by name. diff --git a/reana_client/api/client.py b/reana_client/api/client.py index 3975688d..f3d1d5f3 100644 --- a/reana_client/api/client.py +++ b/reana_client/api/client.py @@ -1062,3 +1062,32 @@ def info(access_token): raise Exception(e.response.json()["message"]) except Exception as e: raise e + + +def get_workflow_retention_rules(workflow, access_token): + """Get the retention rules of a workflow.""" + try: + ( + response, + http_response, + ) = current_rs_api_client.api.get_workflow_retention_rules( + workflow_id_or_name=workflow, + access_token=access_token, + ).result() + if http_response.status_code == 200: + return response + else: + raise Exception( + "Expected status code 200 but replied with " + "{status_code}".format(status_code=http_response.status_code) + ) + + except HTTPError as e: + logging.debug( + "Workflow retention rules could not be retrieved: " + "\nStatus: {}\nReason: {}\n" + "Message: {}".format( + e.response.status_code, e.response.reason, e.response.json()["message"] + ) + ) + raise Exception(e.response.json()["message"]) diff --git a/reana_client/cli/__init__.py b/reana_client/cli/__init__.py index b87b4bf0..508f0c7b 100644 --- a/reana_client/cli/__init__.py +++ b/reana_client/cli/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # This file is part of REANA. -# Copyright (C) 2017, 2018, 2019, 2020, 2021 CERN. +# Copyright (C) 2017, 2018, 2019, 2020, 2021, 2022 CERN. # # REANA is free software; you can redistribute it and/or modify it # under the terms of the MIT License; see LICENSE file for more details. @@ -13,7 +13,7 @@ import click from urllib3 import disable_warnings -from reana_client.cli import workflow, files, ping, secrets, quotas +from reana_client.cli import workflow, files, ping, secrets, quotas, retention_rules from reana_client.utils import get_api_url DEBUG_LOG_FORMAT = ( @@ -43,6 +43,7 @@ class ReanaCLI(click.Group): workflow.workflow_execution_group, workflow.interactive_group, files.files_group, + retention_rules.retention_rules_group, secrets.secrets_group, ] diff --git a/reana_client/cli/retention_rules.py b/reana_client/cli/retention_rules.py new file mode 100644 index 00000000..885a2f97 --- /dev/null +++ b/reana_client/cli/retention_rules.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# +# This file is part of REANA. +# Copyright (C) 2022 CERN. +# +# REANA is free software; you can redistribute it and/or modify it +# under the terms of the MIT License; see LICENSE file for more details. +"""REANA client retention-rules related commands.""" + +import logging +import sys +from typing import Optional, Tuple + +import click + +from reana_client.api.client import get_workflow_retention_rules +from reana_client.cli.utils import ( + add_access_token_options, + add_workflow_option, + check_connection, + display_formatted_output, +) +from reana_client.printer import display_message + + +@click.group(help="Workspace file retention commands") +def retention_rules_group(): + """Workspace file retention commands.""" + pass + + +@retention_rules_group.command() +@add_workflow_option +@add_access_token_options +@check_connection +@click.option( + "--format", + "_format", + multiple=True, + help="Format output according to column titles or column values. " + "Use `=` format. " + "E.g. display pattern and status of active retention rules " + "`--format workspace_files,status=active`.", +) +@click.option( + "--json", + "output_format", + flag_value="json", + default=None, + help="Get output in JSON format.", +) +def retention_rules_list( + access_token: str, workflow: str, _format: Tuple[str], output_format: Optional[str] +) -> None: # noqa: D301 + """List the retention rules for a workflow. + + Example:\n + \t $ reana-client retention-rules-list -w myanalysis.42 + """ + try: + rules = get_workflow_retention_rules(workflow, access_token).get( + "retention_rules", [] + ) + except Exception as e: + logging.debug(e, exc_info=True) + display_message(str(e), msg_type="error") + sys.exit(1) + + sorted_rules = sorted(rules, key=lambda rule: rule["retention_days"]) + + headers = ["workspace_files", "retention_days", "apply_on", "status"] + rows = [[rule[h] or "-" for h in headers] for rule in sorted_rules] + display_formatted_output(rows, headers, _format, output_format) diff --git a/tests/test_cli_retention_rules.py b/tests/test_cli_retention_rules.py new file mode 100644 index 00000000..2044c632 --- /dev/null +++ b/tests/test_cli_retention_rules.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# +# This file is part of REANA. +# Copyright (C) 2022 CERN. +# +# REANA is free software; you can redistribute it and/or modify it +# under the terms of the MIT License; see LICENSE file for more details. +"""REANA client retention rules tests.""" + +from unittest.mock import MagicMock, patch + +from click.testing import CliRunner + +from reana_client.cli import cli + + +@patch("reana_client.cli.retention_rules.get_workflow_retention_rules") +def test_retention_rules_list(mock_get_workflow_retention_rules: MagicMock): + """Test retention-rules-list command.""" + workflow_id = "123456" + workflow_name = "workflow" + access_token = "secret" + pattern = "*.tmp" + mock_get_workflow_retention_rules.return_value = { + "workflow_id": workflow_id, + "workflow_name": workflow_name, + "retention_rules": [ + { + "workspace_files": pattern, + "status": "created", + "apply_on": None, + "retention_days": 42, + }, + ], + } + + runner = CliRunner(env={"REANA_SERVER_URL": "localhost"}) + result = runner.invoke( + cli, ["retention-rules-list", "-w", workflow_name, "-t", access_token] + ) + + assert result.exit_code == 0 + assert pattern in result.output + mock_get_workflow_retention_rules.assert_called_once_with( + workflow_name, access_token + )