Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 27 additions & 11 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
26 changes: 26 additions & 0 deletions test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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")

Expand Down
Loading