diff --git a/README.md b/README.md index 3e4bbc7..f67820a 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,8 @@ with-connect -e MY_VAR=value -e ANOTHER_VAR=123 -- rsconnect deploy manifest . You can use this to override Connect server configuration by passing in `CONNECT_` prefixed variables, following https://docs.posit.co/connect/admin/appendix/configuration/#environment-variables. +with-connect reads Connect's service log from the container's output to tell when Connect has started, so it sets `CONNECT_LOGGING_SERVICELOG=STDOUT` and `CONNECT_LOGGING_SERVICELOGLEVEL=INFO`. These take precedence over the `[Logging]` section of a `--config` file. To override them anyway, pass them with `-e`. + If you need env vars that are useful for the command running after `--`, just set them in the environment from which you call `with-connect`: the command will inherit that environment. ### Start-Only Mode diff --git a/main.py b/main.py index 22ffbd2..6c3cb9a 100644 --- a/main.py +++ b/main.py @@ -228,6 +228,32 @@ def parse_image_spec(image: str) -> tuple[str, str, bool]: return (image, "latest", True) +def build_container_env(bootstrap_secret: str, env_vars: list[str] | None) -> dict[str, str]: + """ + Build the container environment: bootstrap and logging settings first, + then user-provided KEY=VALUE env vars, which override them. + + with-connect detects startup by reading the container's stdout for + "Starting HTTP server on", an INFO-level service log message. A --config + file that sends the service log to a file or raises its level above INFO + would hide that message, so force both here. CONNECT_* env vars take + precedence over the config file. + """ + container_env = { + # "CONNECT_TENSORFLOW_ENABLED": "false", + "CONNECT_BOOTSTRAP_ENABLED": "true", + "CONNECT_BOOTSTRAP_SECRETKEY": bootstrap_secret, + "CONNECT_LOGGING_SERVICELOG": "STDOUT", + "CONNECT_LOGGING_SERVICELOGLEVEL": "INFO", + } + if env_vars: + for env_var in env_vars: + if "=" in env_var: + key, value = env_var.split("=", 1) + container_env[key] = value + return container_env + + def get_docker_tag(version: str) -> tuple[str, str]: """ Convert a version string to the appropriate Docker image and tag. @@ -348,17 +374,7 @@ def main() -> int: ) ) - # Build container environment with bootstrap settings and user-provided env vars - container_env = { - # "CONNECT_TENSORFLOW_ENABLED": "false", - "CONNECT_BOOTSTRAP_ENABLED": "true", - "CONNECT_BOOTSTRAP_SECRETKEY": bootstrap_secret, - } - if args.env_vars: - for env_var in args.env_vars: - if "=" in env_var: - key, value = env_var.split("=", 1) - container_env[key] = value + container_env = build_container_env(bootstrap_secret, args.env_vars) run_kwargs = { "image": image_name, diff --git a/test_main.py b/test_main.py index 41525dc..8e8e0fc 100644 --- a/test_main.py +++ b/test_main.py @@ -131,6 +131,26 @@ def test_get_docker_tag_invalid_format(): assert main.get_docker_tag("custom-tag") == ("rstudio/rstudio-connect", "custom-tag") +def test_container_env_forces_service_log_to_stdout(): + # Startup detection reads "Starting HTTP server on" from stdout, so the + # service log must go there at INFO level whatever --config says. + env = main.build_container_env("secret", None) + assert env["CONNECT_BOOTSTRAP_ENABLED"] == "true" + assert env["CONNECT_BOOTSTRAP_SECRETKEY"] == "secret" + assert env["CONNECT_LOGGING_SERVICELOG"] == "STDOUT" + assert env["CONNECT_LOGGING_SERVICELOGLEVEL"] == "INFO" + + +def test_container_env_user_vars_override_defaults(): + env = main.build_container_env( + "secret", + ["CONNECT_LOGGING_SERVICELOG=STDERR", "MY_VAR=a=b", "IGNORED"], + ) + assert env["CONNECT_LOGGING_SERVICELOG"] == "STDERR" + assert env["MY_VAR"] == "a=b" + assert "IGNORED" not in env + + def test_force_amd64_legacy_rstudio_images(): # Legacy rstudio/* on Docker Hub and ghcr.io/rstudio/* mirrors are # amd64-only and need the pin. @@ -459,6 +479,12 @@ def test_post_bootstrap_raises_on_other_errors(): test_get_docker_tag_invalid_format() print("✓ test_get_docker_tag_invalid_format passed") + test_container_env_forces_service_log_to_stdout() + print("✓ test_container_env_forces_service_log_to_stdout passed") + + test_container_env_user_vars_override_defaults() + print("✓ test_container_env_user_vars_override_defaults passed") + test_extract_server_version() print("✓ test_extract_server_version passed")