def test_auto_tfvars( self, caplog: LogCaptureFixture, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path, ) -> None: """Test auto_tfvars.""" caplog.set_level(LogLevels.DEBUG, logger=MODULE) mock_tfenv = MagicMock(current_version="0.12.0") mocker.patch.object(Terraform, "tfenv", mock_tfenv) options = { "terraform_write_auto_tfvars": True, } parameters = {"key": "val"} obj = Terraform(runway_context, module_root=tmp_path, options=options, parameters=parameters) assert obj.auto_tfvars.is_file() assert json.loads(obj.auto_tfvars.read_text()) == parameters assert "unable to parse current version" not in "\n".join( caplog.messages) # check cases where the file will not be written obj.auto_tfvars.unlink() del obj.auto_tfvars obj.options.write_auto_tfvars = False assert not obj.auto_tfvars.exists() # type: ignore del obj.auto_tfvars obj.options.write_auto_tfvars = True obj.parameters = {} assert not obj.auto_tfvars.exists() # type: ignore
def test_handle_backend_no_handler( self, caplog: LogCaptureFixture, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path, ) -> None: """Test handle_backend with no handler.""" caplog.set_level(LogLevels.DEBUG, logger=MODULE) mock_get_full_configuration = MagicMock(return_value={}) backend: Dict[str, Union[Dict[str, Any], str]] = { "type": "unsupported", "config": {}, } obj = Terraform(runway_context, module_root=tmp_path) mocker.patch.object(obj, "tfenv", MagicMock(backend=backend)) mocker.patch.object( obj.options.backend_config, "get_full_configuration", mock_get_full_configuration, ) assert not obj.handle_backend() mock_get_full_configuration.assert_not_called() assert 'backed "unsupported" does not require special handling' in "\n".join( caplog.messages)
def test_handle_backend_remote_name( self, caplog: LogCaptureFixture, monkeypatch: MonkeyPatch, runway_context: MockRunwayContext, tmp_path: Path, ) -> None: """Test handle_backend for remote backend with workspace prefix.""" caplog.set_level(LogLevels.DEBUG, logger=MODULE) monkeypatch.setenv("TF_WORKSPACE", "anything") mock_get_full_configuration = MagicMock(return_value={}) backend = { "type": "remote", "config": { "workspaces": { "name": "test" } } } obj = Terraform(runway_context, module_root=tmp_path) monkeypatch.setattr(obj, "tfenv", MagicMock(backend=backend)) monkeypatch.setattr( obj.options.backend_config, "get_full_configuration", mock_get_full_configuration, ) assert not obj.handle_backend() mock_get_full_configuration.assert_called_once_with() assert "TF_WORKSPACE" not in obj.ctx.env.vars assert obj.required_workspace == "default" assert 'forcing use of static workspace "default"' in "\n".join( caplog.messages)
def test_handle_backend_remote_prefix( self, caplog: LogCaptureFixture, monkeypatch: MonkeyPatch, runway_context: MockRunwayContext, tmp_path: Path, ) -> None: """Test handle_backend for remote backend with workspace prefix.""" caplog.set_level(LogLevels.DEBUG, logger=MODULE) monkeypatch.delenv("TF_WORKSPACE", raising=False) mock_get_full_configuration = MagicMock(return_value={}) backend = { "type": "remote", "config": { "workspaces": { "prefix": "test" } } } obj = Terraform(runway_context, module_root=tmp_path) monkeypatch.setattr(obj, "tfenv", MagicMock(backend=backend)) monkeypatch.setattr( obj.options.backend_config, "get_full_configuration", mock_get_full_configuration, ) assert not obj.handle_backend() mock_get_full_configuration.assert_called_once_with() assert obj.ctx.env.vars["TF_WORKSPACE"] == obj.ctx.env.name assert 'set environment variable "TF_WORKSPACE" to avoid prompt' in "\n".join( caplog.messages)
def test_handle_backend_remote_undetermined( self, caplog: LogCaptureFixture, monkeypatch: MonkeyPatch, runway_context: MockRunwayContext, tmp_path: Path, ) -> None: """Test handle_backend for remote backend with workspace undetermined.""" caplog.set_level(LogLevels.WARNING, logger=MODULE) monkeypatch.delenv("TF_WORKSPACE", raising=False) mock_get_full_configuration = MagicMock(return_value={}) backend: Dict[str, Union[Dict[str, Any], str]] = { "type": "remote", "config": {}, } obj = Terraform(runway_context, module_root=tmp_path) monkeypatch.setattr(obj, "tfenv", MagicMock(backend=backend)) monkeypatch.setattr( obj.options.backend_config, "get_full_configuration", mock_get_full_configuration, ) assert not obj.handle_backend() mock_get_full_configuration.assert_called_once_with() assert '"workspaces" not defined in backend config' in "\n".join( caplog.messages)
def test_skip( self, env: bool, param: bool, expected: bool, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path, ) -> None: """Test skip.""" mocker.patch.object(Terraform, "env_file", env) obj = Terraform(runway_context, module_root=tmp_path) obj.parameters = param # type: ignore assert obj.skip == expected
def test_handle_backend_no_type( self, caplog: LogCaptureFixture, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path, ) -> None: """Test handle_backend with no type.""" caplog.set_level(LogLevels.INFO, logger=MODULE) obj = Terraform(runway_context, module_root=tmp_path) mocker.patch.object(obj, "tfenv", MagicMock(backend={"type": None})) assert not obj.handle_backend() assert "unable to determine backend for module" in "\n".join( caplog.messages)
def test_terraform_workspace_new(self, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path) -> None: """Test terraform_workspace_new.""" mock_gen_command = mocker.patch.object( Terraform, "gen_command", return_value=["mock_gen_command"]) mock_run_command = mocker.patch(f"{MODULE}.run_module_command") obj = Terraform(runway_context, module_root=tmp_path) assert not obj.terraform_workspace_new("name") mock_gen_command.assert_called_once_with(["workspace", "new"], ["name"]) mock_run_command.assert_called_once_with(["mock_gen_command"], env_vars=obj.ctx.env.vars, logger=obj.logger)
def test_terraform_destroy(self, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path) -> None: """Test terraform_destroy.""" mock_gen_command = mocker.patch.object( Terraform, "gen_command", return_value=["mock_gen_command"]) mock_run_command = mocker.patch(f"{MODULE}.run_module_command") obj = Terraform(runway_context, module_root=tmp_path) mocker.patch.object(obj, "env_file", ["env_file"]) expected_arg_list = ["-force", "env_file"] assert not obj.terraform_destroy() mock_gen_command.assert_called_once_with("destroy", expected_arg_list) mock_run_command.assert_called_once_with(["mock_gen_command"], env_vars=obj.ctx.env.vars, logger=obj.logger)
def test_terraform_plan(self, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path) -> None: """Test terraform_plan.""" mock_gen_command = mocker.patch.object( Terraform, "gen_command", return_value=["mock_gen_command"]) mock_run_command = mocker.patch(f"{MODULE}.run_module_command") options = {"args": {"plan": ["plan_arg"]}} obj = Terraform(runway_context, module_root=tmp_path, options=options) mocker.patch.object(obj, "env_file", ["env_file"]) assert not obj.terraform_plan() mock_gen_command.assert_called_once_with("plan", ["env_file", "plan_arg"]) mock_run_command.assert_called_once_with(["mock_gen_command"], env_vars=obj.ctx.env.vars, logger=obj.logger)
def test_terraform_workspace_list(self, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path) -> None: """Test terraform_workspace_list.""" mock_gen_command = mocker.patch.object( Terraform, "gen_command", return_value=["mock_gen_command"]) mock_subprocess = mocker.patch(f"{MODULE}.subprocess") check_output_result = MagicMock() check_output_result.decode.return_value = "decoded" mock_subprocess.check_output.return_value = check_output_result obj = Terraform(runway_context, module_root=tmp_path) assert obj.terraform_workspace_list() == "decoded" mock_gen_command.assert_called_once_with(["workspace", "list"]) mock_subprocess.check_output.assert_called_once_with( ["mock_gen_command"], env=obj.ctx.env.vars) check_output_result.decode.assert_called_once_with()
def test_terraform_get( self, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path, ) -> None: """Test terraform_get.""" mock_gen_command = mocker.patch.object( Terraform, "gen_command", return_value=["mock_gen_command"]) mock_run_command = mocker.patch(f"{MODULE}.run_module_command") obj = Terraform(runway_context, module_root=tmp_path) assert not obj.terraform_get() mock_gen_command.assert_called_once_with("get", ["-update=true"]) mock_run_command.assert_called_once_with(["mock_gen_command"], env_vars=obj.ctx.env.vars, logger=obj.logger)
def test_current_workspace(self, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path) -> None: """Test current_workspace.""" mock_terraform_workspace_show = mocker.patch.object( Terraform, "terraform_workspace_show", return_value="default") assert (Terraform(runway_context, module_root=tmp_path).current_workspace == "default") mock_terraform_workspace_show.assert_called_once_with()
def test_tfenv(self, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path) -> None: """Test tfenv.""" mock_tfenv = mocker.patch(f"{MODULE}.TFEnvManager", return_value="tfenv") obj = Terraform(runway_context, module_root=tmp_path) assert obj.tfenv == "tfenv" mock_tfenv.assert_called_once_with(tmp_path)
def test_tf_bin_file(self, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path) -> None: """Test tf_bin version in file.""" mock_tfenv = MagicMock(version_file=True) mock_tfenv.install.return_value = "success" mocker.patch.object(Terraform, "tfenv", mock_tfenv) obj = Terraform(runway_context, module_root=tmp_path) assert obj.tf_bin == "success" mock_tfenv.install.assert_called_once_with(None)
def test_handle_parameters(self, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path) -> None: """Test handle_parameters.""" mock_update_envvars = mocker.patch( f"{MODULE}.update_env_vars_with_tf_var_values", return_value={"result": "success"}, ) obj = Terraform(runway_context.copy(), module_root=tmp_path) mocker.patch.object( obj, "auto_tfvars", MagicMock(exists=MagicMock(side_effect=[True, False]))) assert not obj.handle_parameters() mock_update_envvars.assert_not_called() assert not obj.handle_parameters() mock_update_envvars.assert_called_once_with(runway_context.env.vars, {}) assert obj.ctx.env.vars == {"result": "success"}
def test_gen_command( self, command: Union[List[str], str], args_list: Optional[List[str]], expected: List[str], mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path, ) -> None: """Test gen_command.""" mocker.patch.object(Terraform, "tf_bin", "terraform") expected.insert(0, "terraform") obj = Terraform(runway_context, module_root=tmp_path) mocker.patch.object(obj.ctx, "no_color", False) assert obj.gen_command(command, args_list=args_list) == expected mocker.patch.object(obj.ctx, "no_color", True) expected.append("-no-color") assert obj.gen_command(command, args_list=args_list) == expected
def test_tf_bin_options(self, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path) -> None: """Test tf_bin version in options.""" mock_tfenv = MagicMock() mock_tfenv.install.return_value = "success" mocker.patch.object(Terraform, "tfenv", mock_tfenv) options = {"terraform_version": "0.12.0"} obj = Terraform(runway_context, module_root=tmp_path, options=options) assert obj.tf_bin == "success" mock_tfenv.install.assert_called_once_with("0.12.0")
def test_tf_bin_global(self, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path) -> None: """Test tf_bin from global install.""" mocker.patch.object( Terraform, "tfenv", MagicMock(install=MagicMock(side_effect=ValueError))) mock_which = mocker.patch(f"{MODULE}.which", return_value=True) obj = Terraform(runway_context, module_root=tmp_path) assert obj.tf_bin == "terraform" mock_which.assert_called_once_with("terraform")
def test_cleanup_dot_terraform( self, caplog: LogCaptureFixture, runway_context: MockRunwayContext, tmp_path: Path, ) -> None: """Test cleanup_dot_terraform.""" caplog.set_level(logging.DEBUG, logger=MODULE) obj = Terraform(runway_context, module_root=tmp_path) obj.cleanup_dot_terraform() assert "skipped cleanup" in "\n".join(caplog.messages) dot_tf = tmp_path / ".terraform" dot_tf_modules = dot_tf / "modules" dot_tf_modules.mkdir(parents=True) (dot_tf_modules / "some_file").touch() dot_tf_plugins = dot_tf / "plugins" dot_tf_plugins.mkdir(parents=True) (dot_tf_plugins / "some_file").touch() dot_tf_tfstate = dot_tf / "terraform.tfstate" dot_tf_tfstate.touch() obj.cleanup_dot_terraform() assert dot_tf.exists() assert not dot_tf_modules.exists() assert dot_tf_plugins.exists() assert (dot_tf_plugins / "some_file").exists() assert not dot_tf_tfstate.exists() assert "removing some of its contents" in "\n".join(caplog.messages)
def test_terraform_init(self, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path) -> None: """Test terraform_init.""" mock_gen_command = mocker.patch.object( Terraform, "gen_command", return_value=["mock_gen_command"]) mock_run_command = mocker.patch(f"{MODULE}.run_module_command") options: Dict[str, Union[Dict[str, Any], str]] = { "args": { "init": ["init_arg"] }, "terraform_backend_config": { "bucket": "name" }, } obj = Terraform(runway_context, module_root=tmp_path, options=options) expected_arg_list = [ "-reconfigure", "-backend-config", "bucket=name", "-backend-config", "region=us-east-1", "init_arg", ] assert not obj.terraform_init() mock_gen_command.assert_called_once_with("init", expected_arg_list) mock_run_command.assert_called_once_with( ["mock_gen_command"], env_vars=obj.ctx.env.vars, exit_on_error=False, logger=obj.logger, ) mock_run_command.side_effect = subprocess.CalledProcessError(1, "") with pytest.raises(SystemExit) as excinfo: assert obj.terraform_init() assert excinfo.value.code == 1
def test_terraform_workspace_select(self, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path) -> None: """Test terraform_workspace_new.""" mock_gen_command = mocker.patch.object( Terraform, "gen_command", return_value=["mock_gen_command"]) mock_run_command = mocker.patch(f"{MODULE}.run_module_command") mocker.patch.object( Terraform, "terraform_workspace_show", side_effect=["first-val", "second-val"], ) obj = Terraform(runway_context, module_root=tmp_path) assert obj.current_workspace == "first-val" # load cached value assert not obj.terraform_workspace_select("name") mock_gen_command.assert_called_once_with(["workspace", "select"], ["name"]) mock_run_command.assert_called_once_with(["mock_gen_command"], env_vars=obj.ctx.env.vars, logger=obj.logger) # cache was cleared and a new value was obtained assert obj.current_workspace == "second-val"
def test_terraform_apply(self, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path) -> None: """Test terraform_apply.""" mock_gen_command = mocker.patch.object(Terraform, "gen_command") mock_run_command = mocker.patch(f"{MODULE}.run_module_command") mock_gen_command.return_value = ["mock_gen_command"] options = {"args": {"apply": ["arg"]}} obj = Terraform(runway_context, module_root=tmp_path, options=options) mocker.patch.object(obj, "env_file", ["env_file"]) mocker.patch.object(obj.ctx.env, "ci", True) expected_arg_list = ["env_file", "arg", "-auto-approve=true"] assert not obj.terraform_apply() mock_gen_command.assert_called_once_with("apply", expected_arg_list) mock_run_command.assert_called_once_with(["mock_gen_command"], env_vars=obj.ctx.env.vars, logger=obj.logger) mocker.patch.object(obj.ctx.env, "ci", False) expected_arg_list[2] = "-auto-approve=false" assert not obj.terraform_apply() mock_gen_command.assert_called_with("apply", expected_arg_list) assert mock_run_command.call_count == 2
def test_init(self, runway_context: MockRunwayContext, tmp_path: Path) -> None: """Test class instantiation.""" parameters = {"key1": "val1"} obj = Terraform( runway_context, explicitly_enabled=True, module_root=tmp_path, parameters=parameters, ) assert obj.logger assert obj.path == tmp_path assert obj.explicitly_enabled assert isinstance(obj.options, TerraformOptions) assert obj.parameters == parameters assert obj.required_workspace == runway_context.env.name
def test_env_file( self, filename: Union[List[str], str], expected: Optional[str], runway_context: MockRunwayContext, tmp_path: Path, ) -> None: """Test env_file.""" obj = Terraform(runway_context, module_root=tmp_path) if isinstance(filename, list): for name in filename: (tmp_path / name).touch() else: (tmp_path / filename).touch() if expected: assert obj.env_file == ["-var-file=" + expected] else: assert not obj.env_file
def test_auto_tfvars_invalid_version( self, caplog: LogCaptureFixture, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path, ) -> None: """Test auto_tfvars with a version that cannot be converted to int.""" caplog.set_level(LogLevels.DEBUG, logger=MODULE) mock_tfenv = MagicMock(current_version="v0.12.0") mocker.patch.object(Terraform, "tfenv", mock_tfenv) options = {"terraform_write_auto_tfvars": True} parameters = {"key": "val"} obj = Terraform(runway_context, module_root=tmp_path, options=options, parameters=parameters) assert obj.auto_tfvars.is_file() assert json.loads(obj.auto_tfvars.read_text()) == parameters assert "unable to parse current version" in "\n".join(caplog.messages)
def test_tf_bin_missing( self, caplog: LogCaptureFixture, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path, ) -> None: """Test tf_bin missing.""" caplog.set_level(LogLevels.ERROR, logger=MODULE) mock_which = mocker.patch(f"{MODULE}.which", return_value=False) mocker.patch.object( Terraform, "tfenv", MagicMock(install=MagicMock(side_effect=ValueError))) obj = Terraform(runway_context, module_root=tmp_path) with pytest.raises(SystemExit) as excinfo: assert obj.tf_bin assert excinfo.value.code == 1 mock_which.assert_called_once_with("terraform") assert ( "terraform not available and a version to install not specified" in "\n".join(caplog.messages))
def test_auto_tfvars_unsupported_version( self, caplog: LogCaptureFixture, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path, ) -> None: """Test auto_tfvars with a version that does not support it.""" caplog.set_level(LogLevels.WARNING, logger=MODULE) mock_tfenv = MagicMock(current_version="0.9.0") mocker.patch.object(Terraform, "tfenv", mock_tfenv) options = {"terraform_write_auto_tfvars": True} parameters = {"key": "val"} obj = Terraform(runway_context, module_root=tmp_path, options=options, parameters=parameters) assert obj.auto_tfvars.is_file() assert json.loads(obj.auto_tfvars.read_text()) == parameters assert ("Terraform version does not support the use of " "*.auto.tfvars; some variables may be missing") in "\n".join( caplog.messages)
def test_execute( self, action: str, caplog: LogCaptureFixture, mocker: MockerFixture, runway_context: MockRunwayContext, tmp_path: Path, ) -> None: """Test executing a Runway action.""" caplog.set_level(LogLevels.DEBUG, logger=MODULE) mocker.patch.object(Terraform, "handle_backend", MagicMock()) mocker.patch.object(Terraform, "skip", True) mocker.patch.object(Terraform, "cleanup_dot_terraform", MagicMock()) mocker.patch.object(Terraform, "handle_parameters", MagicMock()) mocker.patch.object(Terraform, "terraform_init", MagicMock()) mocker.patch.object(Terraform, "current_workspace", "test") mocker.patch.object(Terraform, "terraform_workspace_list", MagicMock(return_value="* test")) mocker.patch.object(Terraform, "terraform_workspace_select", MagicMock()) mocker.patch.object(Terraform, "terraform_workspace_new", MagicMock()) mocker.patch.object(Terraform, "terraform_get", MagicMock()) mocker.patch.object(Terraform, "terraform_apply", MagicMock()) mocker.patch.object(Terraform, "terraform_destroy", MagicMock()) mocker.patch.object(Terraform, "terraform_plan", MagicMock()) mocker.patch.object( Terraform, "auto_tfvars", MagicMock(exists=MagicMock(return_value=True), unlink=MagicMock()), ) command = "apply" if action == "deploy" else action # pylint: disable=no-member # module is skipped obj = Terraform(runway_context, module_root=tmp_path) assert not obj[action]() obj.handle_backend.assert_called_once_with() obj.cleanup_dot_terraform.assert_not_called() obj.handle_parameters.assert_not_called() obj.auto_tfvars.exists.assert_called_once_with() obj.auto_tfvars.unlink.assert_called_once_with() caplog.clear() # module is run; workspace matches obj.auto_tfvars.exists.return_value = False mocker.patch.object(obj, "skip", False) assert not obj[action]() obj.cleanup_dot_terraform.assert_called_once_with() obj.handle_parameters.assert_called_once_with() obj.terraform_init.assert_called_once_with() obj.terraform_workspace_list.assert_not_called() obj.terraform_workspace_select.assert_not_called() obj.terraform_workspace_new.assert_not_called() obj.terraform_get.assert_called_once_with() obj["terraform_" + command].assert_called_once_with() assert obj.auto_tfvars.exists.call_count == 2 assert obj.auto_tfvars.unlink.call_count == 1 logs = "\n".join(caplog.messages) assert "init (in progress)" in logs assert "init (complete)" in logs assert "re-running init after workspace change..." not in logs assert "{} (in progress)".format(command) in logs assert "{} (complete)".format(command) in logs caplog.clear() # module is run; switch to workspace mocker.patch.object(Terraform, "current_workspace", "default") assert not obj[action]() obj.terraform_workspace_list.assert_called_once_with() obj.terraform_workspace_select.assert_called_once_with("test") obj.terraform_workspace_new.assert_not_called() logs = "\n".join(caplog.messages) assert "re-running init after workspace change..." in logs # module is run; create workspace mocker.patch.object(Terraform, "terraform_workspace_list", MagicMock(return_value="")) assert not obj[action]() obj.terraform_workspace_new.assert_called_once_with("test")
def test_init_options_workspace(self, runway_context: MockRunwayContext, tmp_path: Path) -> None: """Test class instantiation with workspace option.""" options = {"terraform_workspace": "default"} obj = Terraform(runway_context, module_root=tmp_path, options=options) assert obj.required_workspace == options["terraform_workspace"]