diff --git a/AGENTS.md b/AGENTS.md index bdebb748c..cd66286e9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,6 +25,7 @@ - `linodecli/configuration/` owns config loading, interactive configuration, OAuth token flow, env token handling, and API URL overrides. - `linodecli/plugins/` contains hand-written commands outside the generated OpenAPI surface. If you add or change a plugin, read `linodecli/plugins/README.md` for the `call(args, context)` interface and third-party `PLUGIN_NAME` requirement. - If you touch `linodecli/baked/*.py`, read `linodecli/baked/AGENTS.md` first. Key constraint: baked model state must stay pickle-safe. +- The table and ASCII table output overrides for `object-storage keys-create` disable truncation so the one-time secret key is displayed in full. ## Tests - `make test` only runs unit tests; it is an alias for `make test-unit`. diff --git a/linodecli/overrides.py b/linodecli/overrides.py index dff913877..7f390c0ba 100644 --- a/linodecli/overrides.py +++ b/linodecli/overrides.py @@ -47,6 +47,30 @@ def inner(func): return inner +@output_override("object-storage", "keys-create", OutputMode.table) +def handle_object_storage_keys_create( + operation, output_handler, json_data +) -> bool: + # pylint: disable=unused-argument + """ + Preserve the complete secret key, which is only returned on creation. + """ + output_handler.disable_truncation = True + return True + + +@output_override("object-storage", "keys-create", OutputMode.ascii_table) +def handle_object_storage_keys_create_ascii( + operation, output_handler, json_data +) -> bool: + # pylint: disable=unused-argument + """ + Preserve the complete secret key in ASCII table output as well. + """ + output_handler.disable_truncation = True + return True + + @output_override("databases", "mysql-config-view", OutputMode.table) def handle_databases_mysql_config_view( operation, output_handler, json_data diff --git a/tests/unit/test_overrides.py b/tests/unit/test_overrides.py index f5533b6b6..f2514286e 100644 --- a/tests/unit/test_overrides.py +++ b/tests/unit/test_overrides.py @@ -2,6 +2,9 @@ import io from unittest.mock import patch +import pytest +from rich.console import Console + from linodecli import OutputMode from linodecli.overrides import OUTPUT_OVERRIDES @@ -11,6 +14,39 @@ class TestOverrides: Unit tests for linodecli.overrides """ + @pytest.mark.parametrize("mode", [OutputMode.table, OutputMode.ascii_table]) + @pytest.mark.parametrize("action", ["keys-create", "keys-list"]) + def test_object_storage_key_truncation( + self, mock_cli, list_operation_for_output_tests, action, mode + ): + operation = list_operation_for_output_tests + operation.command = "object-storage" + operation.action = action + mock_cli.output_handler.mode = mode + mock_cli.output_handler.columns = "cool" + secret = "UNIQUESECRETKEY0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + output = io.StringIO() + console = Console(file=output, width=28, color_system=None) + + with patch( + "linodecli.output.output_handler.rprint", + lambda *args, **kwargs: console.print(*args), + ): + operation.process_response_json( + {"cool": secret}, mock_cli.output_handler + ) + + # Rich folds long values across rows; reconstruct the cell content. + rendered = "".join( + line.strip("│| ") for line in output.getvalue().splitlines() + ) + if action == "keys-create": + assert secret in rendered + assert "…" not in rendered + else: + assert secret not in rendered + assert "…" in rendered + def test_domains_zone_file( self, mock_cli, list_operation_for_overrides_test ):