Skip to content

Commit d8e1a06

Browse files
committed
Allow passing environment variables to terraform commands
For example, to set the AWS region and profile.
1 parent ad4e5d1 commit d8e1a06

File tree

6 files changed

+236
-29
lines changed

6 files changed

+236
-29
lines changed
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
from typing import Iterable
1+
from typing import Iterable, Optional
22

33
from invoke.context import Context
44

55
import infrablocks.invoke_terraform.terraform as tf
6+
from infrablocks.invoke_terraform.terraform.terraform import Environment
67

78

89
class InvokeExecutor(tf.Executor):
910
def __init__(self, context: Context):
1011
self._context = context
1112

12-
def execute(self, command: Iterable[str]) -> None:
13-
self._context.run(" ".join(command))
13+
def execute(
14+
self, command: Iterable[str], env: Optional[Environment] = None
15+
) -> None:
16+
self._context.run(" ".join(command), env=(env or {}))

src/infrablocks/invoke_terraform/task_factory.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import infrablocks.invoke_factory as invoke_factory
88
import infrablocks.invoke_terraform.terraform as tf
9+
from infrablocks.invoke_terraform.terraform.terraform import Environment
910
from infrablocks.invoke_terraform.terraform_factory import TerraformFactory
1011

1112

@@ -21,6 +22,7 @@ class Configuration:
2122
variables: tf.Variables
2223
workspace: Optional[str]
2324
init_configuration: InitConfiguration
25+
environment: Optional[Environment] = None
2426
auto_approve: bool = True
2527

2628
@staticmethod
@@ -32,6 +34,7 @@ def create_empty():
3234
init_configuration=InitConfiguration(
3335
backend_config={}, reconfigure=False
3436
),
37+
environment={},
3538
)
3639

3740

@@ -74,6 +77,7 @@ def plan(context: Context, arguments: invoke_factory.Arguments):
7477
terraform.plan(
7578
chdir=configuration.source_directory,
7679
vars=configuration.variables,
80+
environment=configuration.environment,
7781
)
7882

7983
return plan
@@ -90,6 +94,7 @@ def apply(context: Context, arguments: invoke_factory.Arguments):
9094
chdir=configuration.source_directory,
9195
vars=configuration.variables,
9296
autoapprove=configuration.auto_approve,
97+
environment=configuration.environment,
9398
)
9499

95100
return apply
@@ -111,13 +116,15 @@ def _pre_command_setup(
111116
chdir=configuration.source_directory,
112117
backend_config=configuration.init_configuration.backend_config,
113118
reconfigure=configuration.init_configuration.reconfigure,
119+
environment=configuration.environment,
114120
)
115121

116122
if configuration.workspace is not None:
117123
terraform.select_workspace(
118124
configuration.workspace,
119125
chdir=configuration.source_directory,
120126
or_create=True,
127+
environment=configuration.environment,
121128
)
122129

123130
return terraform, configuration

src/infrablocks/invoke_terraform/terraform/terraform.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
type ConfigurationValue = Union[bool, int, float, str, None]
44
type Variables = Dict[str, ConfigurationValue]
55
type BackendConfig = Union[str, Dict[str, ConfigurationValue]]
6+
type Environment = Dict[str, str]
67

78

89
class Executor:
9-
def execute(self, command: Iterable[str]) -> None:
10+
def execute(
11+
self, command: Iterable[str], env: Optional[Environment]
12+
) -> None:
1013
raise Exception("NotImplementedException")
1114

1215

