diff --git a/README.md b/README.md index 307389f..db79f0f 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,17 @@ Example `tf.env`: AWS_DEFAULT_REGION=eu-west-1 ``` +### State file name + +By default, the Terraform state is stored at `terraform//main.tfstate` in the S3 +backend bucket. If a single repo contains multiple Terraform stacks (e.g. one per subdirectory), +set `TF_STATE_FILE_NAME` per stack so each stack writes to its own state file. The variable can +be set in the environment or via `tf.env`: + +``` +TF_STATE_FILE_NAME=environment-reporter.tfstate +``` + ## Development ### Prerequisites diff --git a/bin/tf b/bin/tf index 740078b..264abdf 100755 --- a/bin/tf +++ b/bin/tf @@ -95,10 +95,12 @@ class TfVarsFiles: class TfBackend: - def __init__(self, account_id: str, region: str, repo_name: str): + def __init__(self, account_id: str, region: str, repo_name: str, + state_file_name: str = "main.tfstate"): self._account_id = account_id self._region = region self._repo_name = repo_name + self._state_file_name = state_file_name @property def bucket(self) -> str: @@ -114,7 +116,7 @@ class TfBackend: @property def state_path(self) -> str: - return f"terraform/{self._repo_name}/main.tfstate" + return f"terraform/{self._repo_name}/{self._state_file_name}" def init_command(self, extra_args: list[str] | None = None) -> list[str]: command = [ @@ -198,8 +200,10 @@ class TfRunner: if not account_id: raise TfError("Cannot determine AWS account ID for backend config.") repo_name = TfBackend._get_repo_name() + state_file_name = os.environ.get("TF_STATE_FILE_NAME", "main.tfstate") return TfBackend( - account_id=account_id, region=context.region, repo_name=repo_name + account_id=account_id, region=context.region, repo_name=repo_name, + state_file_name=state_file_name, ) def _run_init(self, backend: TfBackend) -> None: diff --git a/tests/test_backend.py b/tests/test_backend.py index c057f38..c545dcb 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -54,6 +54,23 @@ def test_state_path_varies_by_repo(self): assert backend_a.state_path != backend_b.state_path + def test_state_path_uses_custom_state_file_name(self): + backend = tf.TfBackend(account_id="123456789012", region="eu-west-1", + repo_name="my-repo", + state_file_name="environment-reporter.tfstate") + + assert backend.state_path == \ + "terraform/my-repo/environment-reporter.tfstate" + + def test_state_path_defaults_to_main_tfstate(self): + backend_default = tf.TfBackend(account_id="123456789012", + region="eu-west-1", repo_name="my-repo") + backend_explicit = tf.TfBackend(account_id="123456789012", + region="eu-west-1", repo_name="my-repo", + state_file_name="main.tfstate") + + assert backend_default.state_path == backend_explicit.state_path + class TestTfBackendDataDir: def test_data_dir_includes_environment_id(self): diff --git a/tests/test_main.py b/tests/test_main.py index cf4c49d..20d18ba 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -80,6 +80,33 @@ def test_init_preserves_extra_args(self, monkeypatch): command = mock_execvp.call_args[0][1] assert "-upgrade" in command + def test_init_uses_tf_state_file_name_env_var(self, monkeypatch): + monkeypatch.setenv("AWS_VAULT", "staging") + monkeypatch.setenv("AWS_DEFAULT_REGION", "eu-west-1") + monkeypatch.setenv("TF_STATE_FILE_NAME", "environment-reporter.tfstate") + + with patch("tf.TfBackend._get_repo_name", return_value="my-repo"), \ + patch("tf.TfVarsFiles._get_account_id", return_value="123456789012"), \ + patch("tf.os.execvp") as mock_execvp: + tf.TfRunner(["init"]).call() + + command = mock_execvp.call_args[0][1] + assert "-backend-config=key=terraform/my-repo/" \ + "environment-reporter.tfstate" in command + + def test_init_defaults_to_main_tfstate_when_env_var_unset(self, monkeypatch): + monkeypatch.setenv("AWS_VAULT", "staging") + monkeypatch.setenv("AWS_DEFAULT_REGION", "eu-west-1") + monkeypatch.delenv("TF_STATE_FILE_NAME", raising=False) + + with patch("tf.TfBackend._get_repo_name", return_value="my-repo"), \ + patch("tf.TfVarsFiles._get_account_id", return_value="123456789012"), \ + patch("tf.os.execvp") as mock_execvp: + tf.TfRunner(["init"]).call() + + command = mock_execvp.call_args[0][1] + assert "-backend-config=key=terraform/my-repo/main.tfstate" in command + class TestTfRunnerAutoInit: def test_plan_runs_init_before_command(self, monkeypatch):