From 57f09aeb5e142e2de7ad549498a8254cff8759bf Mon Sep 17 00:00:00 2001 From: Paul Wallrabe Date: Tue, 17 Mar 2026 18:31:16 +0100 Subject: [PATCH 01/13] fix: rename --insecure-tls-config to --insecure-tls with deprecation Resolves #318 Co-Authored-By: Claude Opus 4.6 --- .../configuration/authentication.md | 14 +++--- .../guides/setup-distributed-mode.md | 6 +-- .../jumpstarter_cli_admin/create.py | 18 +++---- .../jumpstarter_cli_admin/create_test.py | 12 ++--- .../jumpstarter_cli_admin/import_res.py | 18 +++---- .../jumpstarter_cli_admin/import_res_test.py | 10 ++-- .../jumpstarter_cli_common/opt.py | 48 +++++++++++++++---- .../jumpstarter_cli_common/opt_test.py | 47 +++++++++++++++++- .../jumpstarter-cli/jumpstarter_cli/login.py | 12 ++--- specs/003-fix-tls-flag-naming-impl/tasks.md | 32 +++++++++++++ 10 files changed, 160 insertions(+), 57 deletions(-) create mode 100644 specs/003-fix-tls-flag-naming-impl/tasks.md diff --git a/python/docs/source/getting-started/configuration/authentication.md b/python/docs/source/getting-started/configuration/authentication.md index f37886b46..5afc17f02 100644 --- a/python/docs/source/getting-started/configuration/authentication.md +++ b/python/docs/source/getting-started/configuration/authentication.md @@ -52,14 +52,14 @@ prefixed with "keycloak:" (e.g., keycloak:example-user). prefix usernames with `keycloak:` as configured in the claim mappings: ```console -$ jmp admin create client test-client --insecure-tls-config --oidc-username keycloak:developer-1 +$ jmp admin create client test-client --insecure-tls --oidc-username keycloak:developer-1 ``` 4. Instruct users to log in with: ```console $ jmp login --client \ - --insecure-tls-config \ + --insecure-tls \ --endpoint \ --namespace --name \ --issuer https:///realms/ @@ -69,7 +69,7 @@ For non-interactive login, add username and password: ```console $ jmp login --client [other parameters] \ - --insecure-tls-config \ + --insecure-tls \ --username \ --password ``` @@ -84,7 +84,7 @@ For exporters, use similar login command but with the `--exporter` flag: ```console $ jmp login --exporter \ - --insecure-tls-config \ + --insecure-tls \ --endpoint \ --namespace --name \ --issuer https:///realms/ @@ -197,7 +197,7 @@ spec: ```console $ jmp admin create exporter test-exporter --label foo=bar \ - --insecure-tls-config \ + --insecure-tls \ --oidc-username dex:system:serviceaccount:default:test-service-account ``` @@ -207,7 +207,7 @@ For clients: ```console $ jmp login --client \ - --insecure-tls-config \ + --insecure-tls \ --endpoint \ --namespace --name \ --issuer https://dex.dex.svc.cluster.local:5556 \ @@ -219,7 +219,7 @@ For exporters: ```console $ jmp login --exporter \ - --insecure-tls-config \ + --insecure-tls \ --endpoint \ --namespace --name \ --issuer https://dex.dex.svc.cluster.local:5556 \ diff --git a/python/docs/source/getting-started/guides/setup-distributed-mode.md b/python/docs/source/getting-started/guides/setup-distributed-mode.md index 157ce72e3..a9073447d 100644 --- a/python/docs/source/getting-started/guides/setup-distributed-mode.md +++ b/python/docs/source/getting-started/guides/setup-distributed-mode.md @@ -7,7 +7,7 @@ controller service, configuring drivers, and running the exporter. The jumpstarter-controller endpoints are secured by TLS. However, in release 0.7.x, the certificates are self-signed and rotated on every restart. This means the client will not be able to verify the server certificate. To bypass this, you should use the -`--insecure-tls-config` flag when creating clients and exporters. This issue will be +`--insecure-tls` flag when creating clients and exporters. This issue will be resolved in the next release. See [issue #72](https://github.com/jumpstarter-dev/jumpstarter/issues/72) for more details. Alternatively, you can configure the ingress/route in reencrypt mode with your own key and certificate. @@ -40,7 +40,7 @@ Run this command to create an exporter named `example-distributed` and save the configuration locally: ```console -$ jmp admin create exporter example-distributed --label foo=bar --save --insecure-tls-config +$ jmp admin create exporter example-distributed --label foo=bar --save --insecure-tls ``` After creating the exporter, find the new configuration file at @@ -88,7 +88,7 @@ development purposes, and saves the configuration locally in `${HOME}/.config/jumpstarter/clients/`: ```console -$ jmp admin create client hello --save --unsafe --insecure-tls-config +$ jmp admin create client hello --save --unsafe --insecure-tls ``` ### Spawn an Exporter Shell diff --git a/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py b/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py index 295c527ee..d01a53833 100644 --- a/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py +++ b/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py @@ -8,7 +8,7 @@ OutputType, confirm_insecure_tls, opt_context, - opt_insecure_tls_config, + opt_insecure_tls, opt_kubeconfig, opt_labels, opt_namespace, @@ -69,7 +69,7 @@ def create(): @opt_labels() @opt_kubeconfig @opt_context -@opt_insecure_tls_config +@opt_insecure_tls @opt_oidc_username @opt_nointeractive @opt_output_all @@ -78,7 +78,7 @@ async def create_client( name: Optional[str], kubeconfig: Optional[str], context: Optional[str], - insecure_tls_config: bool, + insecure_tls: bool, namespace: str, labels: dict[str, str], save: bool, @@ -91,7 +91,7 @@ async def create_client( ): """Create a client object in the Kubernetes cluster""" try: - confirm_insecure_tls(insecure_tls_config, nointeractive) + confirm_insecure_tls(insecure_tls, nointeractive) async with ClientsV1Alpha1Api(namespace, kubeconfig, context) as api: if output is None: # Only print status if is not JSON/YAML @@ -111,7 +111,7 @@ async def create_client( allow_drivers = allow.split(",") if allow is not None and len(allow) > 0 else [] client_config.drivers.unsafe = unsafe client_config.drivers.allow = allow_drivers - client_config.tls.insecure = insecure_tls_config + client_config.tls.insecure = insecure_tls ClientConfigV1Alpha1.save(client_config, out) # If this is the only client config, set it as default if out is None and len(ClientConfigV1Alpha1.list().items) == 1: @@ -146,7 +146,7 @@ async def create_client( @opt_labels(required=True) @opt_kubeconfig @opt_context -@opt_insecure_tls_config +@opt_insecure_tls @opt_oidc_username @opt_nointeractive @opt_output_all @@ -155,7 +155,7 @@ async def create_exporter( name: Optional[str], kubeconfig: Optional[str], context: Optional[str], - insecure_tls_config: bool, + insecure_tls: bool, namespace: str, labels: dict[str, str], save: bool, @@ -166,7 +166,7 @@ async def create_exporter( ): """Create an exporter object in the Kubernetes cluster""" try: - confirm_insecure_tls(insecure_tls_config, nointeractive) + confirm_insecure_tls(insecure_tls, nointeractive) async with ExportersV1Alpha1Api(namespace, kubeconfig, context) as api: if output is None: click.echo(f"Creating exporter '{name}' in namespace '{namespace}'") @@ -176,7 +176,7 @@ async def create_exporter( if output is None: click.echo("Fetching exporter credentials from cluster") exporter_config = await api.get_exporter_config(name) - exporter_config.tls.insecure = insecure_tls_config + exporter_config.tls.insecure = insecure_tls ExporterConfigV1Alpha1.save(exporter_config, out) if output is None: click.echo(f"Exporter configuration successfully saved to {exporter_config.path}") diff --git a/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py b/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py index 5d4c2d9c8..e6703aa39 100644 --- a/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py +++ b/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py @@ -118,7 +118,7 @@ def test_create_client( mock_get_client_config.return_value = INSECURE_TLS_CLIENT_CONFIG # Save with prompts accept insecure = Y, save = Y, unsafe = Y - result = runner.invoke(create, ["client", "--insecure-tls-config", CLIENT_NAME], input="Y\nY\nY\n") + result = runner.invoke(create, ["client", "--insecure-tls", CLIENT_NAME], input="Y\nY\nY\n") assert result.exit_code == 0 assert "Client configuration successfully saved" in result.output mock_save_client.assert_called_once_with(INSECURE_TLS_CLIENT_CONFIG, None) @@ -126,7 +126,7 @@ def test_create_client( # Save no interactive and insecure tls result = runner.invoke( - create, ["client", "--insecure-tls-config", "--unsafe", "--save", "--nointeractive", CLIENT_NAME] + create, ["client", "--insecure-tls", "--unsafe", "--save", "--nointeractive", CLIENT_NAME] ) assert result.exit_code == 0 assert "Client configuration successfully saved" in result.output @@ -137,7 +137,7 @@ def test_create_client( mock_get_client_config.return_value = INSECURE_TLS_CLIENT_CONFIG # Save with prompts accept insecure = N - result = runner.invoke(create, ["client", "--insecure-tls-config", CLIENT_NAME], input="n\n") + result = runner.invoke(create, ["client", "--insecure-tls", CLIENT_NAME], input="n\n") assert result.exit_code == 1 assert "Aborted" in result.output @@ -295,7 +295,7 @@ def test_create_exporter( _get_exporter_config_mock.return_value = INSECURE_TLS_EXPORTER_CONFIG # Save with prompts accept insecure = Y, save = Y result = runner.invoke( - create, ["exporter", "--insecure-tls-config", EXPORTER_NAME, "--label", "foo=bar"], input="Y\nY\n" + create, ["exporter", "--insecure-tls", EXPORTER_NAME, "--label", "foo=bar"], input="Y\nY\n" ) assert result.exit_code == 0 assert "Exporter configuration successfully saved" in result.output @@ -305,7 +305,7 @@ def test_create_exporter( _get_exporter_config_mock.return_value = INSECURE_TLS_EXPORTER_CONFIG # Save with prompts accept no interactive result = runner.invoke( - create, ["exporter", "--insecure-tls-config", "--nointeractive", "--save", EXPORTER_NAME, "--label", "foo=bar"] + create, ["exporter", "--insecure-tls", "--nointeractive", "--save", EXPORTER_NAME, "--label", "foo=bar"] ) assert result.exit_code == 0 assert "Exporter configuration successfully saved" in result.output @@ -316,7 +316,7 @@ def test_create_exporter( _get_exporter_config_mock.return_value = INSECURE_TLS_EXPORTER_CONFIG # Save with prompts accept insecure = N result = runner.invoke( - create, ["exporter", "--insecure-tls-config", EXPORTER_NAME, "--label", "foo=bar"], input="n\n" + create, ["exporter", "--insecure-tls", EXPORTER_NAME, "--label", "foo=bar"], input="n\n" ) assert result.exit_code == 1 assert "Aborted" in result.output diff --git a/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res.py b/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res.py index e4bb656f1..2faf0b684 100644 --- a/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res.py +++ b/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res.py @@ -6,7 +6,7 @@ PathOutputType, confirm_insecure_tls, opt_context, - opt_insecure_tls_config, + opt_insecure_tls, opt_kubeconfig, opt_namespace, opt_nointeractive, @@ -48,7 +48,7 @@ def import_res(): @opt_namespace @opt_kubeconfig @opt_context -@opt_insecure_tls_config +@opt_insecure_tls @opt_output_path_only @opt_nointeractive @blocking @@ -57,7 +57,7 @@ async def import_client( namespace: str, kubeconfig: Optional[str], context: Optional[str], - insecure_tls_config: bool, + insecure_tls: bool, allow: Optional[str], unsafe: bool, out: Optional[str], @@ -69,7 +69,7 @@ async def import_client( if out is None and ClientConfigV1Alpha1.exists(name): raise click.ClickException(f"A client with the name '{name}' already exists") try: - confirm_insecure_tls(insecure_tls_config, nointeractive) + confirm_insecure_tls(insecure_tls, nointeractive) async with ClientsV1Alpha1Api(namespace, kubeconfig, context) as api: if unsafe is False and allow is None and nointeractive is False: unsafe = click.confirm("Allow unsafe driver client imports?") @@ -81,7 +81,7 @@ async def import_client( click.echo("Fetching client credentials from cluster") allow_drivers = allow.split(",") if allow is not None and len(allow) > 0 else [] client_config = await api.get_client_config(name, allow=allow_drivers, unsafe=unsafe) - client_config.tls.insecure = insecure_tls_config + client_config.tls.insecure = insecure_tls config_path = ClientConfigV1Alpha1.save(client_config, out) # If this is the only client config, set it as default if out is None and len(ClientConfigV1Alpha1.list().items) == 1: @@ -108,7 +108,7 @@ async def import_client( @opt_namespace @opt_kubeconfig @opt_context -@opt_insecure_tls_config +@opt_insecure_tls @opt_output_path_only @opt_nointeractive @blocking @@ -118,7 +118,7 @@ async def import_exporter( out: Optional[str], kubeconfig: Optional[str], context: Optional[str], - insecure_tls_config: bool, + insecure_tls: bool, output: PathOutputType, nointeractive: bool, ): @@ -130,12 +130,12 @@ async def import_exporter( else: raise click.ClickException(f'An exporter with the name "{name}" already exists') try: - confirm_insecure_tls(insecure_tls_config, nointeractive) + confirm_insecure_tls(insecure_tls, nointeractive) async with ExportersV1Alpha1Api(namespace, kubeconfig, context) as api: if output is None: click.echo("Fetching exporter credentials from cluster") exporter_config = await api.get_exporter_config(name) - exporter_config.tls.insecure = insecure_tls_config + exporter_config.tls.insecure = insecure_tls config_path = ExporterConfigV1Alpha1.save(exporter_config, out) if output is None: click.echo(f"Exporter configuration successfully saved to {config_path}") diff --git a/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py b/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py index e375c1a01..0bac2f40d 100644 --- a/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py +++ b/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py @@ -71,21 +71,21 @@ def test_import_client(_load_kube_config_mock, get_client_config_mock: AsyncMock get_client_config_mock.return_value = INSECURE_TLS_CLIENT_CONFIG # Save with prompts accept insecure = Y - result = runner.invoke(import_res, ["client", CLIENT_NAME, "--insecure-tls-config"], input="Y\nY\n") + result = runner.invoke(import_res, ["client", CLIENT_NAME, "--insecure-tls"], input="Y\nY\n") assert result.exit_code == 0 assert "Client configuration successfully saved" in result.output save_client_config_mock.assert_called_once_with(INSECURE_TLS_CLIENT_CONFIG, None) save_client_config_mock.reset_mock() # Save with prompts no interactive prompts and insecure tls cert - result = runner.invoke(import_res, ["client", CLIENT_NAME, "--nointeractive", "--insecure-tls-config"]) + result = runner.invoke(import_res, ["client", CLIENT_NAME, "--nointeractive", "--insecure-tls"]) assert result.exit_code == 0 assert "Client configuration successfully saved" in result.output save_client_config_mock.assert_called_once_with(INSECURE_TLS_CLIENT_CONFIG, None) save_client_config_mock.reset_mock() # Save with prompts accept insecure = N - result = runner.invoke(import_res, ["client", CLIENT_NAME, "--insecure-tls-config"], input="n\n") + result = runner.invoke(import_res, ["client", CLIENT_NAME, "--insecure-tls"], input="n\n") assert result.exit_code == 1 assert "Aborted" in result.output save_client_config_mock.assert_not_called() @@ -168,14 +168,14 @@ def test_import_exporter(_load_kube_config_mock, _get_exporter_config_mock, save _get_exporter_config_mock.return_value = INSECURE_TLS_EXPORTER_CONFIG # Save with prompts accept insecure = Y - result = runner.invoke(import_res, ["exporter", EXPORTER_NAME, "--insecure-tls-config"], input="Y\n") + result = runner.invoke(import_res, ["exporter", EXPORTER_NAME, "--insecure-tls"], input="Y\n") assert result.exit_code == 0 assert "Exporter configuration successfully saved" in result.output save_exporter_config_mock.assert_called_once_with(INSECURE_TLS_EXPORTER_CONFIG, None) save_exporter_config_mock.reset_mock() # Save with prompts accept insecure = N - result = runner.invoke(import_res, ["exporter", EXPORTER_NAME, "--insecure-tls-config"], input="n\n") + result = runner.invoke(import_res, ["exporter", EXPORTER_NAME, "--insecure-tls"], input="n\n") assert result.exit_code == 1 assert "Aborted" in result.output save_exporter_config_mock.assert_not_called() diff --git a/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py b/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py index cdbeefc1e..360c9876e 100644 --- a/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py +++ b/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py @@ -88,26 +88,54 @@ def _opt_labels_callback(ctx, param, value): callback=_opt_labels_callback, ) -opt_insecure_tls_config = click.option( - "--insecure-tls-config", - "insecure_tls_config", - is_flag=True, - default=False, - help="Disable endpoint TLS verification. This is insecure and should only be used for testing purposes", +_DEPRECATED_TLS_FLAG_WARNING = ( + "WARNING: --insecure-tls-config is deprecated and will be removed in a future release. " + "Use --insecure-tls instead." ) -def confirm_insecure_tls(insecure_tls_config: bool, nointeractive: bool): - """Confirm if insecure TLS config is enabled and user wants to continue. +def _insecure_tls_deprecated_callback(ctx, param, value): + if value: + click.echo(_DEPRECATED_TLS_FLAG_WARNING, err=True) + ctx.params["insecure_tls"] = True + return value + + +def opt_insecure_tls(func): + func = click.option( + "--insecure-tls-config", + "insecure_tls_config_deprecated", + is_flag=True, + default=False, + hidden=True, + expose_value=False, + callback=_insecure_tls_deprecated_callback, + is_eager=True, + )(func) + func = click.option( + "--insecure-tls", + "insecure_tls", + is_flag=True, + default=False, + help="Disable endpoint TLS verification. This is insecure and should only be used for testing purposes", + )(func) + return func + + +opt_insecure_tls_config = opt_insecure_tls + + +def confirm_insecure_tls(insecure_tls: bool, nointeractive: bool): + """Confirm if insecure TLS is enabled and user wants to continue. Args: - insecure_tls_config (bool): Insecure TLS config flag requested by the user. + insecure_tls (bool): Insecure TLS flag requested by the user. nointeractive (bool): This flag is set to True if the command is run in non-interactive mode. Raises: click.Abort: Abort the command if user does not want to continue. """ - if nointeractive is False and insecure_tls_config: + if nointeractive is False and insecure_tls: if not click.confirm("Insecure TLS config is enabled. Are you sure you want to continue?"): click.echo("Aborting.") raise click.Abort() diff --git a/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt_test.py b/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt_test.py index 834a617e6..daf07fcab 100644 --- a/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt_test.py +++ b/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt_test.py @@ -1,8 +1,11 @@ -"""Tests for SourcePrefixFormatter in opt.py.""" +"""Tests for SourcePrefixFormatter and insecure TLS option in opt.py.""" import logging -from jumpstarter_cli_common.opt import SourcePrefixFormatter +import click +from click.testing import CliRunner + +from jumpstarter_cli_common.opt import SourcePrefixFormatter, opt_insecure_tls class TestSourcePrefixFormatter: @@ -76,3 +79,43 @@ def test_prefix_omitted_on_consecutive_same_source(self) -> None: ) formatted3 = formatter.format(record3) assert "[different.source]" in formatted3 + + +def _make_insecure_tls_command(): + @click.command() + @opt_insecure_tls + def cmd(insecure_tls: bool): + click.echo(f"insecure_tls={insecure_tls}") + + return cmd + + +class TestInsecureTlsOption: + def test_insecure_tls_flag_is_accepted(self) -> None: + runner = CliRunner() + cmd = _make_insecure_tls_command() + result = runner.invoke(cmd, ["--insecure-tls"]) + assert result.exit_code == 0 + assert "insecure_tls=True" in result.output + + def test_insecure_tls_flag_defaults_to_false(self) -> None: + runner = CliRunner() + cmd = _make_insecure_tls_command() + result = runner.invoke(cmd, []) + assert result.exit_code == 0 + assert "insecure_tls=False" in result.output + + def test_deprecated_insecure_tls_config_still_works(self) -> None: + runner = CliRunner() + cmd = _make_insecure_tls_command() + result = runner.invoke(cmd, ["--insecure-tls-config"]) + assert result.exit_code == 0 + assert "insecure_tls=True" in result.output + + def test_deprecated_insecure_tls_config_emits_warning(self) -> None: + runner = CliRunner() + cmd = _make_insecure_tls_command() + result = runner.invoke(cmd, ["--insecure-tls-config"]) + assert result.exit_code == 0 + assert "deprecated" in result.output.lower() + assert "--insecure-tls" in result.output diff --git a/python/packages/jumpstarter-cli/jumpstarter_cli/login.py b/python/packages/jumpstarter-cli/jumpstarter_cli/login.py index 63938ae44..ce82eb7f4 100644 --- a/python/packages/jumpstarter-cli/jumpstarter_cli/login.py +++ b/python/packages/jumpstarter-cli/jumpstarter_cli/login.py @@ -11,7 +11,7 @@ from jumpstarter_cli_common.oidc import Config, decode_jwt_issuer, opt_oidc from jumpstarter_cli_common.opt import ( confirm_insecure_tls, - opt_insecure_tls_config, + opt_insecure_tls, opt_nointeractive, ) @@ -159,7 +159,7 @@ def parse_login_argument(login_arg: str) -> tuple[str | None, str]: "--unsafe", is_flag=True, help="Should all driver client packages be allowed to load (UNSAFE!).", default=None ) # end client specific -@opt_insecure_tls_config +@opt_insecure_tls @click.option( "--insecure-login-tls", is_flag=True, @@ -191,7 +191,7 @@ async def login( # noqa: C901 callback_port: int | None, offline_access: bool, unsafe, - insecure_tls_config: bool, + insecure_tls: bool, insecure_login_tls: bool, insecure_login_http: bool, nointeractive: bool, @@ -212,7 +212,7 @@ async def login( # noqa: C901 - Default namespace """ - confirm_insecure_tls(insecure_tls_config, nointeractive) + confirm_insecure_tls(insecure_tls, nointeractive) if insecure_login_http and insecure_login_tls: raise click.UsageError("--insecure-login-http and --insecure-login-tls cannot be used together.") @@ -231,7 +231,7 @@ async def login( # noqa: C901 click.echo(f"Fetching configuration from {login_endpoint}...") auth_config = await fetch_auth_config( login_endpoint, - insecure_tls=insecure_login_tls or insecure_tls_config, + insecure_tls=insecure_login_tls or insecure_tls, use_http=insecure_login_http, ) @@ -305,7 +305,7 @@ async def login( # noqa: C901 ) # Build TLS config with CA bundle if available - tls_config = TLSConfigV1Alpha1(insecure=insecure_tls_config, ca=ca_bundle or "") + tls_config = TLSConfigV1Alpha1(insecure=insecure_tls, ca=ca_bundle or "") if kind.startswith("client"): config = ClientConfigV1Alpha1( diff --git a/specs/003-fix-tls-flag-naming-impl/tasks.md b/specs/003-fix-tls-flag-naming-impl/tasks.md new file mode 100644 index 000000000..832bc06ee --- /dev/null +++ b/specs/003-fix-tls-flag-naming-impl/tasks.md @@ -0,0 +1,32 @@ +# Tasks: Rename --insecure-tls-config to --insecure-tls + +## T001 [US1] Write failing test that --insecure-tls is accepted +- [x] Add test in opt_test.py that creates a Click command using the new option and invokes it with `--insecure-tls` +- [x] Verify the flag value is passed correctly to the command function as `insecure_tls` + +## T002 [US1] Write failing test that --insecure-tls-config emits deprecation warning +- [x] Add test in opt_test.py that invokes a command with `--insecure-tls-config` +- [x] Verify the command still succeeds (backward compatibility) +- [x] Verify a deprecation warning is printed to stderr + +## T003 [US1] Rename the option in opt.py +- [x] Change primary flag from `--insecure-tls-config` to `--insecure-tls` +- [x] Change the Python parameter name from `insecure_tls_config` to `insecure_tls` +- [x] Add `--insecure-tls-config` as a hidden deprecated alias with a callback that emits a warning +- [x] Update `confirm_insecure_tls` function signature to use `insecure_tls` parameter name +- [x] Rename the exported symbol from `opt_insecure_tls_config` to `opt_insecure_tls` + +## T004 [US1] Update all call sites +- [x] Update jumpstarter_cli/login.py: imports, decorator, parameter name, and usages +- [x] Update jumpstarter_cli_admin/create.py: imports, decorator, parameter name, and usages +- [x] Update jumpstarter_cli_admin/import_res.py: imports, decorator, parameter name, and usages + +## T005 [US2] Update tests and documentation references +- [x] Update create_test.py: change `--insecure-tls-config` to `--insecure-tls` in test invocations +- [x] Update import_res_test.py: change `--insecure-tls-config` to `--insecure-tls` in test invocations +- [x] Update documentation files that reference `--insecure-tls-config` + +## T006 Run tests and linting +- [x] Run unit tests for jumpstarter-cli-common +- [x] Run unit tests for jumpstarter-cli-admin +- [x] Run linting with make lint-fix From 25f7682fbb2c6b46b061803d9fa02174e508ea2a Mon Sep 17 00:00:00 2001 From: Paul Wallrabe Date: Tue, 17 Mar 2026 18:33:06 +0100 Subject: [PATCH 02/13] docs: add feature specification and implementation plan Co-Authored-By: Claude Opus 4.6 --- .../contracts/cli-flags.md | 96 +++++++++++++ specs/003-fix-tls-flag-naming/data-model.md | 45 ++++++ specs/003-fix-tls-flag-naming/plan.md | 90 ++++++++++++ specs/003-fix-tls-flag-naming/quickstart.md | 55 ++++++++ specs/003-fix-tls-flag-naming/research.md | 128 ++++++++++++++++++ specs/003-fix-tls-flag-naming/spec.md | 118 ++++++++++++++++ 6 files changed, 532 insertions(+) create mode 100644 specs/003-fix-tls-flag-naming/contracts/cli-flags.md create mode 100644 specs/003-fix-tls-flag-naming/data-model.md create mode 100644 specs/003-fix-tls-flag-naming/plan.md create mode 100644 specs/003-fix-tls-flag-naming/quickstart.md create mode 100644 specs/003-fix-tls-flag-naming/research.md create mode 100644 specs/003-fix-tls-flag-naming/spec.md diff --git a/specs/003-fix-tls-flag-naming/contracts/cli-flags.md b/specs/003-fix-tls-flag-naming/contracts/cli-flags.md new file mode 100644 index 000000000..234638ae0 --- /dev/null +++ b/specs/003-fix-tls-flag-naming/contracts/cli-flags.md @@ -0,0 +1,96 @@ +# Contract: CLI Flag Names and Deprecation Behavior + +**Feature**: 003-fix-tls-flag-naming +**Date**: 2026-03-17 + +## New Flag Definition + +### `--insecure-tls` (replaces `--insecure-tls-config`) + +- **CLI name**: `--insecure-tls` +- **Parameter name**: `insecure_tls` +- **Type**: Boolean flag (`is_flag=True`) +- **Default**: `False` +- **Help text**: "Disable endpoint TLS verification. This is insecure and should only be used for testing purposes." +- **Available on**: `jmp login`, `jmp admin create client`, `jmp admin create exporter`, `jmp admin import client`, `jmp admin import exporter` + +### `--insecure-tls-config` (deprecated alias) + +- **CLI name**: `--insecure-tls-config` +- **Parameter name**: `insecure_tls` (shared with `--insecure-tls`) +- **Type**: Boolean flag (`is_flag=True`) +- **Default**: `False` +- **Hidden**: `True` (not shown in `--help`) +- **Help text**: "Deprecated: use --insecure-tls instead." +- **Behavior**: When used, emits deprecation warning to stderr before proceeding. + +### `--insecure-login-tls` (unchanged) + +- **CLI name**: `--insecure-login-tls` +- **Parameter name**: `insecure_login_tls` +- **Type**: Boolean flag +- **Default**: `False` +- **Help text**: "Skip TLS certificate verification when fetching config from login endpoint." +- **Available on**: `jmp login` only + +### `--insecure-login-http` (unchanged) + +- **CLI name**: `--insecure-login-http` +- **Parameter name**: `insecure_login_http` +- **Type**: Boolean flag +- **Default**: `False` +- **Help text**: "Use HTTP instead of HTTPS when fetching config from login endpoint (for local testing)." +- **Available on**: `jmp login` only + +## Deprecation Warning Format + +``` +Warning: '--insecure-tls-config' is deprecated. Use '--insecure-tls' instead. +``` + +Output destination: stderr (via `click.echo(..., err=True)`) + +## Deprecation Timeline + +- **Current release**: Both `--insecure-tls` and `--insecure-tls-config` work. + The old name emits a warning. +- **Next minor release**: Old name continues to work with warning (minimum one + release cycle). +- **Future major release**: Old name may be removed. + +## Click Implementation Pattern + +```python +def _deprecated_insecure_tls_callback(ctx, param, value): + if value: + click.echo( + "Warning: '--insecure-tls-config' is deprecated. " + "Use '--insecure-tls' instead.", + err=True, + ) + return value + +opt_insecure_tls = click.option( + "--insecure-tls", + "insecure_tls", + is_flag=True, + default=False, + help="Disable endpoint TLS verification. This is insecure and " + "should only be used for testing purposes.", +) + +opt_insecure_tls_config = click.option( + "--insecure-tls-config", + "insecure_tls", + is_flag=True, + default=False, + hidden=True, + callback=_deprecated_insecure_tls_callback, + expose_value=False, + help="Deprecated: use --insecure-tls instead.", +) +``` + +Both decorators MUST be applied to every command that supports this flag, with +`opt_insecure_tls_config` (deprecated) applied before `opt_insecure_tls` (new) +in the decorator stack to ensure proper parameter resolution. diff --git a/specs/003-fix-tls-flag-naming/data-model.md b/specs/003-fix-tls-flag-naming/data-model.md new file mode 100644 index 000000000..0436cc99c --- /dev/null +++ b/specs/003-fix-tls-flag-naming/data-model.md @@ -0,0 +1,45 @@ +# Data Model: Flag Mapping + +**Feature**: 003-fix-tls-flag-naming +**Date**: 2026-03-17 + +## Flag Rename Mapping + +| Old CLI Flag | New CLI Flag | Python Parameter (old) | Python Parameter (new) | Scope | Action | +|--------------------------|--------------------------|--------------------------|------------------------|-------------|---------------| +| `--insecure-tls-config` | `--insecure-tls` | `insecure_tls_config` | `insecure_tls` | All commands | Rename + deprecate old | +| `--insecure-login-tls` | `--insecure-login-tls` | `insecure_login_tls` | `insecure_login_tls` | `jmp login` | Keep as-is | +| `--insecure-login-http` | `--insecure-login-http` | `insecure_login_http` | `insecure_login_http` | `jmp login` | Keep as-is | + +## Shared Option Decorators + +| Decorator Variable (old) | Decorator Variable (new) | Defined In | +|--------------------------------|--------------------------|--------------------| +| `opt_insecure_tls_config` | `opt_insecure_tls` | `opt.py` | +| (new, hidden) | `opt_insecure_tls_config`| `opt.py` (deprecated alias) | + +## Commands Affected + +| Command | File | Uses Flag | +|-----------------------|-------------------------------------|----------------------| +| `jmp login` | `jumpstarter_cli/login.py` | `--insecure-tls` | +| `jmp admin create client` | `jumpstarter_cli_admin/create.py` | `--insecure-tls` | +| `jmp admin create exporter` | `jumpstarter_cli_admin/create.py` | `--insecure-tls` | +| `jmp admin import client` | `jumpstarter_cli_admin/import_res.py` | `--insecure-tls` | +| `jmp admin import exporter` | `jumpstarter_cli_admin/import_res.py` | `--insecure-tls` | + +## Helper Function Impact + +| Function | File | Parameter Change | +|------------------------|----------|--------------------------------------------| +| `confirm_insecure_tls` | `opt.py` | `insecure_tls_config` -> `insecure_tls` | + +## Deprecation Behavior + +When `--insecure-tls-config` is used: + +1. The flag is accepted (hidden from `--help`). +2. A warning is printed to stderr: + `Warning: '--insecure-tls-config' is deprecated. Use '--insecure-tls' instead.` +3. The value is written to the same destination parameter `insecure_tls`. +4. Command execution proceeds normally. diff --git a/specs/003-fix-tls-flag-naming/plan.md b/specs/003-fix-tls-flag-naming/plan.md new file mode 100644 index 000000000..8bcea3326 --- /dev/null +++ b/specs/003-fix-tls-flag-naming/plan.md @@ -0,0 +1,90 @@ +# Implementation Plan: Fix TLS Flag Naming + +**Branch**: `003-fix-tls-flag-naming` | **Date**: 2026-03-17 | **Spec**: [spec.md](spec.md) +**Input**: Feature specification from `/specs/003-fix-tls-flag-naming/spec.md` + +## Summary + +Rename the inconsistent `--insecure-tls-config` CLI flag to `--insecure-tls` +across all commands (`jmp login`, `jmp admin create`, `jmp admin import`). The +old name is preserved as a hidden deprecated alias that emits a warning to +stderr. The two login-specific flags (`--insecure-login-tls`, +`--insecure-login-http`) are already well-named and remain unchanged. + +The implementation uses Click's `hidden=True` option with a shared destination +parameter to provide backward-compatible aliasing with zero custom subclasses. + +## Technical Context + +**Language/Version**: Python 3.11+ +**Primary Dependencies**: Click (CLI framework) +**Storage**: N/A (flag rename only, no data changes) +**Testing**: pytest (unit tests in `*_test.py` files), bats (e2e tests) +**Target Platform**: Linux, macOS (CLI tool) +**Project Type**: CLI +**Performance Goals**: N/A (no runtime impact) +**Constraints**: Backward compatibility required for at least one minor release +**Scale/Scope**: 9 files modified across 4 packages + docs + e2e + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +| Principle | Status | Notes | +|-----------|--------|-------| +| I. Clean Code | PASS | Renaming improves intention-revealing naming | +| II. Minimal Dependencies | PASS | No new dependencies; uses existing Click features | +| III. Secure Coding | PASS | Security-relevant flags retain clear warnings | +| IV. Test-Driven Development | PASS | Tests will be written for new flag name and deprecation warning before implementation | +| V. Simplicity | PASS | Dual-option with hidden=True is the simplest deprecation approach | +| Security Requirements | PASS | TLS flags remain functional; no security regression | +| Development Workflow | PASS | Single concern (flag rename), conventional commits | + +No violations. No complexity tracking needed. + +## Project Structure + +### Documentation (this feature) + +```text +specs/003-fix-tls-flag-naming/ ++-- plan.md # This file ++-- research.md # Click deprecation pattern research ++-- data-model.md # Flag mapping (old -> new) ++-- quickstart.md # Migration guide ++-- contracts/ +| +-- cli-flags.md # Flag names and deprecation behavior contract ++-- tasks.md # Task breakdown (created separately) +``` + +### Source Code (repository root) + +```text +python/packages/jumpstarter-cli-common/jumpstarter_cli_common/ ++-- opt.py # Flag definitions (primary change) + +python/packages/jumpstarter-cli/jumpstarter_cli/ ++-- login.py # Update import and parameter name + +python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/ ++-- create.py # Update import and parameter name ++-- create_test.py # Add new-flag tests, keep old-flag tests ++-- import_res.py # Update import and parameter name ++-- import_res_test.py # Add new-flag tests, keep old-flag tests + +python/docs/source/getting-started/ ++-- guides/setup-distributed-mode.md # Update flag references ++-- configuration/authentication.md # Update flag references + +e2e/ ++-- tests.bats # Update flag references +``` + +**Structure Decision**: This is a cross-cutting rename across an existing +monorepo. No new packages or directories are needed. Changes touch the shared +`jumpstarter-cli-common` package (where the option is defined), the two CLI +packages that consume it, documentation, and e2e tests. + +## Complexity Tracking + +No constitution violations. Table intentionally left empty. diff --git a/specs/003-fix-tls-flag-naming/quickstart.md b/specs/003-fix-tls-flag-naming/quickstart.md new file mode 100644 index 000000000..ab671452d --- /dev/null +++ b/specs/003-fix-tls-flag-naming/quickstart.md @@ -0,0 +1,55 @@ +# Quickstart: Fix TLS Flag Naming + +**Feature**: 003-fix-tls-flag-naming +**Date**: 2026-03-17 + +## What Changed + +The `--insecure-tls-config` flag has been renamed to `--insecure-tls` across +all commands. The old name still works but prints a deprecation warning. + +## Before and After + +### Before + +```bash +jmp login my-client@login.example.com --insecure-tls-config +jmp admin create client my-client --insecure-tls-config +jmp admin create exporter my-exporter --insecure-tls-config +``` + +### After + +```bash +jmp login my-client@login.example.com --insecure-tls +jmp admin create client my-client --insecure-tls +jmp admin create exporter my-exporter --insecure-tls +``` + +## Migration Guide + +1. Search your scripts for `--insecure-tls-config` and replace with + `--insecure-tls`. +2. No other flags changed. `--insecure-login-tls` and `--insecure-login-http` + remain the same. +3. Old flag will continue to work during the deprecation period but will emit: + ``` + Warning: '--insecure-tls-config' is deprecated. Use '--insecure-tls' instead. + ``` + +## Verification + +```bash +# Verify new flag appears in help +jmp login --help | grep insecure-tls + +# Verify old flag still works (with deprecation warning) +jmp login my-client@login.example.com --insecure-tls-config 2>&1 | grep "deprecated" +``` + +## Unchanged Flags + +The following flags were already well-named and remain unchanged: + +- `--insecure-login-tls` -- skip TLS verification for login endpoint +- `--insecure-login-http` -- use HTTP instead of HTTPS for login endpoint diff --git a/specs/003-fix-tls-flag-naming/research.md b/specs/003-fix-tls-flag-naming/research.md new file mode 100644 index 000000000..b16f58ffc --- /dev/null +++ b/specs/003-fix-tls-flag-naming/research.md @@ -0,0 +1,128 @@ +# Research: Click Flag Deprecation and Aliasing Patterns + +**Feature**: 003-fix-tls-flag-naming +**Date**: 2026-03-17 + +## Click Option Deprecation Strategies + +### Strategy 1: Custom Click Option Class with Deprecation Warning + +Click does not have a built-in deprecation mechanism for options. The standard +approach is to create a custom `click.Option` subclass that intercepts the +option processing and emits a warning when the deprecated name is used. + +```python +import warnings +import click + +class DeprecatedOption(click.Option): + def __init__(self, *args, deprecated_name=None, **kwargs): + self.deprecated_name = deprecated_name + super().__init__(*args, **kwargs) + + def type_cast_value(self, ctx, value): + if self.deprecated_name and self.deprecated_name in (ctx.params or {}): + warnings.warn( + f"'{self.deprecated_name}' is deprecated, use '{self.name}' instead.", + DeprecationWarning, + stacklevel=2, + ) + return super().type_cast_value(ctx, value) +``` + +**Limitation**: This approach requires tracking which CLI token was actually +used, which Click does not expose directly. + +### Strategy 2: Dual Options with Callback Merging (Recommended) + +Define both the old and new flag as separate Click options, where the old one +triggers a deprecation warning via a callback and maps its value into the new +parameter name. + +```python +def _deprecated_flag_callback(ctx, param, value, *, new_name, old_name): + if value: + click.echo( + f"Warning: '--{old_name}' is deprecated. Use '--{new_name}' instead.", + err=True, + ) + ctx.params[new_name.replace("-", "_")] = value + return value + +opt_insecure_tls = click.option( + "--insecure-tls", + "insecure_tls", + is_flag=True, + default=False, + help="Disable endpoint TLS verification.", +) + +opt_insecure_tls_config_deprecated = click.option( + "--insecure-tls-config", + "insecure_tls", # same dest as new option + is_flag=True, + default=False, + hidden=True, # hide from --help + help="Deprecated: use --insecure-tls instead.", +) +``` + +**Key insight**: Click allows multiple options to write to the same parameter +name. By using `hidden=True` the deprecated flag disappears from `--help` but +remains functional. No callback is needed for basic aliasing since both options +write to the same destination parameter. + +For the deprecation warning, a callback on the hidden option can emit the +warning. + +### Strategy 3: Click Parameter Secondary Names + +Click options support multiple names out of the box: + +```python +click.option("--insecure-tls", "--insecure-tls-config", "insecure_tls", ...) +``` + +However, this shows both names in `--help` output and provides no mechanism to +warn about the deprecated name. Not suitable for our use case. + +## Recommended Approach + +**Strategy 2** (dual options with shared destination) is the best fit because: + +1. It hides the deprecated flag from `--help` via `hidden=True`. +2. It allows a deprecation warning via a callback on the hidden option. +3. It requires no custom Click subclasses, keeping the code simple. +4. It is forward-compatible: removing the deprecated option later is a one-line + deletion. + +## Existing Patterns in Jumpstarter + +The codebase uses `click.option` decorators defined as module-level variables +in `opt.py` and applied as decorators on command functions. The pattern of +shared option decorators (e.g., `opt_insecure_tls_config`, `opt_nointeractive`) +is well-established. + +The deprecation approach should follow this same pattern: define +`opt_insecure_tls` as the new decorator and `opt_insecure_tls_config` as a +hidden deprecated alias, both in `opt.py`. + +## Files Requiring Changes + +1. `python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py` - + Rename option, add deprecated alias +2. `python/packages/jumpstarter-cli/jumpstarter_cli/login.py` - + Update import and usage +3. `python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py` - + Update import and usage +4. `python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res.py` - + Update import and usage +5. `python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py` - + Add tests for new name, keep tests for old name +6. `python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py` - + Add tests for new name, keep tests for old name +7. `python/docs/source/getting-started/guides/setup-distributed-mode.md` - + Update flag references +8. `python/docs/source/getting-started/configuration/authentication.md` - + Update flag references +9. `e2e/tests.bats` - Update flag references diff --git a/specs/003-fix-tls-flag-naming/spec.md b/specs/003-fix-tls-flag-naming/spec.md new file mode 100644 index 000000000..ad7a1660c --- /dev/null +++ b/specs/003-fix-tls-flag-naming/spec.md @@ -0,0 +1,118 @@ +# Feature Specification: Fix TLS Flag Naming + +**Feature Branch**: `003-fix-tls-flag-naming` +**Created**: 2026-03-17 +**Status**: Draft +**Input**: Rename inconsistent TLS/insecure flags in `jmp login` and related commands + +## User Scenarios & Testing + +### User Story 1 - Consistent flag naming for TLS options (Priority: P1) + +A user running `jmp login` or `jmp admin create` should see consistently named +`--insecure-*` flags that clearly communicate their scope and effect. The current +flag `--insecure-tls-config` is ambiguous because "config" does not describe what +is being made insecure. + +**Why this priority**: Confusing flag names lead to misconfiguration and reduced +trust in security-sensitive operations. + +**Independent Test**: Run `jmp login --help` and verify the new flag names appear +with clear help text. Verify old flag names still work but emit a deprecation +warning. + +**Acceptance Scenarios**: + +1. **Given** a user runs `jmp login --help`, **When** viewing the output, + **Then** the new flag names are displayed and the old names are hidden or + marked deprecated. +2. **Given** a user runs `jmp login --insecure-tls-config`, **When** the command + executes, **Then** it works but prints a deprecation warning to stderr. +3. **Given** a user runs `jmp login --insecure-tls`, **When** the command + executes, **Then** it behaves identically to the old `--insecure-tls-config` + without any deprecation warning. + +--- + +### User Story 2 - Backward compatibility during transition (Priority: P1) + +Existing scripts and documentation that use the old flag names must continue to +work during a deprecation period. + +**Why this priority**: Breaking existing automation without warning violates user +trust. + +**Independent Test**: Run existing e2e tests with old flag names and verify they +pass. Run them with new flag names and verify they also pass. + +**Acceptance Scenarios**: + +1. **Given** a CI script uses `--insecure-tls-config`, **When** the script runs, + **Then** it succeeds and emits a deprecation warning. +2. **Given** documentation references old flag names, **When** a user follows + the docs, **Then** the commands still work. + +--- + +### Edge Cases + +- What happens when both old and new flag names are provided simultaneously? + The command should reject this with a clear error message. +- What happens when the deprecated flag is used in non-interactive mode? + The deprecation warning should still be printed to stderr. + +## Requirements + +### Functional Requirements + +- **FR-001**: The flag `--insecure-tls-config` MUST be renamed to `--insecure-tls` + across all commands that use it (`jmp login`, `jmp admin create`, `jmp admin import`). +- **FR-002**: The flags `--insecure-login-tls` and `--insecure-login-http` MUST + be kept as-is (they already follow the `--insecure-` convention). +- **FR-003**: The old flag name `--insecure-tls-config` MUST remain functional + as a deprecated alias for at least one minor release cycle. +- **FR-004**: Using the deprecated alias MUST emit a warning message to stderr. +- **FR-005**: Documentation MUST be updated to use the new flag names. +- **FR-006**: The Python parameter name `insecure_tls_config` SHOULD be renamed + to `insecure_tls` for internal consistency, with the Click option handling the + mapping. + +### Key Entities + +- **opt_insecure_tls_config**: The shared Click option decorator in + `jumpstarter_cli_common/opt.py`, used by login, create, and import commands. + +## Clarifications + +### Naming Convention Decision + +**Recommendation**: Use the `--insecure-` pattern. + +The three flags serve distinct purposes at different scopes: + +| Current Name | New Name | Scope | Rationale | +|--------------------------|------------------------|--------------------------------|----------------------------| +| `--insecure-tls-config` | `--insecure-tls` | Endpoint gRPC TLS verification | "config" is redundant; the flag disables TLS verification for the stored endpoint config | +| `--insecure-login-tls` | `--insecure-login-tls` | Login endpoint TLS verification | Already follows convention | +| `--insecure-login-http` | `--insecure-login-http`| Login endpoint transport | Already follows convention | + +**Why `--insecure-tls` over `--insecure-endpoint-tls`**: The flag applies to +the general TLS configuration stored in the client/exporter config, not just a +specific endpoint during login. The shorter form `--insecure-tls` is clearer +because it is the "default" scope -- if no qualifier is given, it applies to +the primary TLS connection. The login-specific flags already carry the `login-` +qualifier to distinguish themselves. + +**Alternative rejected**: `--insecure-endpoint-tls` was considered but rejected +because "endpoint" is ambiguous (login endpoint vs gRPC endpoint) and the extra +word adds length without clarity. + +## Success Criteria + +### Measurable Outcomes + +- **SC-001**: `jmp login --help` shows `--insecure-tls` as the flag name. +- **SC-002**: All existing tests pass with the old flag name (backward compat). +- **SC-003**: All existing tests pass with the new flag name. +- **SC-004**: A deprecation warning is emitted when the old name is used. +- **SC-005**: Documentation files reference only the new flag name. From 13f28714915f60fcb5b53178e047e0b15c7517f3 Mon Sep 17 00:00:00 2001 From: Paul Wallrabe Date: Tue, 17 Mar 2026 18:35:54 +0100 Subject: [PATCH 03/13] chore: gitignore specs/ directory Co-Authored-By: Claude Opus 4.6 --- specs/003-fix-tls-flag-naming-impl/tasks.md | 32 ----- .../contracts/cli-flags.md | 96 ------------- specs/003-fix-tls-flag-naming/data-model.md | 45 ------ specs/003-fix-tls-flag-naming/plan.md | 90 ------------ specs/003-fix-tls-flag-naming/quickstart.md | 55 -------- specs/003-fix-tls-flag-naming/research.md | 128 ------------------ specs/003-fix-tls-flag-naming/spec.md | 118 ---------------- 7 files changed, 564 deletions(-) delete mode 100644 specs/003-fix-tls-flag-naming-impl/tasks.md delete mode 100644 specs/003-fix-tls-flag-naming/contracts/cli-flags.md delete mode 100644 specs/003-fix-tls-flag-naming/data-model.md delete mode 100644 specs/003-fix-tls-flag-naming/plan.md delete mode 100644 specs/003-fix-tls-flag-naming/quickstart.md delete mode 100644 specs/003-fix-tls-flag-naming/research.md delete mode 100644 specs/003-fix-tls-flag-naming/spec.md diff --git a/specs/003-fix-tls-flag-naming-impl/tasks.md b/specs/003-fix-tls-flag-naming-impl/tasks.md deleted file mode 100644 index 832bc06ee..000000000 --- a/specs/003-fix-tls-flag-naming-impl/tasks.md +++ /dev/null @@ -1,32 +0,0 @@ -# Tasks: Rename --insecure-tls-config to --insecure-tls - -## T001 [US1] Write failing test that --insecure-tls is accepted -- [x] Add test in opt_test.py that creates a Click command using the new option and invokes it with `--insecure-tls` -- [x] Verify the flag value is passed correctly to the command function as `insecure_tls` - -## T002 [US1] Write failing test that --insecure-tls-config emits deprecation warning -- [x] Add test in opt_test.py that invokes a command with `--insecure-tls-config` -- [x] Verify the command still succeeds (backward compatibility) -- [x] Verify a deprecation warning is printed to stderr - -## T003 [US1] Rename the option in opt.py -- [x] Change primary flag from `--insecure-tls-config` to `--insecure-tls` -- [x] Change the Python parameter name from `insecure_tls_config` to `insecure_tls` -- [x] Add `--insecure-tls-config` as a hidden deprecated alias with a callback that emits a warning -- [x] Update `confirm_insecure_tls` function signature to use `insecure_tls` parameter name -- [x] Rename the exported symbol from `opt_insecure_tls_config` to `opt_insecure_tls` - -## T004 [US1] Update all call sites -- [x] Update jumpstarter_cli/login.py: imports, decorator, parameter name, and usages -- [x] Update jumpstarter_cli_admin/create.py: imports, decorator, parameter name, and usages -- [x] Update jumpstarter_cli_admin/import_res.py: imports, decorator, parameter name, and usages - -## T005 [US2] Update tests and documentation references -- [x] Update create_test.py: change `--insecure-tls-config` to `--insecure-tls` in test invocations -- [x] Update import_res_test.py: change `--insecure-tls-config` to `--insecure-tls` in test invocations -- [x] Update documentation files that reference `--insecure-tls-config` - -## T006 Run tests and linting -- [x] Run unit tests for jumpstarter-cli-common -- [x] Run unit tests for jumpstarter-cli-admin -- [x] Run linting with make lint-fix diff --git a/specs/003-fix-tls-flag-naming/contracts/cli-flags.md b/specs/003-fix-tls-flag-naming/contracts/cli-flags.md deleted file mode 100644 index 234638ae0..000000000 --- a/specs/003-fix-tls-flag-naming/contracts/cli-flags.md +++ /dev/null @@ -1,96 +0,0 @@ -# Contract: CLI Flag Names and Deprecation Behavior - -**Feature**: 003-fix-tls-flag-naming -**Date**: 2026-03-17 - -## New Flag Definition - -### `--insecure-tls` (replaces `--insecure-tls-config`) - -- **CLI name**: `--insecure-tls` -- **Parameter name**: `insecure_tls` -- **Type**: Boolean flag (`is_flag=True`) -- **Default**: `False` -- **Help text**: "Disable endpoint TLS verification. This is insecure and should only be used for testing purposes." -- **Available on**: `jmp login`, `jmp admin create client`, `jmp admin create exporter`, `jmp admin import client`, `jmp admin import exporter` - -### `--insecure-tls-config` (deprecated alias) - -- **CLI name**: `--insecure-tls-config` -- **Parameter name**: `insecure_tls` (shared with `--insecure-tls`) -- **Type**: Boolean flag (`is_flag=True`) -- **Default**: `False` -- **Hidden**: `True` (not shown in `--help`) -- **Help text**: "Deprecated: use --insecure-tls instead." -- **Behavior**: When used, emits deprecation warning to stderr before proceeding. - -### `--insecure-login-tls` (unchanged) - -- **CLI name**: `--insecure-login-tls` -- **Parameter name**: `insecure_login_tls` -- **Type**: Boolean flag -- **Default**: `False` -- **Help text**: "Skip TLS certificate verification when fetching config from login endpoint." -- **Available on**: `jmp login` only - -### `--insecure-login-http` (unchanged) - -- **CLI name**: `--insecure-login-http` -- **Parameter name**: `insecure_login_http` -- **Type**: Boolean flag -- **Default**: `False` -- **Help text**: "Use HTTP instead of HTTPS when fetching config from login endpoint (for local testing)." -- **Available on**: `jmp login` only - -## Deprecation Warning Format - -``` -Warning: '--insecure-tls-config' is deprecated. Use '--insecure-tls' instead. -``` - -Output destination: stderr (via `click.echo(..., err=True)`) - -## Deprecation Timeline - -- **Current release**: Both `--insecure-tls` and `--insecure-tls-config` work. - The old name emits a warning. -- **Next minor release**: Old name continues to work with warning (minimum one - release cycle). -- **Future major release**: Old name may be removed. - -## Click Implementation Pattern - -```python -def _deprecated_insecure_tls_callback(ctx, param, value): - if value: - click.echo( - "Warning: '--insecure-tls-config' is deprecated. " - "Use '--insecure-tls' instead.", - err=True, - ) - return value - -opt_insecure_tls = click.option( - "--insecure-tls", - "insecure_tls", - is_flag=True, - default=False, - help="Disable endpoint TLS verification. This is insecure and " - "should only be used for testing purposes.", -) - -opt_insecure_tls_config = click.option( - "--insecure-tls-config", - "insecure_tls", - is_flag=True, - default=False, - hidden=True, - callback=_deprecated_insecure_tls_callback, - expose_value=False, - help="Deprecated: use --insecure-tls instead.", -) -``` - -Both decorators MUST be applied to every command that supports this flag, with -`opt_insecure_tls_config` (deprecated) applied before `opt_insecure_tls` (new) -in the decorator stack to ensure proper parameter resolution. diff --git a/specs/003-fix-tls-flag-naming/data-model.md b/specs/003-fix-tls-flag-naming/data-model.md deleted file mode 100644 index 0436cc99c..000000000 --- a/specs/003-fix-tls-flag-naming/data-model.md +++ /dev/null @@ -1,45 +0,0 @@ -# Data Model: Flag Mapping - -**Feature**: 003-fix-tls-flag-naming -**Date**: 2026-03-17 - -## Flag Rename Mapping - -| Old CLI Flag | New CLI Flag | Python Parameter (old) | Python Parameter (new) | Scope | Action | -|--------------------------|--------------------------|--------------------------|------------------------|-------------|---------------| -| `--insecure-tls-config` | `--insecure-tls` | `insecure_tls_config` | `insecure_tls` | All commands | Rename + deprecate old | -| `--insecure-login-tls` | `--insecure-login-tls` | `insecure_login_tls` | `insecure_login_tls` | `jmp login` | Keep as-is | -| `--insecure-login-http` | `--insecure-login-http` | `insecure_login_http` | `insecure_login_http` | `jmp login` | Keep as-is | - -## Shared Option Decorators - -| Decorator Variable (old) | Decorator Variable (new) | Defined In | -|--------------------------------|--------------------------|--------------------| -| `opt_insecure_tls_config` | `opt_insecure_tls` | `opt.py` | -| (new, hidden) | `opt_insecure_tls_config`| `opt.py` (deprecated alias) | - -## Commands Affected - -| Command | File | Uses Flag | -|-----------------------|-------------------------------------|----------------------| -| `jmp login` | `jumpstarter_cli/login.py` | `--insecure-tls` | -| `jmp admin create client` | `jumpstarter_cli_admin/create.py` | `--insecure-tls` | -| `jmp admin create exporter` | `jumpstarter_cli_admin/create.py` | `--insecure-tls` | -| `jmp admin import client` | `jumpstarter_cli_admin/import_res.py` | `--insecure-tls` | -| `jmp admin import exporter` | `jumpstarter_cli_admin/import_res.py` | `--insecure-tls` | - -## Helper Function Impact - -| Function | File | Parameter Change | -|------------------------|----------|--------------------------------------------| -| `confirm_insecure_tls` | `opt.py` | `insecure_tls_config` -> `insecure_tls` | - -## Deprecation Behavior - -When `--insecure-tls-config` is used: - -1. The flag is accepted (hidden from `--help`). -2. A warning is printed to stderr: - `Warning: '--insecure-tls-config' is deprecated. Use '--insecure-tls' instead.` -3. The value is written to the same destination parameter `insecure_tls`. -4. Command execution proceeds normally. diff --git a/specs/003-fix-tls-flag-naming/plan.md b/specs/003-fix-tls-flag-naming/plan.md deleted file mode 100644 index 8bcea3326..000000000 --- a/specs/003-fix-tls-flag-naming/plan.md +++ /dev/null @@ -1,90 +0,0 @@ -# Implementation Plan: Fix TLS Flag Naming - -**Branch**: `003-fix-tls-flag-naming` | **Date**: 2026-03-17 | **Spec**: [spec.md](spec.md) -**Input**: Feature specification from `/specs/003-fix-tls-flag-naming/spec.md` - -## Summary - -Rename the inconsistent `--insecure-tls-config` CLI flag to `--insecure-tls` -across all commands (`jmp login`, `jmp admin create`, `jmp admin import`). The -old name is preserved as a hidden deprecated alias that emits a warning to -stderr. The two login-specific flags (`--insecure-login-tls`, -`--insecure-login-http`) are already well-named and remain unchanged. - -The implementation uses Click's `hidden=True` option with a shared destination -parameter to provide backward-compatible aliasing with zero custom subclasses. - -## Technical Context - -**Language/Version**: Python 3.11+ -**Primary Dependencies**: Click (CLI framework) -**Storage**: N/A (flag rename only, no data changes) -**Testing**: pytest (unit tests in `*_test.py` files), bats (e2e tests) -**Target Platform**: Linux, macOS (CLI tool) -**Project Type**: CLI -**Performance Goals**: N/A (no runtime impact) -**Constraints**: Backward compatibility required for at least one minor release -**Scale/Scope**: 9 files modified across 4 packages + docs + e2e - -## Constitution Check - -*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - -| Principle | Status | Notes | -|-----------|--------|-------| -| I. Clean Code | PASS | Renaming improves intention-revealing naming | -| II. Minimal Dependencies | PASS | No new dependencies; uses existing Click features | -| III. Secure Coding | PASS | Security-relevant flags retain clear warnings | -| IV. Test-Driven Development | PASS | Tests will be written for new flag name and deprecation warning before implementation | -| V. Simplicity | PASS | Dual-option with hidden=True is the simplest deprecation approach | -| Security Requirements | PASS | TLS flags remain functional; no security regression | -| Development Workflow | PASS | Single concern (flag rename), conventional commits | - -No violations. No complexity tracking needed. - -## Project Structure - -### Documentation (this feature) - -```text -specs/003-fix-tls-flag-naming/ -+-- plan.md # This file -+-- research.md # Click deprecation pattern research -+-- data-model.md # Flag mapping (old -> new) -+-- quickstart.md # Migration guide -+-- contracts/ -| +-- cli-flags.md # Flag names and deprecation behavior contract -+-- tasks.md # Task breakdown (created separately) -``` - -### Source Code (repository root) - -```text -python/packages/jumpstarter-cli-common/jumpstarter_cli_common/ -+-- opt.py # Flag definitions (primary change) - -python/packages/jumpstarter-cli/jumpstarter_cli/ -+-- login.py # Update import and parameter name - -python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/ -+-- create.py # Update import and parameter name -+-- create_test.py # Add new-flag tests, keep old-flag tests -+-- import_res.py # Update import and parameter name -+-- import_res_test.py # Add new-flag tests, keep old-flag tests - -python/docs/source/getting-started/ -+-- guides/setup-distributed-mode.md # Update flag references -+-- configuration/authentication.md # Update flag references - -e2e/ -+-- tests.bats # Update flag references -``` - -**Structure Decision**: This is a cross-cutting rename across an existing -monorepo. No new packages or directories are needed. Changes touch the shared -`jumpstarter-cli-common` package (where the option is defined), the two CLI -packages that consume it, documentation, and e2e tests. - -## Complexity Tracking - -No constitution violations. Table intentionally left empty. diff --git a/specs/003-fix-tls-flag-naming/quickstart.md b/specs/003-fix-tls-flag-naming/quickstart.md deleted file mode 100644 index ab671452d..000000000 --- a/specs/003-fix-tls-flag-naming/quickstart.md +++ /dev/null @@ -1,55 +0,0 @@ -# Quickstart: Fix TLS Flag Naming - -**Feature**: 003-fix-tls-flag-naming -**Date**: 2026-03-17 - -## What Changed - -The `--insecure-tls-config` flag has been renamed to `--insecure-tls` across -all commands. The old name still works but prints a deprecation warning. - -## Before and After - -### Before - -```bash -jmp login my-client@login.example.com --insecure-tls-config -jmp admin create client my-client --insecure-tls-config -jmp admin create exporter my-exporter --insecure-tls-config -``` - -### After - -```bash -jmp login my-client@login.example.com --insecure-tls -jmp admin create client my-client --insecure-tls -jmp admin create exporter my-exporter --insecure-tls -``` - -## Migration Guide - -1. Search your scripts for `--insecure-tls-config` and replace with - `--insecure-tls`. -2. No other flags changed. `--insecure-login-tls` and `--insecure-login-http` - remain the same. -3. Old flag will continue to work during the deprecation period but will emit: - ``` - Warning: '--insecure-tls-config' is deprecated. Use '--insecure-tls' instead. - ``` - -## Verification - -```bash -# Verify new flag appears in help -jmp login --help | grep insecure-tls - -# Verify old flag still works (with deprecation warning) -jmp login my-client@login.example.com --insecure-tls-config 2>&1 | grep "deprecated" -``` - -## Unchanged Flags - -The following flags were already well-named and remain unchanged: - -- `--insecure-login-tls` -- skip TLS verification for login endpoint -- `--insecure-login-http` -- use HTTP instead of HTTPS for login endpoint diff --git a/specs/003-fix-tls-flag-naming/research.md b/specs/003-fix-tls-flag-naming/research.md deleted file mode 100644 index b16f58ffc..000000000 --- a/specs/003-fix-tls-flag-naming/research.md +++ /dev/null @@ -1,128 +0,0 @@ -# Research: Click Flag Deprecation and Aliasing Patterns - -**Feature**: 003-fix-tls-flag-naming -**Date**: 2026-03-17 - -## Click Option Deprecation Strategies - -### Strategy 1: Custom Click Option Class with Deprecation Warning - -Click does not have a built-in deprecation mechanism for options. The standard -approach is to create a custom `click.Option` subclass that intercepts the -option processing and emits a warning when the deprecated name is used. - -```python -import warnings -import click - -class DeprecatedOption(click.Option): - def __init__(self, *args, deprecated_name=None, **kwargs): - self.deprecated_name = deprecated_name - super().__init__(*args, **kwargs) - - def type_cast_value(self, ctx, value): - if self.deprecated_name and self.deprecated_name in (ctx.params or {}): - warnings.warn( - f"'{self.deprecated_name}' is deprecated, use '{self.name}' instead.", - DeprecationWarning, - stacklevel=2, - ) - return super().type_cast_value(ctx, value) -``` - -**Limitation**: This approach requires tracking which CLI token was actually -used, which Click does not expose directly. - -### Strategy 2: Dual Options with Callback Merging (Recommended) - -Define both the old and new flag as separate Click options, where the old one -triggers a deprecation warning via a callback and maps its value into the new -parameter name. - -```python -def _deprecated_flag_callback(ctx, param, value, *, new_name, old_name): - if value: - click.echo( - f"Warning: '--{old_name}' is deprecated. Use '--{new_name}' instead.", - err=True, - ) - ctx.params[new_name.replace("-", "_")] = value - return value - -opt_insecure_tls = click.option( - "--insecure-tls", - "insecure_tls", - is_flag=True, - default=False, - help="Disable endpoint TLS verification.", -) - -opt_insecure_tls_config_deprecated = click.option( - "--insecure-tls-config", - "insecure_tls", # same dest as new option - is_flag=True, - default=False, - hidden=True, # hide from --help - help="Deprecated: use --insecure-tls instead.", -) -``` - -**Key insight**: Click allows multiple options to write to the same parameter -name. By using `hidden=True` the deprecated flag disappears from `--help` but -remains functional. No callback is needed for basic aliasing since both options -write to the same destination parameter. - -For the deprecation warning, a callback on the hidden option can emit the -warning. - -### Strategy 3: Click Parameter Secondary Names - -Click options support multiple names out of the box: - -```python -click.option("--insecure-tls", "--insecure-tls-config", "insecure_tls", ...) -``` - -However, this shows both names in `--help` output and provides no mechanism to -warn about the deprecated name. Not suitable for our use case. - -## Recommended Approach - -**Strategy 2** (dual options with shared destination) is the best fit because: - -1. It hides the deprecated flag from `--help` via `hidden=True`. -2. It allows a deprecation warning via a callback on the hidden option. -3. It requires no custom Click subclasses, keeping the code simple. -4. It is forward-compatible: removing the deprecated option later is a one-line - deletion. - -## Existing Patterns in Jumpstarter - -The codebase uses `click.option` decorators defined as module-level variables -in `opt.py` and applied as decorators on command functions. The pattern of -shared option decorators (e.g., `opt_insecure_tls_config`, `opt_nointeractive`) -is well-established. - -The deprecation approach should follow this same pattern: define -`opt_insecure_tls` as the new decorator and `opt_insecure_tls_config` as a -hidden deprecated alias, both in `opt.py`. - -## Files Requiring Changes - -1. `python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py` - - Rename option, add deprecated alias -2. `python/packages/jumpstarter-cli/jumpstarter_cli/login.py` - - Update import and usage -3. `python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py` - - Update import and usage -4. `python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res.py` - - Update import and usage -5. `python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py` - - Add tests for new name, keep tests for old name -6. `python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py` - - Add tests for new name, keep tests for old name -7. `python/docs/source/getting-started/guides/setup-distributed-mode.md` - - Update flag references -8. `python/docs/source/getting-started/configuration/authentication.md` - - Update flag references -9. `e2e/tests.bats` - Update flag references diff --git a/specs/003-fix-tls-flag-naming/spec.md b/specs/003-fix-tls-flag-naming/spec.md deleted file mode 100644 index ad7a1660c..000000000 --- a/specs/003-fix-tls-flag-naming/spec.md +++ /dev/null @@ -1,118 +0,0 @@ -# Feature Specification: Fix TLS Flag Naming - -**Feature Branch**: `003-fix-tls-flag-naming` -**Created**: 2026-03-17 -**Status**: Draft -**Input**: Rename inconsistent TLS/insecure flags in `jmp login` and related commands - -## User Scenarios & Testing - -### User Story 1 - Consistent flag naming for TLS options (Priority: P1) - -A user running `jmp login` or `jmp admin create` should see consistently named -`--insecure-*` flags that clearly communicate their scope and effect. The current -flag `--insecure-tls-config` is ambiguous because "config" does not describe what -is being made insecure. - -**Why this priority**: Confusing flag names lead to misconfiguration and reduced -trust in security-sensitive operations. - -**Independent Test**: Run `jmp login --help` and verify the new flag names appear -with clear help text. Verify old flag names still work but emit a deprecation -warning. - -**Acceptance Scenarios**: - -1. **Given** a user runs `jmp login --help`, **When** viewing the output, - **Then** the new flag names are displayed and the old names are hidden or - marked deprecated. -2. **Given** a user runs `jmp login --insecure-tls-config`, **When** the command - executes, **Then** it works but prints a deprecation warning to stderr. -3. **Given** a user runs `jmp login --insecure-tls`, **When** the command - executes, **Then** it behaves identically to the old `--insecure-tls-config` - without any deprecation warning. - ---- - -### User Story 2 - Backward compatibility during transition (Priority: P1) - -Existing scripts and documentation that use the old flag names must continue to -work during a deprecation period. - -**Why this priority**: Breaking existing automation without warning violates user -trust. - -**Independent Test**: Run existing e2e tests with old flag names and verify they -pass. Run them with new flag names and verify they also pass. - -**Acceptance Scenarios**: - -1. **Given** a CI script uses `--insecure-tls-config`, **When** the script runs, - **Then** it succeeds and emits a deprecation warning. -2. **Given** documentation references old flag names, **When** a user follows - the docs, **Then** the commands still work. - ---- - -### Edge Cases - -- What happens when both old and new flag names are provided simultaneously? - The command should reject this with a clear error message. -- What happens when the deprecated flag is used in non-interactive mode? - The deprecation warning should still be printed to stderr. - -## Requirements - -### Functional Requirements - -- **FR-001**: The flag `--insecure-tls-config` MUST be renamed to `--insecure-tls` - across all commands that use it (`jmp login`, `jmp admin create`, `jmp admin import`). -- **FR-002**: The flags `--insecure-login-tls` and `--insecure-login-http` MUST - be kept as-is (they already follow the `--insecure-` convention). -- **FR-003**: The old flag name `--insecure-tls-config` MUST remain functional - as a deprecated alias for at least one minor release cycle. -- **FR-004**: Using the deprecated alias MUST emit a warning message to stderr. -- **FR-005**: Documentation MUST be updated to use the new flag names. -- **FR-006**: The Python parameter name `insecure_tls_config` SHOULD be renamed - to `insecure_tls` for internal consistency, with the Click option handling the - mapping. - -### Key Entities - -- **opt_insecure_tls_config**: The shared Click option decorator in - `jumpstarter_cli_common/opt.py`, used by login, create, and import commands. - -## Clarifications - -### Naming Convention Decision - -**Recommendation**: Use the `--insecure-` pattern. - -The three flags serve distinct purposes at different scopes: - -| Current Name | New Name | Scope | Rationale | -|--------------------------|------------------------|--------------------------------|----------------------------| -| `--insecure-tls-config` | `--insecure-tls` | Endpoint gRPC TLS verification | "config" is redundant; the flag disables TLS verification for the stored endpoint config | -| `--insecure-login-tls` | `--insecure-login-tls` | Login endpoint TLS verification | Already follows convention | -| `--insecure-login-http` | `--insecure-login-http`| Login endpoint transport | Already follows convention | - -**Why `--insecure-tls` over `--insecure-endpoint-tls`**: The flag applies to -the general TLS configuration stored in the client/exporter config, not just a -specific endpoint during login. The shorter form `--insecure-tls` is clearer -because it is the "default" scope -- if no qualifier is given, it applies to -the primary TLS connection. The login-specific flags already carry the `login-` -qualifier to distinguish themselves. - -**Alternative rejected**: `--insecure-endpoint-tls` was considered but rejected -because "endpoint" is ambiguous (login endpoint vs gRPC endpoint) and the extra -word adds length without clarity. - -## Success Criteria - -### Measurable Outcomes - -- **SC-001**: `jmp login --help` shows `--insecure-tls` as the flag name. -- **SC-002**: All existing tests pass with the old flag name (backward compat). -- **SC-003**: All existing tests pass with the new flag name. -- **SC-004**: A deprecation warning is emitted when the old name is used. -- **SC-005**: Documentation files reference only the new flag name. From 73dbac48390b416da9d29d311ce859e7309a4ac1 Mon Sep 17 00:00:00 2001 From: Paul Wallrabe Date: Tue, 17 Mar 2026 20:47:30 +0100 Subject: [PATCH 04/13] feat: rename --insecure-tls to --insecure and remove redundant login flags Consolidate all TLS/insecure flags into a single --insecure flag. Remove --insecure-login-tls and --insecure-login-http since --insecure covers the entire connection chain including login endpoint. Co-Authored-By: Claude Opus 4.6 --- .../jumpstarter_cli_admin/create.py | 20 +++---- .../jumpstarter_cli_admin/create_test.py | 12 ++-- .../jumpstarter_cli_admin/import_res.py | 20 +++---- .../jumpstarter_cli_admin/import_res_test.py | 10 ++-- .../jumpstarter_cli_common/opt.py | 58 +++++-------------- .../jumpstarter_cli_common/opt_test.py | 41 +++++-------- .../jumpstarter-cli/jumpstarter_cli/login.py | 58 ++++--------------- 7 files changed, 67 insertions(+), 152 deletions(-) diff --git a/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py b/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py index d01a53833..c1bde49d4 100644 --- a/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py +++ b/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py @@ -6,9 +6,9 @@ from jumpstarter_cli_common.callbacks import ClickCallback from jumpstarter_cli_common.opt import ( OutputType, - confirm_insecure_tls, + confirm_insecure, opt_context, - opt_insecure_tls, + opt_insecure, opt_kubeconfig, opt_labels, opt_namespace, @@ -69,7 +69,7 @@ def create(): @opt_labels() @opt_kubeconfig @opt_context -@opt_insecure_tls +@opt_insecure @opt_oidc_username @opt_nointeractive @opt_output_all @@ -78,7 +78,7 @@ async def create_client( name: Optional[str], kubeconfig: Optional[str], context: Optional[str], - insecure_tls: bool, + insecure: bool, namespace: str, labels: dict[str, str], save: bool, @@ -91,7 +91,7 @@ async def create_client( ): """Create a client object in the Kubernetes cluster""" try: - confirm_insecure_tls(insecure_tls, nointeractive) + confirm_insecure(insecure, nointeractive) async with ClientsV1Alpha1Api(namespace, kubeconfig, context) as api: if output is None: # Only print status if is not JSON/YAML @@ -111,7 +111,7 @@ async def create_client( allow_drivers = allow.split(",") if allow is not None and len(allow) > 0 else [] client_config.drivers.unsafe = unsafe client_config.drivers.allow = allow_drivers - client_config.tls.insecure = insecure_tls + client_config.tls.insecure = insecure ClientConfigV1Alpha1.save(client_config, out) # If this is the only client config, set it as default if out is None and len(ClientConfigV1Alpha1.list().items) == 1: @@ -146,7 +146,7 @@ async def create_client( @opt_labels(required=True) @opt_kubeconfig @opt_context -@opt_insecure_tls +@opt_insecure @opt_oidc_username @opt_nointeractive @opt_output_all @@ -155,7 +155,7 @@ async def create_exporter( name: Optional[str], kubeconfig: Optional[str], context: Optional[str], - insecure_tls: bool, + insecure: bool, namespace: str, labels: dict[str, str], save: bool, @@ -166,7 +166,7 @@ async def create_exporter( ): """Create an exporter object in the Kubernetes cluster""" try: - confirm_insecure_tls(insecure_tls, nointeractive) + confirm_insecure(insecure, nointeractive) async with ExportersV1Alpha1Api(namespace, kubeconfig, context) as api: if output is None: click.echo(f"Creating exporter '{name}' in namespace '{namespace}'") @@ -176,7 +176,7 @@ async def create_exporter( if output is None: click.echo("Fetching exporter credentials from cluster") exporter_config = await api.get_exporter_config(name) - exporter_config.tls.insecure = insecure_tls + exporter_config.tls.insecure = insecure ExporterConfigV1Alpha1.save(exporter_config, out) if output is None: click.echo(f"Exporter configuration successfully saved to {exporter_config.path}") diff --git a/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py b/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py index e6703aa39..1235560ae 100644 --- a/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py +++ b/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py @@ -118,7 +118,7 @@ def test_create_client( mock_get_client_config.return_value = INSECURE_TLS_CLIENT_CONFIG # Save with prompts accept insecure = Y, save = Y, unsafe = Y - result = runner.invoke(create, ["client", "--insecure-tls", CLIENT_NAME], input="Y\nY\nY\n") + result = runner.invoke(create, ["client", "--insecure", CLIENT_NAME], input="Y\nY\nY\n") assert result.exit_code == 0 assert "Client configuration successfully saved" in result.output mock_save_client.assert_called_once_with(INSECURE_TLS_CLIENT_CONFIG, None) @@ -126,7 +126,7 @@ def test_create_client( # Save no interactive and insecure tls result = runner.invoke( - create, ["client", "--insecure-tls", "--unsafe", "--save", "--nointeractive", CLIENT_NAME] + create, ["client", "--insecure", "--unsafe", "--save", "--nointeractive", CLIENT_NAME] ) assert result.exit_code == 0 assert "Client configuration successfully saved" in result.output @@ -137,7 +137,7 @@ def test_create_client( mock_get_client_config.return_value = INSECURE_TLS_CLIENT_CONFIG # Save with prompts accept insecure = N - result = runner.invoke(create, ["client", "--insecure-tls", CLIENT_NAME], input="n\n") + result = runner.invoke(create, ["client", "--insecure", CLIENT_NAME], input="n\n") assert result.exit_code == 1 assert "Aborted" in result.output @@ -295,7 +295,7 @@ def test_create_exporter( _get_exporter_config_mock.return_value = INSECURE_TLS_EXPORTER_CONFIG # Save with prompts accept insecure = Y, save = Y result = runner.invoke( - create, ["exporter", "--insecure-tls", EXPORTER_NAME, "--label", "foo=bar"], input="Y\nY\n" + create, ["exporter", "--insecure", EXPORTER_NAME, "--label", "foo=bar"], input="Y\nY\n" ) assert result.exit_code == 0 assert "Exporter configuration successfully saved" in result.output @@ -305,7 +305,7 @@ def test_create_exporter( _get_exporter_config_mock.return_value = INSECURE_TLS_EXPORTER_CONFIG # Save with prompts accept no interactive result = runner.invoke( - create, ["exporter", "--insecure-tls", "--nointeractive", "--save", EXPORTER_NAME, "--label", "foo=bar"] + create, ["exporter", "--insecure", "--nointeractive", "--save", EXPORTER_NAME, "--label", "foo=bar"] ) assert result.exit_code == 0 assert "Exporter configuration successfully saved" in result.output @@ -316,7 +316,7 @@ def test_create_exporter( _get_exporter_config_mock.return_value = INSECURE_TLS_EXPORTER_CONFIG # Save with prompts accept insecure = N result = runner.invoke( - create, ["exporter", "--insecure-tls", EXPORTER_NAME, "--label", "foo=bar"], input="n\n" + create, ["exporter", "--insecure", EXPORTER_NAME, "--label", "foo=bar"], input="n\n" ) assert result.exit_code == 1 assert "Aborted" in result.output diff --git a/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res.py b/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res.py index 2faf0b684..b49cf3818 100644 --- a/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res.py +++ b/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res.py @@ -4,9 +4,9 @@ from jumpstarter_cli_common.blocking import blocking from jumpstarter_cli_common.opt import ( PathOutputType, - confirm_insecure_tls, + confirm_insecure, opt_context, - opt_insecure_tls, + opt_insecure, opt_kubeconfig, opt_namespace, opt_nointeractive, @@ -48,7 +48,7 @@ def import_res(): @opt_namespace @opt_kubeconfig @opt_context -@opt_insecure_tls +@opt_insecure @opt_output_path_only @opt_nointeractive @blocking @@ -57,7 +57,7 @@ async def import_client( namespace: str, kubeconfig: Optional[str], context: Optional[str], - insecure_tls: bool, + insecure: bool, allow: Optional[str], unsafe: bool, out: Optional[str], @@ -69,7 +69,7 @@ async def import_client( if out is None and ClientConfigV1Alpha1.exists(name): raise click.ClickException(f"A client with the name '{name}' already exists") try: - confirm_insecure_tls(insecure_tls, nointeractive) + confirm_insecure(insecure, nointeractive) async with ClientsV1Alpha1Api(namespace, kubeconfig, context) as api: if unsafe is False and allow is None and nointeractive is False: unsafe = click.confirm("Allow unsafe driver client imports?") @@ -81,7 +81,7 @@ async def import_client( click.echo("Fetching client credentials from cluster") allow_drivers = allow.split(",") if allow is not None and len(allow) > 0 else [] client_config = await api.get_client_config(name, allow=allow_drivers, unsafe=unsafe) - client_config.tls.insecure = insecure_tls + client_config.tls.insecure = insecure config_path = ClientConfigV1Alpha1.save(client_config, out) # If this is the only client config, set it as default if out is None and len(ClientConfigV1Alpha1.list().items) == 1: @@ -108,7 +108,7 @@ async def import_client( @opt_namespace @opt_kubeconfig @opt_context -@opt_insecure_tls +@opt_insecure @opt_output_path_only @opt_nointeractive @blocking @@ -118,7 +118,7 @@ async def import_exporter( out: Optional[str], kubeconfig: Optional[str], context: Optional[str], - insecure_tls: bool, + insecure: bool, output: PathOutputType, nointeractive: bool, ): @@ -130,12 +130,12 @@ async def import_exporter( else: raise click.ClickException(f'An exporter with the name "{name}" already exists') try: - confirm_insecure_tls(insecure_tls, nointeractive) + confirm_insecure(insecure, nointeractive) async with ExportersV1Alpha1Api(namespace, kubeconfig, context) as api: if output is None: click.echo("Fetching exporter credentials from cluster") exporter_config = await api.get_exporter_config(name) - exporter_config.tls.insecure = insecure_tls + exporter_config.tls.insecure = insecure config_path = ExporterConfigV1Alpha1.save(exporter_config, out) if output is None: click.echo(f"Exporter configuration successfully saved to {config_path}") diff --git a/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py b/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py index 0bac2f40d..c1dfe534a 100644 --- a/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py +++ b/python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res_test.py @@ -71,21 +71,21 @@ def test_import_client(_load_kube_config_mock, get_client_config_mock: AsyncMock get_client_config_mock.return_value = INSECURE_TLS_CLIENT_CONFIG # Save with prompts accept insecure = Y - result = runner.invoke(import_res, ["client", CLIENT_NAME, "--insecure-tls"], input="Y\nY\n") + result = runner.invoke(import_res, ["client", CLIENT_NAME, "--insecure"], input="Y\nY\n") assert result.exit_code == 0 assert "Client configuration successfully saved" in result.output save_client_config_mock.assert_called_once_with(INSECURE_TLS_CLIENT_CONFIG, None) save_client_config_mock.reset_mock() # Save with prompts no interactive prompts and insecure tls cert - result = runner.invoke(import_res, ["client", CLIENT_NAME, "--nointeractive", "--insecure-tls"]) + result = runner.invoke(import_res, ["client", CLIENT_NAME, "--nointeractive", "--insecure"]) assert result.exit_code == 0 assert "Client configuration successfully saved" in result.output save_client_config_mock.assert_called_once_with(INSECURE_TLS_CLIENT_CONFIG, None) save_client_config_mock.reset_mock() # Save with prompts accept insecure = N - result = runner.invoke(import_res, ["client", CLIENT_NAME, "--insecure-tls"], input="n\n") + result = runner.invoke(import_res, ["client", CLIENT_NAME, "--insecure"], input="n\n") assert result.exit_code == 1 assert "Aborted" in result.output save_client_config_mock.assert_not_called() @@ -168,14 +168,14 @@ def test_import_exporter(_load_kube_config_mock, _get_exporter_config_mock, save _get_exporter_config_mock.return_value = INSECURE_TLS_EXPORTER_CONFIG # Save with prompts accept insecure = Y - result = runner.invoke(import_res, ["exporter", EXPORTER_NAME, "--insecure-tls"], input="Y\n") + result = runner.invoke(import_res, ["exporter", EXPORTER_NAME, "--insecure"], input="Y\n") assert result.exit_code == 0 assert "Exporter configuration successfully saved" in result.output save_exporter_config_mock.assert_called_once_with(INSECURE_TLS_EXPORTER_CONFIG, None) save_exporter_config_mock.reset_mock() # Save with prompts accept insecure = N - result = runner.invoke(import_res, ["exporter", EXPORTER_NAME, "--insecure-tls"], input="n\n") + result = runner.invoke(import_res, ["exporter", EXPORTER_NAME, "--insecure"], input="n\n") assert result.exit_code == 1 assert "Aborted" in result.output save_exporter_config_mock.assert_not_called() diff --git a/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py b/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py index 360c9876e..114821a55 100644 --- a/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py +++ b/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py @@ -88,59 +88,27 @@ def _opt_labels_callback(ctx, param, value): callback=_opt_labels_callback, ) -_DEPRECATED_TLS_FLAG_WARNING = ( - "WARNING: --insecure-tls-config is deprecated and will be removed in a future release. " - "Use --insecure-tls instead." +opt_insecure = click.option( + "--insecure", + is_flag=True, + default=False, + help="Disable TLS verification and allow insecure connections", ) +opt_insecure_tls = opt_insecure +opt_insecure_tls_config = opt_insecure -def _insecure_tls_deprecated_callback(ctx, param, value): - if value: - click.echo(_DEPRECATED_TLS_FLAG_WARNING, err=True) - ctx.params["insecure_tls"] = True - return value - - -def opt_insecure_tls(func): - func = click.option( - "--insecure-tls-config", - "insecure_tls_config_deprecated", - is_flag=True, - default=False, - hidden=True, - expose_value=False, - callback=_insecure_tls_deprecated_callback, - is_eager=True, - )(func) - func = click.option( - "--insecure-tls", - "insecure_tls", - is_flag=True, - default=False, - help="Disable endpoint TLS verification. This is insecure and should only be used for testing purposes", - )(func) - return func - - -opt_insecure_tls_config = opt_insecure_tls - - -def confirm_insecure_tls(insecure_tls: bool, nointeractive: bool): - """Confirm if insecure TLS is enabled and user wants to continue. - - Args: - insecure_tls (bool): Insecure TLS flag requested by the user. - nointeractive (bool): This flag is set to True if the command is run in non-interactive mode. - Raises: - click.Abort: Abort the command if user does not want to continue. - """ - if nointeractive is False and insecure_tls: - if not click.confirm("Insecure TLS config is enabled. Are you sure you want to continue?"): +def confirm_insecure(insecure: bool, nointeractive: bool): + if nointeractive is False and insecure: + if not click.confirm("Insecure mode is enabled. TLS verification will be disabled. Continue?"): click.echo("Aborting.") raise click.Abort() +confirm_insecure_tls = confirm_insecure + + class OutputMode(str): JSON = "json" YAML = "yaml" diff --git a/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt_test.py b/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt_test.py index daf07fcab..9ef09db58 100644 --- a/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt_test.py +++ b/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt_test.py @@ -5,7 +5,7 @@ import click from click.testing import CliRunner -from jumpstarter_cli_common.opt import SourcePrefixFormatter, opt_insecure_tls +from jumpstarter_cli_common.opt import SourcePrefixFormatter, opt_insecure class TestSourcePrefixFormatter: @@ -81,41 +81,26 @@ def test_prefix_omitted_on_consecutive_same_source(self) -> None: assert "[different.source]" in formatted3 -def _make_insecure_tls_command(): +def _make_insecure_command(): @click.command() - @opt_insecure_tls - def cmd(insecure_tls: bool): - click.echo(f"insecure_tls={insecure_tls}") + @opt_insecure + def cmd(insecure: bool): + click.echo(f"insecure={insecure}") return cmd -class TestInsecureTlsOption: - def test_insecure_tls_flag_is_accepted(self) -> None: +class TestInsecureOption: + def test_insecure_flag_is_accepted(self) -> None: runner = CliRunner() - cmd = _make_insecure_tls_command() - result = runner.invoke(cmd, ["--insecure-tls"]) + cmd = _make_insecure_command() + result = runner.invoke(cmd, ["--insecure"]) assert result.exit_code == 0 - assert "insecure_tls=True" in result.output + assert "insecure=True" in result.output - def test_insecure_tls_flag_defaults_to_false(self) -> None: + def test_insecure_flag_defaults_to_false(self) -> None: runner = CliRunner() - cmd = _make_insecure_tls_command() + cmd = _make_insecure_command() result = runner.invoke(cmd, []) assert result.exit_code == 0 - assert "insecure_tls=False" in result.output - - def test_deprecated_insecure_tls_config_still_works(self) -> None: - runner = CliRunner() - cmd = _make_insecure_tls_command() - result = runner.invoke(cmd, ["--insecure-tls-config"]) - assert result.exit_code == 0 - assert "insecure_tls=True" in result.output - - def test_deprecated_insecure_tls_config_emits_warning(self) -> None: - runner = CliRunner() - cmd = _make_insecure_tls_command() - result = runner.invoke(cmd, ["--insecure-tls-config"]) - assert result.exit_code == 0 - assert "deprecated" in result.output.lower() - assert "--insecure-tls" in result.output + assert "insecure=False" in result.output diff --git a/python/packages/jumpstarter-cli/jumpstarter_cli/login.py b/python/packages/jumpstarter-cli/jumpstarter_cli/login.py index ce82eb7f4..45254d5fc 100644 --- a/python/packages/jumpstarter-cli/jumpstarter_cli/login.py +++ b/python/packages/jumpstarter-cli/jumpstarter_cli/login.py @@ -10,8 +10,8 @@ from jumpstarter_cli_common.exceptions import handle_exceptions from jumpstarter_cli_common.oidc import Config, decode_jwt_issuer, opt_oidc from jumpstarter_cli_common.opt import ( - confirm_insecure_tls, - opt_insecure_tls, + confirm_insecure, + opt_insecure, opt_nointeractive, ) @@ -56,37 +56,16 @@ def _validate_auth_config_payload(payload: Any, source_url: str) -> dict[str, An async def fetch_auth_config( login_endpoint: str, - insecure_tls: bool = False, - use_http: bool = False, + insecure: bool = False, ) -> dict[str, Any]: - """Fetch authentication configuration from the login endpoint. - - Args: - login_endpoint: The login endpoint URL (e.g., login.example.com or https://login.example.com) - insecure_tls: Skip TLS certificate verification for HTTPS connections - use_http: Use HTTP instead of HTTPS (for local testing) - - Returns: - Dictionary containing: - - grpcEndpoint: The gRPC controller endpoint - - routerEndpoint: The router endpoint (optional) - - namespace: Default namespace for clients - - caBundle: base64-encoded PEM CA certificate (optional) - - oidc: List of OIDC provider configurations (optional) - """ - # Ensure the URL has a scheme if not login_endpoint.startswith(("http://", "https://")): - scheme = "http" if use_http else "https" + scheme = "http" if insecure else "https" login_endpoint = f"{scheme}://{login_endpoint}" _validate_login_endpoint_url(login_endpoint, allow_http=use_http) url = f"{login_endpoint.rstrip('/')}/v1/auth/config" - - # Configure SSL context: False disables verification, True enables it - ssl_context: ssl.SSLContext | bool = False if insecure_tls else True - - # Use a timeout to prevent the CLI from hanging indefinitely + ssl_context: ssl.SSLContext | bool = False if insecure else True timeout = aiohttp.ClientTimeout(total=_HTTP_TIMEOUT_SECONDS) try: @@ -159,19 +138,7 @@ def parse_login_argument(login_arg: str) -> tuple[str | None, str]: "--unsafe", is_flag=True, help="Should all driver client packages be allowed to load (UNSAFE!).", default=None ) # end client specific -@opt_insecure_tls -@click.option( - "--insecure-login-tls", - is_flag=True, - help="Skip TLS certificate verification when fetching config from login endpoint.", - default=False, -) -@click.option( - "--insecure-login-http", - is_flag=True, - help="Use HTTP instead of HTTPS when fetching config from login endpoint (for local testing).", - default=False, -) +@opt_insecure @opt_nointeractive @opt_config(allow_missing=True) @handle_exceptions @@ -191,9 +158,7 @@ async def login( # noqa: C901 callback_port: int | None, offline_access: bool, unsafe, - insecure_tls: bool, - insecure_login_tls: bool, - insecure_login_http: bool, + insecure: bool, nointeractive: bool, allow, ): @@ -212,9 +177,7 @@ async def login( # noqa: C901 - Default namespace """ - confirm_insecure_tls(insecure_tls, nointeractive) - if insecure_login_http and insecure_login_tls: - raise click.UsageError("--insecure-login-http and --insecure-login-tls cannot be used together.") + confirm_insecure(insecure, nointeractive) # Handle simplified login format: [client-name@]login.endpoint.com ca_bundle = None @@ -231,8 +194,7 @@ async def login( # noqa: C901 click.echo(f"Fetching configuration from {login_endpoint}...") auth_config = await fetch_auth_config( login_endpoint, - insecure_tls=insecure_login_tls or insecure_tls, - use_http=insecure_login_http, + insecure=insecure, ) # Use fetched values if not explicitly provided @@ -305,7 +267,7 @@ async def login( # noqa: C901 ) # Build TLS config with CA bundle if available - tls_config = TLSConfigV1Alpha1(insecure=insecure_tls, ca=ca_bundle or "") + tls_config = TLSConfigV1Alpha1(insecure=insecure, ca=ca_bundle or "") if kind.startswith("client"): config = ClientConfigV1Alpha1( From 966213c6f716e850b556d8e7d851b1b343931a0a Mon Sep 17 00:00:00 2001 From: Paul Wallrabe Date: Tue, 17 Mar 2026 20:50:56 +0100 Subject: [PATCH 05/13] docs: update guides and READMEs to use --insecure flag Co-Authored-By: Claude Opus 4.6 --- .../configuration/authentication.md | 14 +++++++------- .../guides/setup-distributed-mode.md | 6 +++--- .../packages/jumpstarter-driver-flashers/README.md | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/python/docs/source/getting-started/configuration/authentication.md b/python/docs/source/getting-started/configuration/authentication.md index 5afc17f02..524e51455 100644 --- a/python/docs/source/getting-started/configuration/authentication.md +++ b/python/docs/source/getting-started/configuration/authentication.md @@ -52,14 +52,14 @@ prefixed with "keycloak:" (e.g., keycloak:example-user). prefix usernames with `keycloak:` as configured in the claim mappings: ```console -$ jmp admin create client test-client --insecure-tls --oidc-username keycloak:developer-1 +$ jmp admin create client test-client --insecure --oidc-username keycloak:developer-1 ``` 4. Instruct users to log in with: ```console $ jmp login --client \ - --insecure-tls \ + --insecure \ --endpoint \ --namespace --name \ --issuer https:///realms/ @@ -69,7 +69,7 @@ For non-interactive login, add username and password: ```console $ jmp login --client [other parameters] \ - --insecure-tls \ + --insecure \ --username \ --password ``` @@ -84,7 +84,7 @@ For exporters, use similar login command but with the `--exporter` flag: ```console $ jmp login --exporter \ - --insecure-tls \ + --insecure \ --endpoint \ --namespace --name \ --issuer https:///realms/ @@ -197,7 +197,7 @@ spec: ```console $ jmp admin create exporter test-exporter --label foo=bar \ - --insecure-tls \ + --insecure \ --oidc-username dex:system:serviceaccount:default:test-service-account ``` @@ -207,7 +207,7 @@ For clients: ```console $ jmp login --client \ - --insecure-tls \ + --insecure \ --endpoint \ --namespace --name \ --issuer https://dex.dex.svc.cluster.local:5556 \ @@ -219,7 +219,7 @@ For exporters: ```console $ jmp login --exporter \ - --insecure-tls \ + --insecure \ --endpoint \ --namespace --name \ --issuer https://dex.dex.svc.cluster.local:5556 \ diff --git a/python/docs/source/getting-started/guides/setup-distributed-mode.md b/python/docs/source/getting-started/guides/setup-distributed-mode.md index a9073447d..c0e3d794d 100644 --- a/python/docs/source/getting-started/guides/setup-distributed-mode.md +++ b/python/docs/source/getting-started/guides/setup-distributed-mode.md @@ -7,7 +7,7 @@ controller service, configuring drivers, and running the exporter. The jumpstarter-controller endpoints are secured by TLS. However, in release 0.7.x, the certificates are self-signed and rotated on every restart. This means the client will not be able to verify the server certificate. To bypass this, you should use the -`--insecure-tls` flag when creating clients and exporters. This issue will be +`--insecure` flag when creating clients and exporters. This issue will be resolved in the next release. See [issue #72](https://github.com/jumpstarter-dev/jumpstarter/issues/72) for more details. Alternatively, you can configure the ingress/route in reencrypt mode with your own key and certificate. @@ -40,7 +40,7 @@ Run this command to create an exporter named `example-distributed` and save the configuration locally: ```console -$ jmp admin create exporter example-distributed --label foo=bar --save --insecure-tls +$ jmp admin create exporter example-distributed --label foo=bar --save --insecure ``` After creating the exporter, find the new configuration file at @@ -88,7 +88,7 @@ development purposes, and saves the configuration locally in `${HOME}/.config/jumpstarter/clients/`: ```console -$ jmp admin create client hello --save --unsafe --insecure-tls +$ jmp admin create client hello --save --unsafe --insecure ``` ### Spawn an Exporter Shell diff --git a/python/packages/jumpstarter-driver-flashers/README.md b/python/packages/jumpstarter-driver-flashers/README.md index 0e725f3f3..9e8f08d6e 100644 --- a/python/packages/jumpstarter-driver-flashers/README.md +++ b/python/packages/jumpstarter-driver-flashers/README.md @@ -147,7 +147,7 @@ Options: --force-exporter-http Force use of exporter HTTP --force-flash-bundle TEXT Force use of a specific flasher OCI bundle --cacert FILE CA certificate to use for HTTPS - --insecure-tls Skip TLS certificate verification + --insecure Disable TLS verification and allow insecure connections --header TEXT Custom HTTP header in 'Key: Value' format --bearer TEXT Bearer token for HTTP authentication --retries INTEGER Number of retry attempts for flash operation From ae1ae8c699f1cf37941ef44e80dc5f696a4e6808 Mon Sep 17 00:00:00 2001 From: Paul Wallrabe Date: Tue, 17 Mar 2026 21:15:49 +0100 Subject: [PATCH 06/13] fix: update e2e test to use --insecure flag The e2e login test still used the removed --insecure-login-http flag, causing 5 cascading test failures in CI. Co-Authored-By: Claude Opus 4.6 --- e2e/tests.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/tests.bats b/e2e/tests.bats index 715473160..cce574124 100644 --- a/e2e/tests.bats +++ b/e2e/tests.bats @@ -207,7 +207,7 @@ wait_for_exporter() { jmp config client delete test-client-oidc - run jmp login test-client-oidc@${LOGIN_ENDPOINT} --insecure-login-http \ + run jmp login test-client-oidc@${LOGIN_ENDPOINT} --insecure \ --username test-client-oidc@example.com --password password --unsafe assert_success From 41b40572f084fdff5d90174e4e836f0b9f5da689 Mon Sep 17 00:00:00 2001 From: Paul Wallrabe Date: Tue, 17 Mar 2026 21:51:17 +0100 Subject: [PATCH 07/13] fix: add --nointeractive to e2e login test to prevent prompt blocking CI Co-Authored-By: Claude Opus 4.6 --- e2e/tests.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/tests.bats b/e2e/tests.bats index cce574124..9a3424d75 100644 --- a/e2e/tests.bats +++ b/e2e/tests.bats @@ -207,7 +207,7 @@ wait_for_exporter() { jmp config client delete test-client-oidc - run jmp login test-client-oidc@${LOGIN_ENDPOINT} --insecure \ + run jmp login test-client-oidc@${LOGIN_ENDPOINT} --insecure --nointeractive \ --username test-client-oidc@example.com --password password --unsafe assert_success From 5c3243fffe22a84babaef19e4afc01d0145925ec Mon Sep 17 00:00:00 2001 From: Paul Wallrabe Date: Wed, 18 Mar 2026 00:05:43 +0100 Subject: [PATCH 08/13] fix: require --insecure for explicit http:// login endpoints Co-Authored-By: Claude Opus 4.6 --- .../jumpstarter-cli/jumpstarter_cli/login.py | 3 +++ .../jumpstarter_cli/login_test.py | 27 ++++++++----------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/python/packages/jumpstarter-cli/jumpstarter_cli/login.py b/python/packages/jumpstarter-cli/jumpstarter_cli/login.py index 45254d5fc..c1bbe2404 100644 --- a/python/packages/jumpstarter-cli/jumpstarter_cli/login.py +++ b/python/packages/jumpstarter-cli/jumpstarter_cli/login.py @@ -58,6 +58,9 @@ async def fetch_auth_config( login_endpoint: str, insecure: bool = False, ) -> dict[str, Any]: + if login_endpoint.startswith("http://") and not insecure: + raise click.UsageError("HTTP login endpoints require --insecure.") + if not login_endpoint.startswith(("http://", "https://")): scheme = "http" if insecure else "https" login_endpoint = f"{scheme}://{login_endpoint}" diff --git a/python/packages/jumpstarter-cli/jumpstarter_cli/login_test.py b/python/packages/jumpstarter-cli/jumpstarter_cli/login_test.py index 021f9b0e5..4716f4713 100644 --- a/python/packages/jumpstarter-cli/jumpstarter_cli/login_test.py +++ b/python/packages/jumpstarter-cli/jumpstarter_cli/login_test.py @@ -53,7 +53,7 @@ def test_validate_login_endpoint_url_rejects_unsupported_scheme() -> None: def test_validate_login_endpoint_url_rejects_http_without_explicit_opt_in() -> None: - with pytest.raises(click.ClickException, match="Use --insecure-login-http"): + with pytest.raises(click.ClickException, match="Use --insecure"): _validate_login_endpoint_url("http://login.example.com") @@ -150,22 +150,17 @@ async def fake_fetch_auth_config(*args, **kwargs): assert "TLS certificate verification failed" in result.output -def test_login_cli_rejects_conflicting_insecure_flags() -> None: - runner = CliRunner() - result = runner.invoke( - jmp, - [ - "login", - "login.example.com", - "--client-config", - "/tmp/nonexistent-client.yaml", - "--insecure-login-http", - "--insecure-login-tls", - ], - ) +@pytest.mark.asyncio +async def test_fetch_auth_config_rejects_http_without_insecure(): + with pytest.raises(click.UsageError, match="--insecure"): + await fetch_auth_config("http://login.example.com", insecure=False) - assert result.exit_code != 0 - assert "--insecure-login-http and --insecure-login-tls cannot be used together" in result.output + +@pytest.mark.asyncio +async def test_fetch_auth_config_allows_http_with_insecure(): + with pytest.raises(Exception) as exc_info: + await fetch_auth_config("http://login.example.com", insecure=True) + assert not isinstance(exc_info.value, click.UsageError) def test_login_maps_ssl_cert_error_during_oidc_to_friendly_message(monkeypatch) -> None: From 2e4fffa2baf7e7cd8759ded978a1498a579e6d2d Mon Sep 17 00:00:00 2001 From: Paul Wallrabe Date: Wed, 18 Mar 2026 08:31:39 +0100 Subject: [PATCH 09/13] fix: use login-specific insecure warning and mock aiohttp in tests - Replace generic confirm_insecure with login-specific prompt that warns about both TLS disabled and plain HTTP usage - Mock aiohttp.ClientSession in test to avoid network dependency Co-Authored-By: Claude Opus 4.6 --- .../jumpstarter-cli/jumpstarter_cli/login.py | 14 +++++----- .../jumpstarter_cli/login_test.py | 26 ++++++++++++++++--- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/python/packages/jumpstarter-cli/jumpstarter_cli/login.py b/python/packages/jumpstarter-cli/jumpstarter_cli/login.py index c1bbe2404..1608c9753 100644 --- a/python/packages/jumpstarter-cli/jumpstarter_cli/login.py +++ b/python/packages/jumpstarter-cli/jumpstarter_cli/login.py @@ -9,11 +9,7 @@ from jumpstarter_cli_common.config import opt_config from jumpstarter_cli_common.exceptions import handle_exceptions from jumpstarter_cli_common.oidc import Config, decode_jwt_issuer, opt_oidc -from jumpstarter_cli_common.opt import ( - confirm_insecure, - opt_insecure, - opt_nointeractive, -) +from jumpstarter_cli_common.opt import opt_insecure, opt_nointeractive from jumpstarter.common.exceptions import ReauthenticationFailed from jumpstarter.config.client import ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers @@ -180,7 +176,13 @@ async def login( # noqa: C901 - Default namespace """ - confirm_insecure(insecure, nointeractive) + if nointeractive is False and insecure: + if not click.confirm( + "Insecure mode is enabled. TLS verification will be disabled " + "and plain HTTP may be used for login. Continue?" + ): + click.echo("Aborting.") + raise click.Abort() # Handle simplified login format: [client-name@]login.endpoint.com ca_bundle = None diff --git a/python/packages/jumpstarter-cli/jumpstarter_cli/login_test.py b/python/packages/jumpstarter-cli/jumpstarter_cli/login_test.py index 4716f4713..24af16dbc 100644 --- a/python/packages/jumpstarter-cli/jumpstarter_cli/login_test.py +++ b/python/packages/jumpstarter-cli/jumpstarter_cli/login_test.py @@ -1,6 +1,7 @@ import asyncio import json import ssl +from unittest.mock import AsyncMock, MagicMock, patch import click import pytest @@ -158,9 +159,28 @@ async def test_fetch_auth_config_rejects_http_without_insecure(): @pytest.mark.asyncio async def test_fetch_auth_config_allows_http_with_insecure(): - with pytest.raises(Exception) as exc_info: - await fetch_auth_config("http://login.example.com", insecure=True) - assert not isinstance(exc_info.value, click.UsageError) + mock_response = MagicMock() + mock_response.status = 200 + mock_response.json = AsyncMock(return_value={"grpcEndpoint": "grpc.example.com"}) + + mock_get_cm = MagicMock() + mock_get_cm.__aenter__ = AsyncMock(return_value=mock_response) + mock_get_cm.__aexit__ = AsyncMock(return_value=False) + + mock_session = MagicMock() + mock_session.get = MagicMock(return_value=mock_get_cm) + + mock_client_cm = MagicMock() + mock_client_cm.__aenter__ = AsyncMock(return_value=mock_session) + mock_client_cm.__aexit__ = AsyncMock(return_value=False) + + with patch("aiohttp.ClientSession", return_value=mock_client_cm): + result = await fetch_auth_config("http://login.example.com", insecure=True) + + mock_session.get.assert_called_once() + call_url = mock_session.get.call_args[0][0] + assert "http://login.example.com" in call_url + assert result["grpcEndpoint"] == "grpc.example.com" def test_login_maps_ssl_cert_error_during_oidc_to_friendly_message(monkeypatch) -> None: From 944d13270c7d1a8c7536c811b99d2d2ac3be8359 Mon Sep 17 00:00:00 2001 From: Paul Wallrabe Date: Wed, 18 Mar 2026 08:34:18 +0100 Subject: [PATCH 10/13] fix: update --insecure flag description to mention plain HTTP Co-Authored-By: Claude Opus 4.6 --- .../jumpstarter-cli-common/jumpstarter_cli_common/opt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py b/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py index 114821a55..ebace9205 100644 --- a/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py +++ b/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py @@ -92,7 +92,7 @@ def _opt_labels_callback(ctx, param, value): "--insecure", is_flag=True, default=False, - help="Disable TLS verification and allow insecure connections", + help="Disable TLS verification and allow insecure connections, including plain HTTP", ) opt_insecure_tls = opt_insecure From 33c700117f3ff20baa1e01fc54b31536878e5c8b Mon Sep 17 00:00:00 2001 From: Paul Wallrabe Date: Wed, 18 Mar 2026 08:51:29 +0100 Subject: [PATCH 11/13] fix: update confirm_insecure prompt to mention plain HTTP and reuse in login Co-Authored-By: Claude Opus 4.6 --- .../jumpstarter_cli_common/opt.py | 5 ++++- .../packages/jumpstarter-cli/jumpstarter_cli/login.py | 10 ++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py b/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py index ebace9205..0fa811746 100644 --- a/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py +++ b/python/packages/jumpstarter-cli-common/jumpstarter_cli_common/opt.py @@ -101,7 +101,10 @@ def _opt_labels_callback(ctx, param, value): def confirm_insecure(insecure: bool, nointeractive: bool): if nointeractive is False and insecure: - if not click.confirm("Insecure mode is enabled. TLS verification will be disabled. Continue?"): + if not click.confirm( + "Insecure mode is enabled. TLS verification will be disabled" + " and plain HTTP may be used. Continue?" + ): click.echo("Aborting.") raise click.Abort() diff --git a/python/packages/jumpstarter-cli/jumpstarter_cli/login.py b/python/packages/jumpstarter-cli/jumpstarter_cli/login.py index 1608c9753..67b68f122 100644 --- a/python/packages/jumpstarter-cli/jumpstarter_cli/login.py +++ b/python/packages/jumpstarter-cli/jumpstarter_cli/login.py @@ -9,7 +9,7 @@ from jumpstarter_cli_common.config import opt_config from jumpstarter_cli_common.exceptions import handle_exceptions from jumpstarter_cli_common.oidc import Config, decode_jwt_issuer, opt_oidc -from jumpstarter_cli_common.opt import opt_insecure, opt_nointeractive +from jumpstarter_cli_common.opt import confirm_insecure, opt_insecure, opt_nointeractive from jumpstarter.common.exceptions import ReauthenticationFailed from jumpstarter.config.client import ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers @@ -176,13 +176,7 @@ async def login( # noqa: C901 - Default namespace """ - if nointeractive is False and insecure: - if not click.confirm( - "Insecure mode is enabled. TLS verification will be disabled " - "and plain HTTP may be used for login. Continue?" - ): - click.echo("Aborting.") - raise click.Abort() + confirm_insecure(insecure, nointeractive) # Handle simplified login format: [client-name@]login.endpoint.com ca_bundle = None From 2d9a1119c9e249b4ab93bb1b76123223c891aea9 Mon Sep 17 00:00:00 2001 From: Paul Wallrabe Date: Wed, 18 Mar 2026 12:46:11 +0100 Subject: [PATCH 12/13] fix: use insecure instead of undefined use_http in fetch_auth_config Co-Authored-By: Claude Opus 4.6 --- python/packages/jumpstarter-cli/jumpstarter_cli/login.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/packages/jumpstarter-cli/jumpstarter_cli/login.py b/python/packages/jumpstarter-cli/jumpstarter_cli/login.py index 67b68f122..7d0a7cb7d 100644 --- a/python/packages/jumpstarter-cli/jumpstarter_cli/login.py +++ b/python/packages/jumpstarter-cli/jumpstarter_cli/login.py @@ -61,7 +61,7 @@ async def fetch_auth_config( scheme = "http" if insecure else "https" login_endpoint = f"{scheme}://{login_endpoint}" - _validate_login_endpoint_url(login_endpoint, allow_http=use_http) + _validate_login_endpoint_url(login_endpoint, allow_http=insecure) url = f"{login_endpoint.rstrip('/')}/v1/auth/config" ssl_context: ssl.SSLContext | bool = False if insecure else True From 212a3140bba7abdbc2f9e7a05ab397ee0fe87130 Mon Sep 17 00:00:00 2001 From: Paul Wallrabe Date: Fri, 20 Mar 2026 16:37:04 +0100 Subject: [PATCH 13/13] fix: add missing all_clients argument to test_get_leases_calls_list_leases Co-Authored-By: Claude Opus 4.6 --- python/packages/jumpstarter-cli/jumpstarter_cli/get_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/packages/jumpstarter-cli/jumpstarter_cli/get_test.py b/python/packages/jumpstarter-cli/jumpstarter_cli/get_test.py index 8f3c15cef..eeebfeb6e 100644 --- a/python/packages/jumpstarter-cli/jumpstarter_cli/get_test.py +++ b/python/packages/jumpstarter-cli/jumpstarter_cli/get_test.py @@ -241,7 +241,7 @@ def test_get_leases_calls_list_leases(self): with patch("jumpstarter_cli.get.model_print"): get_leases.callback.__wrapped__.__wrapped__( - config=config, selector=None, output="text", show_all=False + config=config, selector=None, output="text", show_all=False, all_clients=False ) config.list_leases.assert_called_once_with(filter=None, only_active=True)