@@ -27,6 +27,7 @@ if os.path.isdir(os.path.join(PARENT_FOLDER, ".venv")):
2727from localstack_client import config # noqa: E402
2828import hcl2 # noqa: E402
2929
30+ DRY_RUN = str (os .environ .get ("DRY_RUN" )).strip ().lower () in ["1" , "true" ]
3031DEFAULT_REGION = "us-east-1"
3132DEFAULT_ACCESS_KEY = "test"
3233AWS_ENDPOINT_URL = os .environ .get ("AWS_ENDPOINT_URL" )
@@ -35,6 +36,7 @@ LOCALHOST_HOSTNAME = "localhost.localstack.cloud"
3536S3_HOSTNAME = os .environ .get ("S3_HOSTNAME" ) or f"s3.{ LOCALHOST_HOSTNAME } "
3637USE_EXEC = str (os .environ .get ("USE_EXEC" )).strip ().lower () in ["1" , "true" ]
3738TF_CMD = os .environ .get ("TF_CMD" ) or "terraform"
39+ TF_PROXIED_CMDS = ("init" , "plan" , "apply" , "destroy" )
3840LS_PROVIDERS_FILE = os .environ .get ("LS_PROVIDERS_FILE" ) or "localstack_providers_override.tf"
3941LOCALSTACK_HOSTNAME = urlparse (AWS_ENDPOINT_URL ).hostname or os .environ .get ("LOCALSTACK_HOSTNAME" ) or "localhost"
4042EDGE_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):
153155
154156 # write temporary config file
155157 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"""
159165 with open (providers_file , mode = "w" ) as fp :
160166 fp .write (tf_config )
161- return providers_file
162167
163168
164169def get_providers_file_path () -> str :
@@ -186,9 +191,12 @@ def determine_provider_aliases() -> list:
186191
187192def generate_s3_backend_config () -> str :
188193 """Generate an S3 `backend {..}` block with local endpoints, if configured"""
194+ is_tf_legacy = TF_VERSION < version .Version ("1.6" )
189195 backend_config = None
190196 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
192200 tf_configs = ensure_list (obj .get ("terraform" , []))
193201 for tf_config in tf_configs :
194202 backend_config = ensure_list (tf_config .get ("backend" ))
@@ -199,6 +207,13 @@ def generate_s3_backend_config() -> str:
199207 if not backend_config :
200208 return ""
201209
210+ legacy_endpoint_mappings = {
211+ "endpoint" : "s3" ,
212+ "iam_endpoint" : "iam" ,
213+ "sts_endpoint" : "sts" ,
214+ "dynamodb_endpoint" : "dynamodb" ,
215+ }
216+
202217 configs = {
203218 # note: default values, updated by `backend_config` further below...
204219 "bucket" : "tf-test-state" ,
@@ -213,15 +228,29 @@ def generate_s3_backend_config() -> str:
213228 "dynamodb" : get_service_endpoint ("dynamodb" ),
214229 },
215230 }
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 ()}
216245 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" ])
219249 result = TF_S3_BACKEND_CONFIG
220250 for key , value in configs .items ():
221251 if isinstance (value , bool ):
222252 value = str (value ).lower ()
223253 elif isinstance (value , dict ):
224- is_tf_legacy = not (TF_VERSION .major > 1 or (TF_VERSION .major == 1 and TF_VERSION .minor > 5 ))
225254 if key == "endpoints" and is_tf_legacy :
226255 value = textwrap .indent (
227256 text = textwrap .dedent (f"""\
@@ -241,6 +270,21 @@ def generate_s3_backend_config() -> str:
241270 return result
242271
243272
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+
244288# ---
245289# AWS CLIENT UTILS
246290# ---
@@ -357,6 +401,11 @@ def get_or_create_ddb_table(table_name: str, region: str = None):
357401# ---
358402# TF UTILS
359403# ---
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+
360409
361410def parse_tf_files () -> dict :
362411 """Parse the local *.tf files and return a dict of <filename> -> <resource_dict>"""
@@ -432,18 +481,26 @@ def main():
432481 print (f"Unable to determine version. See error message for details: { e } " )
433482 exit (1 )
434483
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 ())
438486
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 )
447504
448505
449506if __name__ == "__main__" :
0 commit comments