diff --git a/src/stack/deploy/deployment_create.py b/src/stack/deploy/deployment_create.py index 212f381..9d7016b 100644 --- a/src/stack/deploy/deployment_create.py +++ b/src/stack/deploy/deployment_create.py @@ -273,6 +273,7 @@ def init_operation( # noqa: C901 output, map_ports_to_host, backup_targets=None, + clusterissuer_explicitly_set=False, ): spec_file_content = {"stack": stack, constants.deploy_to_key: deployer_type} if deployer_type in ["k8s", "k8s-kind"]: @@ -287,23 +288,6 @@ def init_operation( # noqa: C901 "WARN: --image-registry not specified: locally built images can only be deployed" " if they are published to a container registry the cluster can reach" ) - if http_proxy_targets: - routes = [] - for target in http_proxy_targets: - routes.append( - { - constants.path_key: target["path"], - constants.proxy_to_key: f"{target['service']}:{target['port']}", - } - ) - http_proxy = { - constants.host_name_key: http_proxy_fqdn, - constants.cluster_issuer_key: http_proxy_clusterissuer, - constants.routes_key: routes, - } - if constants.network_key not in spec_file_content: - spec_file_content[constants.network_key] = {} - spec_file_content[constants.network_key].update({constants.http_proxy_key: [http_proxy]}) else: # Check for --kube-config supplied for non-relevant deployer types if kube_config is not None: @@ -322,10 +306,14 @@ def init_operation( # noqa: C901 constants.host_name_key: http_proxy_fqdn, constants.routes_key: routes, } - if http_proxy_clusterissuer and deployer_type in ["k8s", "k8s-kind"]: - http_proxy[constants.cluster_issuer_key] = http_proxy_clusterissuer - else: - log_info("NOTE: http-cluster-issuer is only used when deploying to Kubernetes") + if deployer_type in ["k8s", "k8s-kind"]: + if http_proxy_clusterissuer: + http_proxy[constants.cluster_issuer_key] = http_proxy_clusterissuer + elif clusterissuer_explicitly_set: + # Only worth saying when the user actually asked for a cluster issuer: the + # option carries a default, so testing its value would flag every non-k8s + # deployment, which is the ordinary case and not something to report. + log_info("NOTE: --http-proxy-clusterissuer is only used when deploying to Kubernetes; ignoring it") if constants.network_key not in spec_file_content: spec_file_content[constants.network_key] = {} spec_file_content[constants.network_key].update({constants.http_proxy_key: [http_proxy]}) diff --git a/src/stack/init/init.py b/src/stack/init/init.py index 1815eab..ebd6760 100644 --- a/src/stack/init/init.py +++ b/src/stack/init/init.py @@ -17,6 +17,8 @@ import click import os +from click.core import ParameterSource + from stack import constants from stack.log import log_info, log_debug, log_warn from stack.config.util import get_config_setting @@ -114,7 +116,7 @@ def _output_checks(specs, deploy_to, http_proxy_fqdn_specified=False): @click.option( "--http-proxy-clusterissuer", required=False, - help="k8s http proxy hostname to use", + help="cert-manager cluster issuer to obtain the k8s http proxy certificate from", default=get_config_setting("http-proxy-clusterissuer", "letsencrypt-prod"), ) @click.option("--output", required=True, help="Write yaml spec file here") @@ -146,6 +148,11 @@ def command( if deploy_to is None: deploy_to = "compose" + # The cluster issuer option carries a default, so its value cannot tell us whether + # the user asked for one. Ask click where the value came from instead, so that we + # only report ignoring it when it was actually requested on the command line. + clusterissuer_explicitly_set = ctx.get_parameter_source("http_proxy_clusterissuer") == ParameterSource.COMMANDLINE + ctx.obj = create_deploy_context( global_options2(ctx), None, @@ -225,6 +232,7 @@ def http_prefix_for(stack): None, map_ports_to_host, backup_targets, + clusterissuer_explicitly_set, ) specs.append(spec) diff --git a/tests/unit/test_init_http_proxy.py b/tests/unit/test_init_http_proxy.py index 0e191c5..6fee796 100644 --- a/tests/unit/test_init_http_proxy.py +++ b/tests/unit/test_init_http_proxy.py @@ -21,6 +21,7 @@ FQDN_UNUSED_WARNING = "--http-proxy-fqdn was specified but there are no HTTP proxy targets" NO_PROXY_NOTE = "No HTTP proxy settings specified" +CLUSTERISSUER_IGNORED_NOTE = "--http-proxy-clusterissuer is only used when deploying to Kubernetes" def init_k8s(stack_dir, output_file, env, extra_args=None): @@ -38,6 +39,19 @@ def init_k8s(stack_dir, output_file, env, extra_args=None): return run_stack(args + (extra_args or []), env) +def init_compose(stack_dir, output_file, env, extra_args=None): + args = [ + "init", + "--stack", + str(stack_dir), + "--deploy-to", + "compose", + "--output", + str(output_file), + ] + return run_stack(args + (extra_args or []), env) + + def test_fqdn_without_targets_warns(minimal_stack, tmp_path, isolated_env): result = init_k8s(minimal_stack, tmp_path / "spec.yml", isolated_env, ["--http-proxy-fqdn", FQDN]) assert result.returncode == 0, result.stderr @@ -90,3 +104,62 @@ def test_fqdn_with_annotated_port(annotated_stack, tmp_path, isolated_env): proxies = spec["network"]["http-proxy"] assert proxies[0]["host-name"] == FQDN assert proxies[0]["routes"] == [{"path": "/", "proxy-to": "web:80"}] + + +# --------------------------------------------------------------------------- +# cluster issuer +# +# --http-proxy-clusterissuer carries a default, so the note about it being +# unused must key off whether the user asked for one, not off its value. +# --------------------------------------------------------------------------- + + +def test_compose_is_silent_about_an_unrequested_cluster_issuer(annotated_stack, tmp_path, isolated_env): + spec_file = tmp_path / "spec.yml" + result = init_compose(annotated_stack, spec_file, isolated_env, ["--http-proxy-fqdn", FQDN]) + assert result.returncode == 0, result.stderr + assert CLUSTERISSUER_IGNORED_NOTE not in result.stdout + result.stderr + + # The default must not leak into a spec that cannot use it. + spec = yaml.safe_load(spec_file.read_text()) + assert "cluster-issuer" not in spec["network"]["http-proxy"][0] + + +def test_compose_reports_an_explicitly_requested_cluster_issuer_is_ignored(annotated_stack, tmp_path, isolated_env): + spec_file = tmp_path / "spec.yml" + result = init_compose( + annotated_stack, + spec_file, + isolated_env, + ["--http-proxy-fqdn", FQDN, "--http-proxy-clusterissuer", "letsencrypt-staging"], + ) + assert result.returncode == 0, result.stderr + assert CLUSTERISSUER_IGNORED_NOTE in result.stdout + result.stderr + + spec = yaml.safe_load(spec_file.read_text()) + assert "cluster-issuer" not in spec["network"]["http-proxy"][0] + + +def test_k8s_keeps_the_default_cluster_issuer_without_comment(annotated_stack, tmp_path, isolated_env): + spec_file = tmp_path / "spec.yml" + result = init_k8s(annotated_stack, spec_file, isolated_env, ["--http-proxy-fqdn", FQDN]) + assert result.returncode == 0, result.stderr + assert CLUSTERISSUER_IGNORED_NOTE not in result.stdout + result.stderr + + spec = yaml.safe_load(spec_file.read_text()) + assert spec["network"]["http-proxy"][0]["cluster-issuer"] == "letsencrypt-prod" + + +def test_k8s_honours_an_explicit_cluster_issuer(annotated_stack, tmp_path, isolated_env): + spec_file = tmp_path / "spec.yml" + result = init_k8s( + annotated_stack, + spec_file, + isolated_env, + ["--http-proxy-fqdn", FQDN, "--http-proxy-clusterissuer", "letsencrypt-staging"], + ) + assert result.returncode == 0, result.stderr + assert CLUSTERISSUER_IGNORED_NOTE not in result.stdout + result.stderr + + spec = yaml.safe_load(spec_file.read_text()) + assert spec["network"]["http-proxy"][0]["cluster-issuer"] == "letsencrypt-staging"