@@ -27,6 +27,7 @@ if os.path.isdir(os.path.join(PARENT_FOLDER, ".venv")):
27
27
from localstack_client import config # noqa: E402
28
28
import hcl2 # noqa: E402
29
29
30
+ DRY_RUN = str (os .environ .get ("DRY_RUN" )).strip ().lower () in ["1" , "true" ]
30
31
DEFAULT_REGION = "us-east-1"
31
32
DEFAULT_ACCESS_KEY = "test"
32
33
AWS_ENDPOINT_URL = os .environ .get ("AWS_ENDPOINT_URL" )
@@ -35,6 +36,7 @@ LOCALHOST_HOSTNAME = "localhost.localstack.cloud"
35
36
S3_HOSTNAME = os .environ .get ("S3_HOSTNAME" ) or f"s3.{ LOCALHOST_HOSTNAME } "
36
37
USE_EXEC = str (os .environ .get ("USE_EXEC" )).strip ().lower () in ["1" , "true" ]
37
38
TF_CMD = os .environ .get ("TF_CMD" ) or "terraform"
39
+ TF_PROXIED_CMDS = ("init" , "plan" , "apply" , "destroy" )
38
40
LS_PROVIDERS_FILE = os .environ .get ("LS_PROVIDERS_FILE" ) or "localstack_providers_override.tf"
39
41
LOCALSTACK_HOSTNAME = urlparse (AWS_ENDPOINT_URL ).hostname or os .environ .get ("LOCALSTACK_HOSTNAME" ) or "localhost"
40
42
EDGE_PORT = int (urlparse (AWS_ENDPOINT_URL ).port or os .environ .get ("EDGE_PORT" ) or 4566 )
@@ -153,12 +155,15 @@ def create_provider_config_file(provider_aliases=None):
153
155
154
156
# write temporary config file
155
157
providers_file = get_providers_file_path ()
156
- if os .path .exists (providers_file ):
157
- msg = f"Providers override file { providers_file } already exists - please delete it first"
158
- raise Exception (msg )
158
+ write_provider_config_file (providers_file , tf_config )
159
+
160
+ return providers_file
161
+
162
+
163
+ def write_provider_config_file (providers_file , tf_config ):
164
+ """Write provider config into file"""
159
165
with open (providers_file , mode = "w" ) as fp :
160
166
fp .write (tf_config )
161
- return providers_file
162
167
163
168
164
169
def get_providers_file_path () -> str :
@@ -186,9 +191,12 @@ def determine_provider_aliases() -> list:
186
191
187
192
def generate_s3_backend_config () -> str :
188
193
"""Generate an S3 `backend {..}` block with local endpoints, if configured"""
194
+ is_tf_legacy = TF_VERSION < version .Version ("1.6" )
189
195
backend_config = None
190
196
tf_files = parse_tf_files ()
191
- for obj in tf_files .values ():
197
+ for filename , obj in tf_files .items ():
198
+ if LS_PROVIDERS_FILE == filename :
199
+ continue
192
200
tf_configs = ensure_list (obj .get ("terraform" , []))
193
201
for tf_config in tf_configs :
194
202
backend_config = ensure_list (tf_config .get ("backend" ))
@@ -199,6 +207,13 @@ def generate_s3_backend_config() -> str:
199
207
if not backend_config :
200
208
return ""
201
209
210
+ legacy_endpoint_mappings = {
211
+ "endpoint" : "s3" ,
212
+ "iam_endpoint" : "iam" ,
213
+ "sts_endpoint" : "sts" ,
214
+ "dynamodb_endpoint" : "dynamodb" ,
215
+ }
216
+
202
217
configs = {
203
218
# note: default values, updated by `backend_config` further below...
204
219
"bucket" : "tf-test-state" ,
@@ -213,15 +228,29 @@ def generate_s3_backend_config() -> str:
213
228
"dynamodb" : get_service_endpoint ("dynamodb" ),
214
229
},
215
230
}
231
+ # Merge in legacy endpoint configs if not existing already
232
+ if is_tf_legacy and backend_config .get ("endpoints" ):
233
+ print ("Warning: Unsupported backend option(s) detected (`endpoints`). Please make sure you always use the corresponding options to your Terraform version." )
234
+ exit (1 )
235
+ for legacy_endpoint , endpoint in legacy_endpoint_mappings .items ():
236
+ if legacy_endpoint in backend_config and (not backend_config .get ("endpoints" ) or endpoint not in backend_config ["endpoints" ]):
237
+ if not backend_config .get ("endpoints" ):
238
+ backend_config ["endpoints" ] = {}
239
+ backend_config ["endpoints" ].update ({endpoint : backend_config [legacy_endpoint ]})
240
+ # Add any missing default endpoints
241
+ if backend_config .get ("endpoints" ):
242
+ backend_config ["endpoints" ] = {
243
+ k : backend_config ["endpoints" ].get (k ) or v
244
+ for k , v in configs ["endpoints" ].items ()}
216
245
configs .update (backend_config )
217
- get_or_create_bucket (configs ["bucket" ])
218
- get_or_create_ddb_table (configs ["dynamodb_table" ], region = configs ["region" ])
246
+ if not DRY_RUN :
247
+ get_or_create_bucket (configs ["bucket" ])
248
+ get_or_create_ddb_table (configs ["dynamodb_table" ], region = configs ["region" ])
219
249
result = TF_S3_BACKEND_CONFIG
220
250
for key , value in configs .items ():
221
251
if isinstance (value , bool ):
222
252
value = str (value ).lower ()
223
253
elif isinstance (value , dict ):
224
- is_tf_legacy = not (TF_VERSION .major > 1 or (TF_VERSION .major == 1 and TF_VERSION .minor > 5 ))
225
254
if key == "endpoints" and is_tf_legacy :
226
255
value = textwrap .indent (
227
256
text = textwrap .dedent (f"""\
@@ -241,6 +270,21 @@ def generate_s3_backend_config() -> str:
241
270
return result
242
271
243
272
273
+ def check_override_file (providers_file : str ) -> None :
274
+ """Checks override file existance"""
275
+ if os .path .exists (providers_file ):
276
+ msg = f"Providers override file { providers_file } already exists"
277
+ err_msg = msg + " - please delete it first, exiting..."
278
+ if DRY_RUN :
279
+ msg += ". File will be overwritten."
280
+ print (msg )
281
+ print ("\t Only 'yes' will be accepted to approve." )
282
+ if input ("\t Enter a value: " ) == "yes" :
283
+ return
284
+ print (err_msg )
285
+ exit (1 )
286
+
287
+
244
288
# ---
245
289
# AWS CLIENT UTILS
246
290
# ---
@@ -357,6 +401,11 @@ def get_or_create_ddb_table(table_name: str, region: str = None):
357
401
# ---
358
402
# TF UTILS
359
403
# ---
404
+ def is_override_needed (args ) -> bool :
405
+ if any (map (lambda x : x in args , TF_PROXIED_CMDS )):
406
+ return True
407
+ return False
408
+
360
409
361
410
def parse_tf_files () -> dict :
362
411
"""Parse the local *.tf files and return a dict of <filename> -> <resource_dict>"""
@@ -432,18 +481,26 @@ def main():
432
481
print (f"Unable to determine version. See error message for details: { e } " )
433
482
exit (1 )
434
483
435
- # create TF provider config file
436
- providers = determine_provider_aliases ()
437
- config_file = create_provider_config_file (providers )
484
+ if is_override_needed (sys .argv [1 :]):
485
+ check_override_file (get_providers_file_path ())
438
486
439
- # call terraform command
440
- try :
441
- if USE_EXEC :
442
- run_tf_exec (cmd , env )
443
- else :
444
- run_tf_subprocess (cmd , env )
445
- finally :
446
- os .remove (config_file )
487
+ # create TF provider config file
488
+ providers = determine_provider_aliases ()
489
+ config_file = create_provider_config_file (providers )
490
+ else :
491
+ config_file = None
492
+
493
+ # call terraform command if not dry-run or any of the commands
494
+ if not DRY_RUN or not is_override_needed (sys .argv [1 :]):
495
+ try :
496
+ if USE_EXEC :
497
+ run_tf_exec (cmd , env )
498
+ else :
499
+ run_tf_subprocess (cmd , env )
500
+ finally :
501
+ # fall through if haven't set during dry-run
502
+ if config_file :
503
+ os .remove (config_file )
447
504
448
505
449
506
if __name__ == "__main__" :
0 commit comments