Skip to content

Commit

Permalink
Move the root options to a Pydantic Model
Browse files Browse the repository at this point in the history
The long term is goal is to move all CLI configurations to models.
Currently attributes are slapped onto one big inherited object and make
it difficult to determine where something is coming from, and there is
significant coupling.

Moving the options into a base model allows for all validation and
conditional logic to be handled within the model, and then the model
can be passed to places where the configuration may be needed. This
will lessen the overuse of passing around big sets of kwargs and
taking out what is needed, and will ensure that the CLI options
are seamlessly available in all the different areas they are needed.

This starts with just the RootCommand, which takes the "global" or
top level options.
  • Loading branch information
ephur committed Jun 18, 2024
1 parent e03154a commit 1cf91a5
Show file tree
Hide file tree
Showing 20 changed files with 646 additions and 423 deletions.
2 changes: 1 addition & 1 deletion .isort.cfg
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[settings]
known_third_party = atlassian,boto3,botocore,click,deepdiff,google,hcl2,jinja2,mergedeep,moto,pydantic,pytest,yaml
known_third_party = atlassian,boto3,botocore,click,deepdiff,google,hcl2,jinja2,lark,mergedeep,moto,pydantic,pytest,yaml
profile = black
20 changes: 10 additions & 10 deletions tests/backends/test_gcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,25 +171,25 @@ def test_parse_gcs_items(self, gbasec, prefix, inval, outval, expected_raise):
assert gbasec.backend._parse_gcs_items(inval) == outval


def test_google_hcl(gbasec):
def test_google_hcl(gbasec, gcp_creds_file):
render = gbasec.backend.hcl("test")
expected_render = """ backend "gcs" {
expected_render = f""" backend "gcs" {{
bucket = "test_gcp_bucket"
prefix = "terraform/test-0002/test"
credentials = "/home/test/test-creds.json"
}"""
credentials = "{gcp_creds_file}"
}}"""
assert render == expected_render


def test_google_data_hcl(gbasec):
expected_render = """data "terraform_remote_state" "test" {
def test_google_data_hcl(gbasec, gcp_creds_file):
expected_render = f"""data "terraform_remote_state" "test" {{
backend = "gcs"
config = {
config = {{
bucket = "test_gcp_bucket"
prefix = "terraform/test-0002/test"
credentials = "/home/test/test-creds.json"
}
}"""
credentials = "{gcp_creds_file}"
}}
}}"""
render = []
render.append(gbasec.backend.data_hcl(["test", "test"]))
render.append(gbasec.backend.data_hcl(["test"]))
Expand Down
65 changes: 37 additions & 28 deletions tests/commands/test_root.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@
import os
import platform
from tempfile import TemporaryDirectory
from unittest.mock import patch

import pytest
from deepdiff import DeepDiff

import tfworker.commands.root
from tfworker.commands.root import ordered_config_load
from tfworker.constants import DEFAULT_CONFIG
from tfworker.types import CLIOptionsRoot


class TestMain:
Expand All @@ -36,26 +39,24 @@ def test_rc_add_args(self, rootc):
assert rc.args.a == 1
assert rc.args.b == "two"

def test_rc_init(self, rootc):
rc = tfworker.commands.root.RootCommand(args={"a": 1, "b": "two"})
assert rc.args.a == 1
assert rc.args.b == "two"

def test_rc_init_clean(self, rootc):
# by default clean should be true
rc = tfworker.commands.root.RootCommand()
rc = tfworker.commands.root.RootCommand(CLIOptionsRoot())
assert rc.clean is True

# if working_dir is passed, clean should be false
rc = tfworker.commands.root.RootCommand(args={"working_dir": "/tmp"})
random_working_dir = TemporaryDirectory()
rc = tfworker.commands.root.RootCommand(
CLIOptionsRoot(working_dir=random_working_dir.name)
)
assert rc.clean is False
if platform.system() == "Darwin":
assert str(rc.temp_dir) == "/private/tmp"
assert str(rc.temp_dir) == f"/private{random_working_dir.name}"
else:
assert str(rc.temp_dir) == "/tmp"
assert str(rc.temp_dir) == random_working_dir.name

# if clean is passed, it should be set to the value passed
rc = tfworker.commands.root.RootCommand(args={"clean": False})
rc = tfworker.commands.root.RootCommand(CLIOptionsRoot(clean=False))
assert rc.temp_dir is not None
assert rc.clean is False

Expand All @@ -64,7 +65,7 @@ def test_rc_init_clean(self, rootc):
tmpdir = TemporaryDirectory()
assert os.path.exists(tmpdir.name) is True
rc = tfworker.commands.root.RootCommand(
args={"clean": True, "working_dir": tmpdir.name}
CLIOptionsRoot(clean=True, working_dir=tmpdir.name)
)
assert rc.clean is True
if platform.system() == "Darwin":
Expand Down Expand Up @@ -94,12 +95,18 @@ def test_config_loader(self, rootc, capfd):
for k, v in expected_tf_vars.items():
assert terraform_config["terraform_vars"][k] == v

# a root command with no config should return None
emptyrc = tfworker.commands.root.RootCommand()
assert emptyrc.load_config() is None
# a root command with no config should attempt to load the default, but fail, and exit
with patch("os.path.exists") as mock_exists:
mock_exists.return_value = False
with pytest.raises(SystemExit) as e:
emptyrc = tfworker.commands.root.RootCommand(CLIOptionsRoot())
assert emptyrc.load_config() is None
mock_exists.assert_called_with(DEFAULT_CONFIG)

# an invalid path should raise an error
invalidrc = tfworker.commands.root.RootCommand({"config_file": "/tmp/invalid"})
invalidrc = tfworker.commands.root.RootCommand(
CLIOptionsRoot(config_file="/tmp/invalid")
)
with pytest.raises(SystemExit) as e:
invalidrc.load_config()
assert e.value.code == 1
Expand All @@ -108,26 +115,28 @@ def test_config_loader(self, rootc, capfd):

# a j2 template with invalid substitutions should raise an error
invalidrc = tfworker.commands.root.RootCommand(
{
"config_file": os.path.join(
os.path.dirname(__file__),
"..",
"fixtures",
"test_config_invalid_j2.yaml",
)
}
CLIOptionsRoot(
**{
"config_file": os.path.join(
os.path.dirname(__file__),
"..",
"fixtures",
"test_config_invalid_j2.yaml",
)
}
)
)
with pytest.raises(SystemExit) as e:
invalidrc.load_config()
assert e.value.code == 1
out, err = capfd.readouterr()
assert "invalid template" in out

def test_pullup_keys_edge(self):
rc = tfworker.commands.root.RootCommand()
assert rc.load_config() is None
assert rc._pullup_keys() is None
assert rc.providers_odict is None
# def test_pullup_keys_edge(self):
# rc = tfworker.commands.root.RootCommand()
# assert rc.load_config() is None
# assert rc._pullup_keys() is None
# assert rc.providers_odict is None

def test_get_config_var_dict(self):
config_vars = ["foo=bar", "this=that", "one=two"]
Expand Down
Loading

0 comments on commit 1cf91a5

Please sign in to comment.