From e60c8e3233917c4d2747eb9ca60169e5f6acca4c Mon Sep 17 00:00:00 2001 From: Alex Castillo Date: Sun, 8 Nov 2026 21:23:31 -0400 Subject: [PATCH 1/2] fix: preserve object storage secret keys in table output --- AGENTS.md | 2 ++ linodecli/overrides.py | 12 ++++++++++++ tests/unit/test_overrides.py | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index bdebb748c..5de2ee7c2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,6 +26,8 @@ - `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 output override for `object-storage keys-create` disables 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`. - Use `make test-unit` for normal unit verification. It sets `LINODE_CLI_TEST_MODE=1` and `XDG_CONFIG_HOME=/tmp/linode/.config` so imports do not trigger interactive config. diff --git a/linodecli/overrides.py b/linodecli/overrides.py index dff913877..26d568093 100644 --- a/linodecli/overrides.py +++ b/linodecli/overrides.py @@ -47,6 +47,18 @@ 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("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..75f5532ef 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,37 @@ class TestOverrides: Unit tests for linodecli.overrides """ + @pytest.mark.parametrize("action", ["keys-create", "keys-list"]) + def test_object_storage_key_truncation( + self, mock_cli, list_operation_for_output_tests, action + ): + operation = list_operation_for_output_tests + operation.command = "object-storage" + operation.action = action + 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 ): From 501f2e1e7caedc4b96c54e7a1af4e82a91d7aa9b Mon Sep 17 00:00:00 2001 From: Alex Castillo Date: Sun, 8 Nov 2026 21:29:33 -0400 Subject: [PATCH 2/2] fix: preserve object storage secrets in ASCII tables --- AGENTS.md | 3 +-- linodecli/overrides.py | 12 ++++++++++++ tests/unit/test_overrides.py | 6 ++++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5de2ee7c2..cd66286e9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,8 +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 output override for `object-storage keys-create` disables truncation so the one-time secret key is displayed in full. +- 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 26d568093..7f390c0ba 100644 --- a/linodecli/overrides.py +++ b/linodecli/overrides.py @@ -59,6 +59,18 @@ def handle_object_storage_keys_create( 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 75f5532ef..f2514286e 100644 --- a/tests/unit/test_overrides.py +++ b/tests/unit/test_overrides.py @@ -14,13 +14,15 @@ 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 + 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() @@ -36,7 +38,7 @@ def test_object_storage_key_truncation( # Rich folds long values across rows; reconstruct the cell content. rendered = "".join( - line.strip("│ ") for line in output.getvalue().splitlines() + line.strip("│| ") for line in output.getvalue().splitlines() ) if action == "keys-create": assert secret in rendered