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
50 changes: 48 additions & 2 deletions dk-installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1737,9 +1737,10 @@ def execute(self, action, args):

def on_action_success(self, action, args):
cred_file_path = action.data_folder.joinpath(CREDENTIALS_FILE.format(args.prod))
protocol = "https" if args.ssl_cert_file and args.ssl_key_file else "http"
with CONSOLE.tee(cred_file_path) as console_tee:
for service, url_tpl in OBS_SERVICES_URLS:
console_tee(f"{service:>20}: {url_tpl.format('http://localhost', args.port)}")
console_tee(f"{service:>20}: {url_tpl.format(f'{protocol}://localhost', args.port)}")
console_tee("")
console_tee(f"Username: {self._user_data['username']}")
console_tee(f"Password: {self._user_data['password']}", skip_logging=True)
Expand Down Expand Up @@ -1769,13 +1770,20 @@ def execute(self, action, args):


class ObsCreateComposeFileStep(CreateComposeFileStepBase):
def pre_execute(self, action, args):
super().pre_execute(action, args)
if bool(args.ssl_cert_file) != bool(args.ssl_key_file):
CONSOLE.msg("Both --ssl-cert-file and --ssl-key-file must be provided to use SSL certificates.")
raise AbortAction

def get_compose_file_contents(self, action, args):
action.analytics.additional_properties["used_custom_image"] = any(
(
args.ui_image != OBS_DEF_UI_IMAGE,
args.be_image != OBS_DEF_BE_IMAGE,
)
)
action.analytics.additional_properties["used_custom_cert"] = bool(args.ssl_cert_file and args.ssl_key_file)
compose_file_content = textwrap.dedent(
"""
name: ${DK_OBSERVABILITY_COMPOSE_NAME:-}
Expand Down Expand Up @@ -1880,12 +1888,14 @@ def get_compose_file_contents(self, action, args):
condition: service_healthy
environment:
OBSERVABILITY_AUTH_METHOD: ${DK_OBSERVABILITY_AUTH_METHOD:-basic}
__SSL_UI_ENVIRONMENT__
links:
- "observability_backend:observability-api"
- "observability_backend:event-api"
- "observability_backend:agent-api"
ports:
- "${DK_OBSERVABILITY_HTTP_PORT:-8082}:8082"
__SSL_UI_VOLUMES__

networks:
datakitchen:
Expand Down Expand Up @@ -1913,6 +1923,28 @@ def get_compose_file_contents(self, action, args):
compose_file_content,
)

# Fill (or strip) the UI TLS placeholders. When a cert+key are provided,
# they are bind-mounted into the UI container and SSL_CERT_FILE/SSL_KEY_FILE
# are set so its nginx serves HTTPS; otherwise the UI stays on plain HTTP.
if args.ssl_cert_file and args.ssl_key_file:
compose_file_content = compose_file_content.replace(
" __SSL_UI_ENVIRONMENT__",
" SSL_CERT_FILE: /dk/ssl/cert.crt\n SSL_KEY_FILE: /dk/ssl/cert.key",
)
compose_file_content = compose_file_content.replace(
" __SSL_UI_VOLUMES__",
" volumes:\n"
f" - type: bind\n"
f" source: {args.ssl_cert_file}\n"
f" target: /dk/ssl/cert.crt\n"
f" - type: bind\n"
f" source: {args.ssl_key_file}\n"
f" target: /dk/ssl/cert.key",
)
else:
compose_file_content = compose_file_content.replace(" __SSL_UI_ENVIRONMENT__\n", "")
compose_file_content = compose_file_content.replace(" __SSL_UI_VOLUMES__\n", "")

return compose_file_content


Expand Down Expand Up @@ -1967,6 +1999,20 @@ def get_parser(self, sub_parsers):
default=OBS_DEF_UI_IMAGE,
help="Observability UI image to use for the install. Defaults to %(default)s",
)
parser.add_argument(
"--ssl-cert-file",
dest="ssl_cert_file",
action="store",
default=None,
help="Path to SSL certificate file. When provided together with --ssl-key-file, the UI serves HTTPS.",
)
parser.add_argument(
"--ssl-key-file",
dest="ssl_key_file",
action="store",
default=None,
help="Path to SSL key file. When provided together with --ssl-cert-file, the UI serves HTTPS.",
)


class DemoContainerAction(Action):
Expand Down Expand Up @@ -2219,7 +2265,7 @@ def get_credentials_from_compose_file(self, file_contents):
return username, password

def get_compose_file_contents(self, action, args):
action.analytics.additional_properties["used_custom_cert"] = args.ssl_cert_file and args.ssl_key_file
action.analytics.additional_properties["used_custom_cert"] = bool(args.ssl_cert_file and args.ssl_key_file)
action.analytics.additional_properties["used_custom_image"] = args.image != TESTGEN_DEFAULT_IMAGE

ssl_variables = (
Expand Down
44 changes: 43 additions & 1 deletion tests/test_obs_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import pytest

from tests.installer import ObsInstallAction, AbortAction, ComposeVerifyExistingInstallStep
from tests.installer import ObsInstallAction, AbortAction, ComposeVerifyExistingInstallStep, ObsCreateComposeFileStep


@pytest.fixture
Expand Down Expand Up @@ -72,3 +72,45 @@ def test_obs_existing_install_abort(obs_install_action, compose_path, stdout_moc
with patch.object(obs_install_action, "steps", new=[ComposeVerifyExistingInstallStep]):
with pytest.raises(AbortAction):
obs_install_action.execute()


@pytest.mark.integration
@pytest.mark.parametrize("arg_to_set", ("ssl_cert_file", "ssl_key_file"))
def test_obs_create_compose_file_abort_args(arg_to_set, obs_install_action, args_mock, console_msg_mock):
setattr(args_mock, arg_to_set, "/some/file/path")

with patch.object(obs_install_action, "steps", new=[ObsCreateComposeFileStep]):
with pytest.raises(AbortAction):
obs_install_action.execute()

console_msg_mock.assert_any_msg_contains(
"Both --ssl-cert-file and --ssl-key-file must be provided to use SSL certificates.",
)


@pytest.mark.integration
def test_obs_compose_contains_ssl(obs_install_action, args_mock, compose_path):
args_mock.ssl_cert_file = "/path/to/cert.crt"
args_mock.ssl_key_file = "/path/to/cert.key"

with patch.object(obs_install_action, "steps", new=[ObsCreateComposeFileStep]):
obs_install_action.execute()

contents = compose_path.read_text()
assert "SSL_CERT_FILE: /dk/ssl/cert.crt" in contents
assert "SSL_KEY_FILE: /dk/ssl/cert.key" in contents
assert "source: /path/to/cert.crt" in contents
assert "source: /path/to/cert.key" in contents
assert "__SSL_UI_ENVIRONMENT__" not in contents
assert "__SSL_UI_VOLUMES__" not in contents


@pytest.mark.integration
def test_obs_compose_without_ssl(obs_install_action, args_mock, compose_path):
with patch.object(obs_install_action, "steps", new=[ObsCreateComposeFileStep]):
obs_install_action.execute()

contents = compose_path.read_text()
assert "SSL_CERT_FILE" not in contents
assert "__SSL_UI_ENVIRONMENT__" not in contents
assert "__SSL_UI_VOLUMES__" not in contents
Loading