@@ -13,8 +13,12 @@ import os
13
13
import sys
14
14
import glob
15
15
import subprocess
16
+ import json
17
+ import textwrap
16
18
19
+ from packaging import version
17
20
from urllib .parse import urlparse
21
+ from typing import Optional
18
22
19
23
PARENT_FOLDER = os .path .realpath (os .path .join (os .path .dirname (__file__ ), ".." ))
20
24
if os .path .isdir (os .path .join (PARENT_FOLDER , ".venv" )):
@@ -34,14 +38,15 @@ TF_CMD = os.environ.get("TF_CMD") or "terraform"
34
38
LS_PROVIDERS_FILE = os .environ .get ("LS_PROVIDERS_FILE" ) or "localstack_providers_override.tf"
35
39
LOCALSTACK_HOSTNAME = urlparse (AWS_ENDPOINT_URL ).hostname or os .environ .get ("LOCALSTACK_HOSTNAME" ) or "localhost"
36
40
EDGE_PORT = int (urlparse (AWS_ENDPOINT_URL ).port or os .environ .get ("EDGE_PORT" ) or 4566 )
41
+ TF_VERSION : Optional [version .Version ] = None
37
42
TF_PROVIDER_CONFIG = """
38
43
provider "aws" {
39
44
access_key = "<access_key>"
40
45
secret_key = "test"
41
46
skip_credentials_validation = true
42
47
skip_metadata_api_check = true
43
48
<configs>
44
- endpoints {
49
+ endpoints {
45
50
<endpoints>
46
51
}
47
52
}
@@ -56,10 +61,7 @@ terraform {
56
61
57
62
access_key = "test"
58
63
secret_key = "test"
59
- endpoint = "<s3_endpoint>"
60
- iam_endpoint = "<iam_endpoint>"
61
- sts_endpoint = "<sts_endpoint>"
62
- dynamodb_endpoint = "<dynamodb_endpoint>"
64
+ <endpoints>
63
65
skip_credentials_validation = true
64
66
skip_metadata_api_check = true
65
67
}
@@ -126,7 +128,7 @@ def create_provider_config_file(provider_aliases=None):
126
128
"<access_key>" ,
127
129
get_access_key (provider ) if CUSTOMIZE_ACCESS_KEY else DEFAULT_ACCESS_KEY
128
130
)
129
- endpoints = "\n " .join ([f'{ s } = "{ get_service_endpoint (s )} "' for s in services ])
131
+ endpoints = "\n " .join ([f' { s } = "{ get_service_endpoint (s )} "' for s in services ])
130
132
provider_config = provider_config .replace ("<endpoints>" , endpoints )
131
133
additional_configs = []
132
134
if use_s3_path_style ():
@@ -139,7 +141,7 @@ def create_provider_config_file(provider_aliases=None):
139
141
region = provider .get ("region" ) or get_region ()
140
142
if isinstance (region , list ):
141
143
region = region [0 ]
142
- additional_configs += [f' region = "{ region } "' ]
144
+ additional_configs += [f'region = "{ region } "' ]
143
145
provider_config = provider_config .replace ("<configs>" , "\n " .join (additional_configs ))
144
146
provider_configs .append (provider_config )
145
147
@@ -203,17 +205,38 @@ def generate_s3_backend_config() -> str:
203
205
"key" : "terraform.tfstate" ,
204
206
"dynamodb_table" : "tf-test-state" ,
205
207
"region" : get_region (),
206
- "s3_endpoint" : get_service_endpoint ("s3" ),
207
- "iam_endpoint" : get_service_endpoint ("iam" ),
208
- "sts_endpoint" : get_service_endpoint ("sts" ),
209
- "dynamodb_endpoint" : get_service_endpoint ("dynamodb" ),
208
+ "endpoints" : {
209
+ "s3" : get_service_endpoint ("s3" ),
210
+ "iam" : get_service_endpoint ("iam" ),
211
+ "sso" : get_service_endpoint ("sso" ),
212
+ "sts" : get_service_endpoint ("sts" ),
213
+ "dynamodb" : get_service_endpoint ("dynamodb" ),
214
+ },
210
215
}
211
216
configs .update (backend_config )
212
217
get_or_create_bucket (configs ["bucket" ])
213
218
get_or_create_ddb_table (configs ["dynamodb_table" ], region = configs ["region" ])
214
219
result = TF_S3_BACKEND_CONFIG
215
220
for key , value in configs .items ():
216
- value = str (value ).lower () if isinstance (value , bool ) else str (value )
221
+ if isinstance (value , bool ):
222
+ value = str (value ).lower ()
223
+ elif isinstance (value , dict ):
224
+ is_tf_legacy = not (TF_VERSION .major > 1 or (TF_VERSION .major == 1 and TF_VERSION .minor > 5 ))
225
+ if key == "endpoints" and is_tf_legacy :
226
+ value = textwrap .indent (
227
+ text = textwrap .dedent (f"""\
228
+ endpoint = "{ value ["s3" ]} "
229
+ iam_endpoint = "{ value ["iam" ]} "
230
+ sts_endpoint = "{ value ["sts" ]} "
231
+ dynamodb_endpoint = "{ value ["dynamodb" ]} "
232
+ """ ),
233
+ prefix = " " * 4 )
234
+ else :
235
+ value = textwrap .indent (
236
+ text = f"{ key } = {{\n " + "\n " .join ([f' { k } = "{ v } "' for k , v in value .items ()]) + "\n }" ,
237
+ prefix = " " * 4 )
238
+ else :
239
+ value = str (value )
217
240
result = result .replace (f"<{ key } >" , value )
218
241
return result
219
242
@@ -347,6 +370,12 @@ def parse_tf_files() -> dict:
347
370
return result
348
371
349
372
373
+ def get_tf_version (env ):
374
+ global TF_VERSION
375
+ output = subprocess .run ([f"{ TF_CMD } " , "version" , "-json" ], env = env , check = True , capture_output = True ).stdout .decode ("utf-8" )
376
+ TF_VERSION = version .parse (json .loads (output )["terraform_version" ])
377
+
378
+
350
379
def run_tf_exec (cmd , env ):
351
380
"""Run terraform using os.exec - can be useful as it does not require any I/O
352
381
handling for stdin/out/err. Does *not* allow us to perform any cleanup logic."""
@@ -395,6 +424,14 @@ def main():
395
424
env = dict (os .environ )
396
425
cmd = [TF_CMD ] + sys .argv [1 :]
397
426
427
+ try :
428
+ get_tf_version (env )
429
+ if not TF_VERSION :
430
+ raise ValueError
431
+ except (FileNotFoundError , ValueError ) as e :
432
+ print (f"Unable to determine version. See error message for details: { e } " )
433
+ exit (1 )
434
+
398
435
# create TF provider config file
399
436
providers = determine_provider_aliases ()
400
437
config_file = create_provider_config_file (providers )
0 commit comments