2021-03-26 21:57:05 -07:00
|
|
|
import os
|
2023-08-28 09:55:50 -07:00
|
|
|
import pathlib
|
|
|
|
import textwrap
|
2021-03-26 21:57:05 -07:00
|
|
|
from unittest import mock
|
|
|
|
|
2023-08-28 09:55:50 -07:00
|
|
|
import deepdiff
|
2021-03-26 21:57:05 -07:00
|
|
|
import expandvars
|
|
|
|
import pytest
|
2022-09-27 06:16:18 +00:00
|
|
|
import yaml
|
2021-03-26 21:57:05 -07:00
|
|
|
|
|
|
|
from datahub.configuration.common import ConfigurationError
|
2022-09-27 06:16:18 +00:00
|
|
|
from datahub.configuration.config_loader import (
|
2025-01-17 14:26:00 -08:00
|
|
|
EnvResolver,
|
2022-09-27 06:16:18 +00:00
|
|
|
list_referenced_env_variables,
|
|
|
|
load_config_file,
|
|
|
|
)
|
2021-03-26 21:57:05 -07:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
2022-09-27 06:16:18 +00:00
|
|
|
"filename,golden_config,env,referenced_env_vars",
|
2021-03-26 21:57:05 -07:00
|
|
|
[
|
|
|
|
(
|
|
|
|
# Basic YAML load
|
|
|
|
"tests/unit/config/basic.yml",
|
2023-08-28 09:55:50 -07:00
|
|
|
{
|
|
|
|
"foo": "bar",
|
|
|
|
"nested": {
|
|
|
|
"array": ["one", "two"],
|
|
|
|
"hi": "hello",
|
|
|
|
"numbers": {4: "four", 6: "six", "8": "eight"},
|
|
|
|
},
|
|
|
|
},
|
2021-03-26 21:57:05 -07:00
|
|
|
{},
|
2022-09-27 06:16:18 +00:00
|
|
|
set(),
|
2021-03-26 21:57:05 -07:00
|
|
|
),
|
|
|
|
(
|
|
|
|
# Basic TOML load
|
|
|
|
"tests/unit/config/basic.toml",
|
|
|
|
{"foo": "bar", "nested": {"array": ["one", "two"], "hi": "hello"}},
|
|
|
|
{},
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
# Variable expansion load
|
|
|
|
(
|
|
|
|
"tests/unit/config/simple_variable_expansion.yml",
|
|
|
|
{
|
|
|
|
"normal": "sa",
|
|
|
|
"path": "stuff2",
|
|
|
|
"server": "http://localhost:8080",
|
|
|
|
"working_dir": "stuff1",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"VAR1": "stuff1",
|
|
|
|
"VAR2": "stuff2",
|
|
|
|
},
|
2024-05-15 22:31:05 -07:00
|
|
|
{"VAR1", "UNSET_VAR3", "VAR2"},
|
2022-03-09 01:54:08 +05:30
|
|
|
),
|
|
|
|
(
|
|
|
|
"tests/unit/config/complex_variable_expansion.yml",
|
|
|
|
{
|
|
|
|
"foo": "bar",
|
|
|
|
"bar": "stuff1",
|
|
|
|
"nested": {
|
|
|
|
"hi": "hello",
|
|
|
|
"vanilla_val_1": "vanillaValue",
|
|
|
|
"vanilla_val_2": "vanilla$Value",
|
|
|
|
"vanilla_val_3": "vanilla${Value",
|
|
|
|
"vanilla_val_4": "vani}lla${Value",
|
|
|
|
"vanilla_val_5": "vanillaValue}",
|
|
|
|
"vanilla_val_6": "vanilla$Value}",
|
|
|
|
"password": "stuff2",
|
|
|
|
"password_1": "stuff3",
|
|
|
|
"password_2": "$VARNONEXISTENT",
|
|
|
|
"array": [
|
|
|
|
"one",
|
|
|
|
"two",
|
|
|
|
"stuff4",
|
|
|
|
"three$array",
|
|
|
|
[
|
|
|
|
"in_one",
|
|
|
|
"in_two",
|
|
|
|
"stuff5",
|
|
|
|
"test_urlstuff6",
|
|
|
|
"test_urlstuff7/action",
|
|
|
|
],
|
|
|
|
{
|
|
|
|
"inner_array": [
|
|
|
|
"in_ar_one",
|
|
|
|
"stuff8",
|
|
|
|
"test_url$vanillavar",
|
|
|
|
"test_urlstuff9vanillaVarstuff10",
|
2022-08-19 09:08:17 -07:00
|
|
|
"${VAR11}",
|
2022-03-09 01:54:08 +05:30
|
|
|
]
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"VAR1": "stuff1",
|
|
|
|
"VAR2": "stuff2",
|
|
|
|
"VAR3": "stuff3",
|
|
|
|
"VAR4": "stuff4",
|
|
|
|
"VAR5": "stuff5",
|
|
|
|
"VAR6": "stuff6",
|
|
|
|
"VAR7": "stuff7",
|
|
|
|
"VAR8": "stuff8",
|
|
|
|
"VAR9": "stuff9",
|
|
|
|
"VAR10": "stuff10",
|
|
|
|
"VAR11": "stuff11",
|
|
|
|
},
|
2024-05-15 22:31:05 -07:00
|
|
|
{
|
|
|
|
"VAR1",
|
|
|
|
"VAR2",
|
|
|
|
"VAR3",
|
|
|
|
"VAR4",
|
|
|
|
"VAR5",
|
|
|
|
"VAR6",
|
|
|
|
"VAR7",
|
|
|
|
"VAR8",
|
|
|
|
"VAR9",
|
|
|
|
"VAR10",
|
|
|
|
# VAR11 is escaped and hence not referenced
|
|
|
|
"VARNONEXISTENT",
|
|
|
|
},
|
2022-09-27 06:16:18 +00:00
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_load_success(pytestconfig, filename, golden_config, env, referenced_env_vars):
|
|
|
|
filepath = pytestconfig.rootpath / filename
|
|
|
|
|
|
|
|
if referenced_env_vars is not None:
|
|
|
|
raw_config = yaml.safe_load(filepath.read_text())
|
|
|
|
assert list_referenced_env_variables(raw_config) == referenced_env_vars
|
|
|
|
|
|
|
|
with mock.patch.dict(os.environ, env):
|
2023-12-04 14:31:58 -05:00
|
|
|
loaded_config = load_config_file(filepath, resolve_env_vars=True)
|
2022-09-27 06:16:18 +00:00
|
|
|
assert loaded_config == golden_config
|
|
|
|
|
|
|
|
# TODO check referenced env vars
|
|
|
|
|
|
|
|
|
2025-01-17 14:26:00 -08:00
|
|
|
def test_load_strict_env_syntax() -> None:
|
|
|
|
config = {
|
|
|
|
"foo": "${BAR}",
|
|
|
|
"baz": "$BAZ",
|
|
|
|
"qux": "qux$QUX",
|
|
|
|
}
|
|
|
|
assert EnvResolver.list_referenced_variables(
|
|
|
|
config,
|
|
|
|
strict_env_syntax=True,
|
|
|
|
) == {"BAR"}
|
|
|
|
|
|
|
|
assert EnvResolver(
|
|
|
|
environ={
|
|
|
|
"BAR": "bar",
|
|
|
|
}
|
|
|
|
).resolve(config) == {
|
|
|
|
"foo": "bar",
|
|
|
|
"baz": "$BAZ",
|
|
|
|
"qux": "qux$QUX",
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-09-27 06:16:18 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"filename,env,error_type",
|
|
|
|
[
|
|
|
|
(
|
|
|
|
# Variable expansion error
|
|
|
|
"tests/unit/config/bad_variable_expansion.yml",
|
|
|
|
{},
|
|
|
|
expandvars.ParameterNullOrNotSet,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
# Missing file
|
|
|
|
"tests/unit/config/this_file_does_not_exist.yml",
|
|
|
|
{},
|
|
|
|
ConfigurationError,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
# Unknown extension
|
|
|
|
"tests/unit/config/bad_extension.whatevenisthis",
|
|
|
|
{},
|
|
|
|
ConfigurationError,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
# Variable expansion error
|
|
|
|
"tests/unit/config/bad_complex_variable_expansion.yml",
|
|
|
|
{},
|
|
|
|
expandvars.MissingClosingBrace,
|
2022-03-09 01:54:08 +05:30
|
|
|
),
|
2021-03-26 21:57:05 -07:00
|
|
|
],
|
|
|
|
)
|
2022-09-27 06:16:18 +00:00
|
|
|
def test_load_error(pytestconfig, filename, env, error_type):
|
2021-03-26 21:57:05 -07:00
|
|
|
filepath = pytestconfig.rootpath / filename
|
|
|
|
|
2025-04-23 15:55:46 +02:00
|
|
|
with mock.patch.dict(os.environ, env), pytest.raises(error_type):
|
|
|
|
_ = load_config_file(filepath)
|
2023-08-28 09:55:50 -07:00
|
|
|
|
|
|
|
|
|
|
|
def test_write_file_directive(pytestconfig):
|
|
|
|
filepath = pytestconfig.rootpath / "tests/unit/config/write_to_file_directive.yml"
|
|
|
|
|
|
|
|
fake_ssl_key = "my-secret-key-value"
|
|
|
|
|
|
|
|
with mock.patch.dict(os.environ, {"DATAHUB_SSL_KEY": fake_ssl_key}):
|
2023-12-04 14:31:58 -05:00
|
|
|
loaded_config = load_config_file(
|
|
|
|
filepath,
|
|
|
|
squirrel_original_config=False,
|
|
|
|
resolve_env_vars=True,
|
|
|
|
process_directives=True,
|
|
|
|
)
|
2023-08-28 09:55:50 -07:00
|
|
|
|
|
|
|
# Check that the rest of the dict is unmodified.
|
|
|
|
diff = deepdiff.DeepDiff(
|
|
|
|
loaded_config,
|
|
|
|
{
|
|
|
|
"foo": "bar",
|
|
|
|
"nested": {
|
|
|
|
"hi": "hello",
|
|
|
|
"another-key": "final-value",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
exclude_paths=[
|
|
|
|
"root['nested']['ssl_cert']",
|
|
|
|
"root['nested']['ssl_key']",
|
|
|
|
],
|
|
|
|
)
|
|
|
|
assert not diff
|
|
|
|
|
|
|
|
# Check that the ssl_cert was written to a file.
|
|
|
|
ssl_cert_path = loaded_config["nested"]["ssl_cert"]
|
|
|
|
assert (
|
|
|
|
pathlib.Path(ssl_cert_path).read_text()
|
|
|
|
== textwrap.dedent(
|
|
|
|
"""
|
|
|
|
-----BEGIN CERTIFICATE-----
|
|
|
|
thisisnotarealcert
|
|
|
|
-----END CERTIFICATE-----
|
|
|
|
"""
|
|
|
|
).lstrip()
|
|
|
|
)
|
|
|
|
|
|
|
|
# Check that the ssl_key was written to a file.
|
|
|
|
ssl_key_path = loaded_config["nested"]["ssl_key"]
|
|
|
|
assert pathlib.Path(ssl_key_path).read_text() == fake_ssl_key
|