From 8c44a69646ba46308a2a9c6d8a3182cb43c375bf Mon Sep 17 00:00:00 2001 From: Emmanuel Quevillon Date: Mon, 15 Sep 2025 11:58:06 +0200 Subject: [PATCH 1/3] :fire: refactor: Merge --plain and --msword option into new one --style [msword,plain]. This allow to easily add new sytle if needed. It solve also option precedence when using both option on the command line. See PR message. --- oks_cli/cache.py | 12 +++++++----- oks_cli/cluster.py | 16 +++++++++------- oks_cli/project.py | 16 +++++++++------- tests/test_cache.py | 26 +++++++++++++++++++++++++- tests/test_cluster.py | 38 ++++++++++++++++++++++++++++++++++++++ tests/test_project.py | 38 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 126 insertions(+), 20 deletions(-) diff --git a/oks_cli/cache.py b/oks_cli/cache.py index 6968c37..a5c6fb9 100644 --- a/oks_cli/cache.py +++ b/oks_cli/cache.py @@ -3,6 +3,7 @@ ctx_update, login_profile, profile_completer, cluster_completer, project_completer, print_table import prettytable +from prettytable import TableStyle # DEFINE THE CACHE COMMAND GROUP @click.group(help="Cache related commands.") @@ -24,11 +25,12 @@ def delete_cache(force): @cache.command('kubeconfigs', help="List cached kubeconfigs") @click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer) @click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer) -@click.option('--plain', is_flag=True, help="Plain table format") -@click.option('--msword', is_flag=True, help="Microsoft Word table format") +@click.option('--style', type=click.Choice(["msword", "plain"]), help="Optional table style format output") +@click.option('--plain', is_flag=True, help="Plain table format", deprecated="Use --style instead") +@click.option('--msword', is_flag=True, help="Microsoft Word table format", deprecated="Use --style instead") @click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer) @click.pass_context -def list_kubeconfigs(ctx, project_name, cluster_name, plain, msword, profile): +def list_kubeconfigs(ctx, project_name, cluster_name, style, plain, msword, profile): """Display cached kubeconfigs with expiration dates in table format.""" project_name, cluster_name, profile = ctx_update(ctx, project_name, cluster_name, profile) login_profile(profile) @@ -57,8 +59,8 @@ def list_kubeconfigs(ctx, project_name, cluster_name, plain, msword, profile): style = None if plain: - style = prettytable.PLAIN_COLUMNS + style = TableStyle.PLAIN_COLUMNS if msword: - style = prettytable.MSWORD_FRIENDLY + style = TableStyle.MSWORD_FRIENDLY print_table(data, fields, style=style) diff --git a/oks_cli/cluster.py b/oks_cli/cluster.py index 6bdc4d7..24d7e2d 100644 --- a/oks_cli/cluster.py +++ b/oks_cli/cluster.py @@ -11,6 +11,7 @@ import human_readable import pathlib import prettytable +from prettytable import TableStyle import logging import yaml @@ -81,13 +82,14 @@ def cluster_logout(ctx, profile): @click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer) @click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer) @click.option('--deleted', '-x', is_flag=True, help="List deleted clusters") # x pour "deleted" / "removed" -@click.option('--plain', is_flag=True, help="Plain table format") -@click.option('--msword', is_flag=True, help="Microsoft Word table format") +@click.option('--style', type=click.Choice(["msword", "plain"]), help="Optional table style format output") +@click.option('--plain', is_flag=True, help="Plain table format", deprecated="Use --style instead") +@click.option('--msword', is_flag=True, help="Microsoft Word table format", deprecated="Use --style instead") @click.option('--watch', '-w', is_flag=True, help="Watch the changes") @click.option('--output', '-o', type=click.Choice(["json", "yaml", "wide"]), help="Specify output format") @click.option('--profile', help="Configuration profile to use") @click.pass_context -def cluster_list(ctx, project_name, cluster_name, deleted, plain, msword, watch, output, profile): +def cluster_list(ctx, project_name, cluster_name, deleted, style, plain, msword, watch, output, profile): """Display clusters with optional filtering and real-time monitoring.""" project_name, cluster_name, profile = ctx_update(ctx, project_name, cluster_name, profile) login_profile(profile) @@ -120,11 +122,11 @@ def cluster_list(ctx, project_name, cluster_name, deleted, plain, msword, watch, table._min_width = {"CREATED": 13, "UPDATED": 13, "STATUS": 10} - if plain or watch: - table.set_style(prettytable.PLAIN_COLUMNS) + if style == 'plain' or plain or watch: + style = TableStyle.PLAIN_COLUMNS - if msword: - table.set_style(prettytable.MSWORD_FRIENDLY) + if style == 'msword' or msword: + style = TableStyle.MSWORD_FRIENDLY def format_row(cluster): status = cluster['statuses']['status'] diff --git a/oks_cli/project.py b/oks_cli/project.py index 281c134..55bcb65 100644 --- a/oks_cli/project.py +++ b/oks_cli/project.py @@ -4,6 +4,7 @@ import dateutil.parser import human_readable import prettytable +from prettytable import TableStyle import os from .utils import do_request, print_output, print_table, find_project_id_by_name, get_project_id, set_project_id, detect_and_parse_input, transform_tuple, ctx_update, set_cluster_id, get_template, get_project_name, format_changed_row, is_interesting_status, login_profile, profile_completer, project_completer @@ -60,14 +61,15 @@ def project_logout(ctx, profile): @project.command('list', help="List all projects") @click.option('--project-name', '-p', help="Name of project", type=click.STRING, shell_complete=project_completer) @click.option('--deleted', '-x', is_flag=True, help="List deleted projects") -@click.option('--plain', is_flag=True, help="Plain table format") -@click.option('--msword', is_flag=True, help="Microsoft Word table format") +@click.option('--style', type=click.Choice(["msword", "plain"]), help="Optional table style format output") +@click.option('--plain', is_flag=True, help="Plain table format", deprecated="Use --style instead") +@click.option('--msword', is_flag=True, help="Microsoft Word table format", deprecated="Use --style instead") @click.option('--uuid', is_flag=True, help="Show UUID") @click.option('--watch', '-w', is_flag=True, help="Watch the changes") @click.option('--output', '-o', type=click.Choice(["json", "yaml"]), help="Specify output format, by default is json") @click.option('--profile', help="Configuration profile to use") @click.pass_context -def project_list(ctx, project_name, deleted, plain, msword, uuid, watch, output, profile): +def project_list(ctx, project_name, deleted, style, plain, msword, uuid, watch, output, profile): """List projects with filtering, formatting, and live watch capabilities.""" project_name, _, profile = ctx_update(ctx, project_name, None, profile) login_profile(profile) @@ -95,11 +97,11 @@ def project_list(ctx, project_name, deleted, plain, msword, uuid, watch, output, table._min_width = {"CREATED": 13, "UPDATED": 13, "STATUS": 10} - if plain or watch: - table.set_style(prettytable.PLAIN_COLUMNS) + if style == 'plain' or plain or watch: + table.set_style(TableStyle.PLAIN_COLUMNS) - if msword: - table.set_style(prettytable.MSWORD_FRIENDLY) + if style == 'mswoord' or msword: + table.set_style(TableStyle.MSWORD_FRIENDLY) def format_row(project): status = project.get('status') diff --git a/tests/test_cache.py b/tests/test_cache.py index 459284e..0c4ae1c 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -17,4 +17,28 @@ def test_cache_kubeconfigs_command(mock_request, add_default_profile): runner = CliRunner() result = runner.invoke(cli, ["cache", "kubeconfigs", "-p", "test", "-c", "test"]) assert result.exit_code == 0 - assert '| user | group | expiration date |' in result.output \ No newline at end of file + assert '| user | group | expiration date |' in result.output + +@patch("oks_cli.utils.requests.request") +def test_cache_kubeconfigs_plain_deprecation(mock_request, add_default_profile): + mock_request.side_effect = [ + MagicMock(status_code=200, headers = {}, json=lambda: {"ResponseContext": {}, "Projects": [{"id": "12345"}]}), + MagicMock(status_code=200, headers = {}, json=lambda: {"ResponseContext": {}, "Clusters": [{"id": "12345"}]}) + ] + + runner = CliRunner() + result = runner.invoke(cli, ["cache", "kubeconfigs", "-p", "test", "-c", "test", "--plain"]) + assert result.exit_code == 0 + assert "DeprecationWarning: The options 'plain' is deprecated. use --style instead" + +@patch("oks_cli.utils.requests.request") +def test_cache_kubeconfigs_msword_deprecation(mock_request, add_default_profile): + mock_request.side_effect = [ + MagicMock(status_code=200, headers = {}, json=lambda: {"ResponseContext": {}, "Projects": [{"id": "12345"}]}), + MagicMock(status_code=200, headers = {}, json=lambda: {"ResponseContext": {}, "Clusters": [{"id": "12345"}]}) + ] + + runner = CliRunner() + result = runner.invoke(cli, ["cache", "kubeconfigs", "-p", "test", "-c", "test", "--msword"]) + assert result.exit_code == 0 + assert "DeprecationWarning: The options 'msword' is deprecated. use --style instead" \ No newline at end of file diff --git a/tests/test_cluster.py b/tests/test_cluster.py index f0d4ebe..efe6afb 100644 --- a/tests/test_cluster.py +++ b/tests/test_cluster.py @@ -18,6 +18,44 @@ def test_cluster_list_command(mock_request, add_default_profile): assert result.exit_code == 0 assert '"name": "test"' in result.output +# Test the "cluster list --plain" command: verifies deprecation warning is thrown +@patch("oks_cli.utils.requests.request") +def test_cluster_list_command_plain_deprecation(mock_request, add_default_profile): + mock_request.side_effect = [ + MagicMock(status_code=200, headers = {}, json=lambda: {"ResponseContext": {}, "Projects": [{"id": "12345"}]}), + MagicMock(status_code=200, headers = {}, json=lambda: { + "ResponseContext": {}, + "Clusters": [ + {"id": "12345", "name": "test", + "statuses": {"status": "ready", + "created_at": "2019-08-24T14:15:22Z", + "updated_at": "2019-08-24T14:15:22Z"}}]}), + ] + + runner = CliRunner() + result = runner.invoke(cli, ["cluster", "list", "-p", "test", "-c", "test", '--plain']) + assert result.exit_code == 0 + assert "DeprecationWarning: The options 'plain' is deprecated. use --style instead" + +# Test the "cluster list --msword" command: verifies deprecation warning is thrown +@patch("oks_cli.utils.requests.request") +def test_cluster_list_command_msword_deprecation(mock_request, add_default_profile): + mock_request.side_effect = [ + MagicMock(status_code=200, headers = {}, json=lambda: {"ResponseContext": {}, "Projects": [{"id": "12345"}]}), + MagicMock(status_code=200, headers = {}, json=lambda: { + "ResponseContext": {}, + "Clusters": [ + {"id": "12345", "name": "test", + "statuses": {"status": "ready", + "created_at": "2019-08-24T14:15:22Z", + "updated_at": "2019-08-24T14:15:22Z"}}]}), + ] + + runner = CliRunner() + result = runner.invoke(cli, ["cluster", "list", "-p", "test", "-c", "test", '--msword']) + assert result.exit_code == 0 + assert "DeprecationWarning: The options 'msword' is deprecated. use --style instead" + # Test the "cluster list" command with all arguments: verifies that advanced filters @patch("oks_cli.utils.requests.request") def test_cluster_list_all_args(mock_request, add_default_profile): diff --git a/tests/test_project.py b/tests/test_project.py index 0d1ce56..4420e78 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -5,6 +5,44 @@ import json # START PROJECT LIST COMMAND +# Test the "project list --plain" command: verifies deprecation warning +@patch("oks_cli.utils.requests.request") +def test_project_list_plain_deprecation_command(mock_request, add_default_profile): + mock_request.side_effect = [ + MagicMock(status_code=200, headers = {}, json=lambda: { + "ResponseContext": {}, + "Projects": [ + {"id": "12345", "name": "test", + "created_at": "2019-08-24T14:15:22Z", + "updated_at": "2019-08-24T14:15:22Z", + "status": "ready", + "region":"eu-west-2"}]}) + ] + + runner = CliRunner() + result = runner.invoke(cli, ["project", "list", '--plain']) + assert result.exit_code == 0 + assert "DeprecationWarning: The options 'plain' is deprecated. use --style instead" + +# Test the "project list --msword" command: verifies deprecation warning +@patch("oks_cli.utils.requests.request") +def test_project_list_msword_deprecation_command(mock_request, add_default_profile): + mock_request.side_effect = [ + MagicMock(status_code=200, headers = {}, json=lambda: { + "ResponseContext": {}, + "Projects": [ + {"id": "12345", "name": "test", + "created_at": "2019-08-24T14:15:22Z", + "updated_at": "2019-08-24T14:15:22Z", + "status": "ready", + "region":"eu-west-2"}]}) + ] + + runner = CliRunner() + result = runner.invoke(cli, ["project", "list", '--msword']) + assert result.exit_code == 0 + assert "DeprecationWarning: The options 'msword' is deprecated. use --style instead" + # Test the "project list" command: verifies listing 1 projects with json @patch("oks_cli.utils.requests.request") def test_project_list_command_json(mock_request, add_default_profile): From fbd9fd37cdc477abeb7123ce564436dab05bf660 Mon Sep 17 00:00:00 2001 From: Emmanuel Quevillon Date: Mon, 15 Sep 2025 12:06:29 +0200 Subject: [PATCH 2/3] Fix setting table style with cluster list command --- oks_cli/cluster.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oks_cli/cluster.py b/oks_cli/cluster.py index 24d7e2d..910b71b 100644 --- a/oks_cli/cluster.py +++ b/oks_cli/cluster.py @@ -123,10 +123,10 @@ def cluster_list(ctx, project_name, cluster_name, deleted, style, plain, msword, table._min_width = {"CREATED": 13, "UPDATED": 13, "STATUS": 10} if style == 'plain' or plain or watch: - style = TableStyle.PLAIN_COLUMNS + table.set_style(TableStyle.PLAIN_COLUMNS) if style == 'msword' or msword: - style = TableStyle.MSWORD_FRIENDLY + table.set_style(TableStyle.MSWORD_FRIENDLY) def format_row(cluster): status = cluster['statuses']['status'] From 880b6315ff854a3fac52bd00d6bd6270bb002e11 Mon Sep 17 00:00:00 2001 From: Emmanuel Quevillon Date: Mon, 15 Sep 2025 15:54:23 +0200 Subject: [PATCH 3/3] :art: refactor: Add multiple=False to --style option arguments --- oks_cli/cache.py | 8 ++++---- oks_cli/cluster.py | 2 +- oks_cli/project.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/oks_cli/cache.py b/oks_cli/cache.py index a5c6fb9..5765109 100644 --- a/oks_cli/cache.py +++ b/oks_cli/cache.py @@ -25,7 +25,7 @@ def delete_cache(force): @cache.command('kubeconfigs', help="List cached kubeconfigs") @click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer) @click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer) -@click.option('--style', type=click.Choice(["msword", "plain"]), help="Optional table style format output") +@click.option('--style', multiple=False, type=click.Choice(["msword", "plain"]), help="Optional table style format output") @click.option('--plain', is_flag=True, help="Plain table format", deprecated="Use --style instead") @click.option('--msword', is_flag=True, help="Microsoft Word table format", deprecated="Use --style instead") @click.option('--profile', help="Configuration profile to use", shell_complete=profile_completer) @@ -57,10 +57,10 @@ def list_kubeconfigs(ctx, project_name, cluster_name, style, plain, msword, prof row = user, group, exp data.append({"user": user, "group": group, "expires_at": exp}) - style = None - if plain: + if style == 'plain' or plain: style = TableStyle.PLAIN_COLUMNS - if msword: + + if style == 'msword' or msword: style = TableStyle.MSWORD_FRIENDLY print_table(data, fields, style=style) diff --git a/oks_cli/cluster.py b/oks_cli/cluster.py index 910b71b..a80572d 100644 --- a/oks_cli/cluster.py +++ b/oks_cli/cluster.py @@ -82,7 +82,7 @@ def cluster_logout(ctx, profile): @click.option('--project-name', '-p', required=False, help="Project Name", shell_complete=project_completer) @click.option('--cluster-name', '--name', '-c', required=False, help="Cluster Name", shell_complete=cluster_completer) @click.option('--deleted', '-x', is_flag=True, help="List deleted clusters") # x pour "deleted" / "removed" -@click.option('--style', type=click.Choice(["msword", "plain"]), help="Optional table style format output") +@click.option('--style', multiple=False, type=click.Choice(["msword", "plain"]), help="Optional table style format output") @click.option('--plain', is_flag=True, help="Plain table format", deprecated="Use --style instead") @click.option('--msword', is_flag=True, help="Microsoft Word table format", deprecated="Use --style instead") @click.option('--watch', '-w', is_flag=True, help="Watch the changes") diff --git a/oks_cli/project.py b/oks_cli/project.py index 55bcb65..b5aee18 100644 --- a/oks_cli/project.py +++ b/oks_cli/project.py @@ -61,7 +61,7 @@ def project_logout(ctx, profile): @project.command('list', help="List all projects") @click.option('--project-name', '-p', help="Name of project", type=click.STRING, shell_complete=project_completer) @click.option('--deleted', '-x', is_flag=True, help="List deleted projects") -@click.option('--style', type=click.Choice(["msword", "plain"]), help="Optional table style format output") +@click.option('--style', multiple=False, type=click.Choice(["msword", "plain"]), help="Optional table style format output") @click.option('--plain', is_flag=True, help="Plain table format", deprecated="Use --style instead") @click.option('--msword', is_flag=True, help="Microsoft Word table format", deprecated="Use --style instead") @click.option('--uuid', is_flag=True, help="Show UUID") @@ -100,7 +100,7 @@ def project_list(ctx, project_name, deleted, style, plain, msword, uuid, watch, if style == 'plain' or plain or watch: table.set_style(TableStyle.PLAIN_COLUMNS) - if style == 'mswoord' or msword: + if style == 'msword' or msword: table.set_style(TableStyle.MSWORD_FRIENDLY) def format_row(project):