@@ -19,6 +22,7 @@ def init(
1922
chdir: Optional[str] = None,
2023
backend_config: Optional[BackendConfig] = {},
2124
reconfigure: Optional[bool] = False,
25+
environment: Optional[Environment] = None,
2226
):
2327
base_command = self._build_base_command(chdir)
2428
command = (
@@ -30,21 +34,25 @@ def init(
3034
if reconfigure:
3135
command = command + ["-reconfigure"]
3236

33-
self._executor.execute(command)
37+
self._executor.execute(command, env=environment)
3438

3539
def plan(
36-
self, chdir: Optional[str] = None, vars: Optional[Variables] = {}
40+
self,
41+
chdir: Optional[str] = None,
42+
vars: Optional[Variables] = {},
43+
environment: Optional[Environment] = None,
3744
):
3845
base_command = self._build_base_command(chdir)
3946
command = base_command + ["plan"] + self._build_vars(vars)
4047

41-
self._executor.execute(command)
48+
self._executor.execute(command, env=environment)
4249

4350
def apply(
4451
self,
4552
chdir: Optional[str] = None,
4653
vars: Optional[Variables] = {},
4754
autoapprove: bool = False,
55+
environment: Optional[Environment] = None,
4856
):
4957
base_command = self._build_base_command(chdir)
5058
autoapprove_flag = ["-auto-approve"] if autoapprove else []
@@ -55,13 +63,14 @@ def apply(
5563
+ self._build_vars(vars)
5664
)
5765

58-
self._executor.execute(command)
66+
self._executor.execute(command, env=environment)
5967

6068
def select_workspace(
6169
self,
6270
workspace: str,
6371
chdir: Optional[str] = None,
6472
or_create: bool = False,
73+
environment: Optional[Environment] = None,
6574
):
6675
base_command = self._build_base_command(chdir)
6776
command = base_command + ["workspace", "select"]
@@ -71,7 +80,7 @@ def select_workspace(
7180

7281
command = command + [workspace]
7382

74-
self._executor.execute(command)
83+
self._executor.execute(command, env=environment)
7584

7685
@staticmethod
7786
def _build_base_command(chdir: Optional[str]) -> List[str]:

tests/unit/infrablocks/invoke_terraform/terraform/test_terraform.py

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ def test_init_executes(self):
2121

2222
terraform.init()
2323

24-
executor.execute.assert_called_once_with(["terraform", "init"])
24+
executor.execute.assert_called_once_with(
25+
["terraform", "init"], env=None
26+
)
2527

2628
def test_init_executes_with_chdir(self):
2729
executor = Mock(spec=Executor)
@@ -30,7 +32,7 @@ def test_init_executes_with_chdir(self):
3032
terraform.init(chdir="/some/dir")
3133

3234
executor.execute.assert_called_once_with(
33-
["terraform", "-chdir=/some/dir", "init"]
35+
["terraform", "-chdir=/some/dir", "init"], env=None
3436
)
3537

3638
def test_init_executes_with_backend_config_dictionary(self):
@@ -41,7 +43,7 @@ def test_init_executes_with_backend_config_dictionary(self):
4143
terraform.init(backend_config=backend_config)
4244

4345
executor.execute.assert_called_once_with(
44-
["terraform", "init", '-backend-config="foo=1"']
46+
["terraform", "init", '-backend-config="foo=1"'], env=None
4547
)
4648

4749
def test_init_executes_with_backend_config_path(self):
@@ -52,7 +54,8 @@ def test_init_executes_with_backend_config_path(self):
5254
terraform.init(backend_config=backend_config)
5355

5456
executor.execute.assert_called_once_with(
55-
["terraform", "init", "-backend-config=/some/config.tfvars"]
57+
["terraform", "init", "-backend-config=/some/config.tfvars"],
58+
env=None,
5659
)
5760

5861
def test_init_executes_with_reconfigure(self):
@@ -62,7 +65,18 @@ def test_init_executes_with_reconfigure(self):
6265
terraform.init(reconfigure=True)
6366

6467
executor.execute.assert_called_once_with(
65-
["terraform", "init", "-reconfigure"]
68+
["terraform", "init", "-reconfigure"], env=None
69+
)
70+
71+
def test_init_executes_with_environment(self):
72+
executor = Mock(spec=Executor)
73+
terraform = Terraform(executor)
74+
environment = {"ENV_VAR": "value"}
75+
76+
terraform.init(environment=environment)
77+
78+
executor.execute.assert_called_once_with(
79+
["terraform", "init"], env=environment
6680
)
6781

6882
def test_plan_executes(self):
@@ -71,7 +85,9 @@ def test_plan_executes(self):
7185

7286
terraform.plan()
7387

74-
executor.execute.assert_called_once_with(["terraform", "plan"])
88+
executor.execute.assert_called_once_with(
89+
["terraform", "plan"], env=None
90+
)
7591

7692
def test_plan_executes_with_chdir(self):
7793
executor = Mock(spec=Executor)
@@ -80,7 +96,7 @@ def test_plan_executes_with_chdir(self):
8096
terraform.plan(chdir="/some/dir")
8197

8298
executor.execute.assert_called_once_with(
83-
["terraform", "-chdir=/some/dir", "plan"]
99+
["terraform", "-chdir=/some/dir", "plan"], env=None
84100
)
85101

86102
def test_plan_executes_with_vars(self):
@@ -91,7 +107,18 @@ def test_plan_executes_with_vars(self):
91107
terraform.plan(vars=variables)
92108

93109
executor.execute.assert_called_once_with(
94-
["terraform", "plan", '-var="foo=1"']
110+
["terraform", "plan", '-var="foo=1"'], env=None
111+
)
112+
113+
def test_plan_executes_with_environment(self):
114+
executor = Mock(spec=Executor)
115+
terraform = Terraform(executor)
116+
environment = {"ENV_VAR": "value"}
117+
118+
terraform.plan(environment=environment)
119+
120+
executor.execute.assert_called_once_with(
121+
["terraform", "plan"], env=environment
95122
)
96123

97124
def test_apply_executes(self):
@@ -100,7 +127,9 @@ def test_apply_executes(self):
100127

101128
terraform.apply()
102129

103-
executor.execute.assert_called_once_with(["terraform", "apply"])
130+
executor.execute.assert_called_once_with(
131+
["terraform", "apply"], env=None
132+
)
104133

105134
def test_apply_executes_with_chdir(self):
106135
executor = Mock(spec=Executor)
@@ -109,7 +138,7 @@ def test_apply_executes_with_chdir(self):
109138
terraform.apply(chdir="/some/dir")
110139

111140
executor.execute.assert_called_once_with(
112-
["terraform", "-chdir=/some/dir", "apply"]
141+
["terraform", "-chdir=/some/dir", "apply"], env=None
113142
)
114143

115144
def test_apply_executes_with_vars(self):
@@ -120,7 +149,7 @@ def test_apply_executes_with_vars(self):
120149
terraform.apply(vars=variables)
121150

122151
executor.execute.assert_called_once_with(
123-
["terraform", "apply", '-var="foo=1"']
152+
["terraform", "apply", '-var="foo=1"'], env=None
124153
)
125154

126155
def test_apply_executes_with_autoapprove(self):
@@ -130,7 +159,18 @@ def test_apply_executes_with_autoapprove(self):
130159
terraform.apply(autoapprove=True)
131160

132161
executor.execute.assert_called_once_with(
133-
["terraform", "apply", "-auto-approve"]
162+
["terraform", "apply", "-auto-approve"], env=None
163+
)
164+
165+
def test_apply_executes_with_environment(self):
166+
executor = Mock(spec=Executor)
167+
terraform = Terraform(executor)
168+
environment = {"ENV_VAR": "value"}
169+
170+
terraform.apply(environment=environment)
171+
172+
executor.execute.assert_called_once_with(
173+
["terraform", "apply"], env=environment
134174
)
135175

136176
def test_select_workspace_executes(self):
@@ -141,7 +181,7 @@ def test_select_workspace_executes(self):
141181
terraform.select_workspace(workspace)
142182

143183
executor.execute.assert_called_once_with(
144-
["terraform", "workspace", "select", workspace]
184+
["terraform", "workspace", "select", workspace], env=None
145185
)
146186

147187
def test_select_workspace_executes_with_chdir(self):
@@ -152,7 +192,14 @@ def test_select_workspace_executes_with_chdir(self):
152192
terraform.select_workspace(workspace, chdir="/some/dir")
153193

154194
executor.execute.assert_called_once_with(
155-
["terraform", "-chdir=/some/dir", "workspace", "select", workspace]
195+
[
196+
"terraform",
197+
"-chdir=/some/dir",
198+
"workspace",
199+
"select",
200+
workspace,
201+
],
202+
env=None,
156203
)
157204

158205
def test_select_workspace_executes_with_or_create(self):
@@ -163,5 +210,18 @@ def test_select_workspace_executes_with_or_create(self):
163210
terraform.select_workspace(workspace, or_create=True)
164211

165212
executor.execute.assert_called_once_with(
166-
["terraform", "workspace", "select", "-or-create=true", workspace]
213+
["terraform", "workspace", "select", "-or-create=true", workspace],
214+
env=None,
215+
)
216+
217+
def test_select_workspace_executes_with_environment(self):
218+
executor = Mock(spec=Executor)
219+
terraform = Terraform(executor)
220+
workspace = "workspace"
221+
environment = {"ENV_VAR": "value"}
222+
223+
terraform.select_workspace(workspace, environment=environment)
224+
225+
executor.execute.assert_called_once_with(
226+
["terraform", "workspace", "select", workspace], env=environment
167227
)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from unittest.mock import Mock
2+
3+
from invoke.context import Context
4+
5+
from infrablocks.invoke_terraform.invoke_executor import InvokeExecutor
6+
7+
8+
class TestInvokeExecutor:
9+
def test_run_invoked_with_command(self):
10+
context = Mock(spec=Context)
11+
12+
executor = InvokeExecutor(context)
13+
14+
executor.execute(["some_command", "arg1", "arg2"])
15+
16+
context.run.assert_called_once_with(
17+
"some_command arg1 arg2",
18+
env={},
19+
)
20+
21+
def test_run_invoked_with_env(self):
22+
context = Mock(spec=Context)
23+
24+
executor = InvokeExecutor(context)
25+
26+
executor.execute(
27+
["some_command", "arg1", "arg2"], env={"ENV_VAR": "value"}
28+
)
29+
30+
context.run.assert_called_once_with(
31+
"some_command arg1 arg2",
32+
env={"ENV_VAR": "value"},
33+
)
34+
35+
def test_run_invoked_with_empty_env(self):
36+
context = Mock(spec=Context)
37+
38+
executor = InvokeExecutor(context)
39+
40+
executor.execute(["some_command", "arg1", "arg2"], env=None)
41+
42+
context.run.assert_called_once_with(
43+
"some_command arg1 arg2",
44+
env={},
45+
)

0 commit comments

Comments
 (0)