Skip to content

Commit e623ff6

Browse files
authored
Introduce proper backend config merge (#61)
1 parent 62b5cc1 commit e623ff6

File tree

4 files changed

+69
-26
lines changed

4 files changed

+69
-26
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ please refer to the man pages of `terraform --help`.
5050

5151
## Change Log
5252

53+
* v0.20.0: Fix S3 backend option merging
5354
* v0.19.0: Add `SKIP_ALIASES` configuration environment variable
5455
* v0.18.2: Fix warning on aliased custom endpoint names
5556
* v0.18.1: Fix issue with not proxied commands

bin/tflocal

+20-22
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,7 @@ provider "aws" {
5555
"""
5656
TF_S3_BACKEND_CONFIG = """
5757
terraform {
58-
backend "s3" {
59-
region = "<region>"
60-
bucket = "<bucket>"
61-
key = "<key>"
62-
dynamodb_table = "<dynamodb_table>"
63-
64-
access_key = "test"
65-
secret_key = "test"
66-
<endpoints>
67-
skip_credentials_validation = true
68-
skip_metadata_api_check = true
58+
backend "s3" {<configs>
6959
}
7060
}
7161
"""
@@ -265,6 +255,10 @@ def generate_s3_backend_config() -> str:
265255
"key": "terraform.tfstate",
266256
"dynamodb_table": "tf-test-state",
267257
"region": get_region(),
258+
"skip_credentials_validation": True,
259+
"skip_metadata_api_check": True,
260+
"secret_key": "test",
261+
268262
"endpoints": {
269263
"s3": get_service_endpoint("s3"),
270264
"iam": get_service_endpoint("iam"),
@@ -278,40 +272,44 @@ def generate_s3_backend_config() -> str:
278272
print("Warning: Unsupported backend option(s) detected (`endpoints`). Please make sure you always use the corresponding options to your Terraform version.")
279273
exit(1)
280274
for legacy_endpoint, endpoint in legacy_endpoint_mappings.items():
275+
if legacy_endpoint in backend_config and backend_config.get("endpoints") and endpoint in backend_config["endpoints"]:
276+
del backend_config[legacy_endpoint]
277+
continue
281278
if legacy_endpoint in backend_config and (not backend_config.get("endpoints") or endpoint not in backend_config["endpoints"]):
282279
if not backend_config.get("endpoints"):
283280
backend_config["endpoints"] = {}
284281
backend_config["endpoints"].update({endpoint: backend_config[legacy_endpoint]})
282+
del backend_config[legacy_endpoint]
285283
# Add any missing default endpoints
286284
if backend_config.get("endpoints"):
287285
backend_config["endpoints"] = {
288286
k: backend_config["endpoints"].get(k) or v
289287
for k, v in configs["endpoints"].items()}
288+
backend_config["access_key"] = get_access_key(backend_config) if CUSTOMIZE_ACCESS_KEY else DEFAULT_ACCESS_KEY
290289
configs.update(backend_config)
291290
if not DRY_RUN:
292291
get_or_create_bucket(configs["bucket"])
293292
get_or_create_ddb_table(configs["dynamodb_table"], region=configs["region"])
294293
result = TF_S3_BACKEND_CONFIG
295-
for key, value in configs.items():
294+
config_options = ""
295+
for key, value in sorted(configs.items()):
296296
if isinstance(value, bool):
297297
value = str(value).lower()
298298
elif isinstance(value, dict):
299299
if key == "endpoints" and is_tf_legacy:
300-
value = textwrap.indent(
301-
text=textwrap.dedent(f"""\
302-
endpoint = "{value["s3"]}"
303-
iam_endpoint = "{value["iam"]}"
304-
sts_endpoint = "{value["sts"]}"
305-
dynamodb_endpoint = "{value["dynamodb"]}"
306-
"""),
307-
prefix=" " * 4)
300+
for legacy_endpoint, endpoint in legacy_endpoint_mappings.items():
301+
config_options += f'\n {legacy_endpoint} = "{configs[key][endpoint]}"'
302+
continue
308303
else:
309304
value = textwrap.indent(
310305
text=f"{key} = {{\n" + "\n".join([f' {k} = "{v}"' for k, v in value.items()]) + "\n}",
311306
prefix=" " * 4)
307+
config_options += f"\n{value}"
308+
continue
312309
else:
313-
value = str(value)
314-
result = result.replace(f"<{key}>", value)
310+
value = f'"{str(value)}"'
311+
config_options += f'\n {key} = {value}'
312+
result = result.replace("<configs>", config_options)
315313
return result
316314

317315

setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = terraform-local
3-
version = 0.19.0
3+
version = 0.20.0
44
url = https://github.com/localstack/terraform-local
55
author = LocalStack Team
66
author_email = [email protected]

tests/test_apply.py

+47-3
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ def test_dry_run(monkeypatch):
230230
override_file = os.path.join(temp_dir, "localstack_providers_override.tf")
231231
assert check_override_file_exists(override_file)
232232

233-
assert check_override_file_backend_content(override_file, is_legacy=is_legacy_tf)
233+
assert check_override_file_backend_endpoints_content(override_file, is_legacy=is_legacy_tf)
234234

235235
# assert that bucket with state file exists
236236
s3 = client("s3", region_name="us-east-2")
@@ -276,6 +276,50 @@ def check_override_file_content(override_file):
276276
return True
277277

278278

279+
def test_s3_backend_configs_merge(monkeypatch):
280+
monkeypatch.setenv("DRY_RUN", "1")
281+
state_bucket = "tf-state-conf-merge"
282+
state_table = "tf-state-conf-merge"
283+
# Temporarily change "." -> "-" as aws provider >5.55.0 fails with LocalStack
284+
# by calling aws-global pseudo region at S3 bucket creation instead of us-east-1
285+
bucket_name = "bucket-conf-merge"
286+
config = """
287+
terraform {
288+
backend "s3" {
289+
bucket = "%s"
290+
key = "terraform.tfstate"
291+
dynamodb_table = "%s"
292+
region = "us-east-2"
293+
skip_credentials_validation = true
294+
encryption = true
295+
use_path_style = true
296+
acl = "bucket-owner-full-control"
297+
}
298+
}
299+
resource "aws_s3_bucket" "test-bucket" {
300+
bucket = "%s"
301+
}
302+
""" % (state_bucket, state_table, bucket_name)
303+
temp_dir = deploy_tf_script(config, cleanup=False, user_input="yes")
304+
override_file = os.path.join(temp_dir, "localstack_providers_override.tf")
305+
assert check_override_file_exists(override_file)
306+
assert check_override_file_backend_extra_content(override_file)
307+
rmtree(temp_dir)
308+
309+
310+
def check_override_file_backend_extra_content(override_file):
311+
try:
312+
with open(override_file, "r") as fp:
313+
result = hcl2.load(fp)
314+
result = result["terraform"][0]["backend"][0]["s3"]
315+
except Exception as e:
316+
raise Exception(f'Unable to parse "{override_file}" as HCL file: {e}')
317+
318+
return result.get("use_path_style") is True and \
319+
result.get("encryption") is True and \
320+
result.get("acl") == "bucket-owner-full-control"
321+
322+
279323
@pytest.mark.parametrize("endpoints", [
280324
'',
281325
'endpoint = "http://s3-localhost.localstack.cloud:4566"',
@@ -314,15 +358,15 @@ def test_s3_backend_endpoints_merge(monkeypatch, endpoints: str):
314358
temp_dir = deploy_tf_script(config, cleanup=False, user_input="yes")
315359
override_file = os.path.join(temp_dir, "localstack_providers_override.tf")
316360
assert check_override_file_exists(override_file)
317-
assert check_override_file_backend_content(override_file, is_legacy=is_legacy_tf)
361+
assert check_override_file_backend_endpoints_content(override_file, is_legacy=is_legacy_tf)
318362
rmtree(temp_dir)
319363

320364

321365
def check_override_file_exists(override_file):
322366
return os.path.isfile(override_file)
323367

324368

325-
def check_override_file_backend_content(override_file, is_legacy: bool = False):
369+
def check_override_file_backend_endpoints_content(override_file, is_legacy: bool = False):
326370
legacy_options = (
327371
"endpoint",
328372
"iam_endpoint",

0 commit comments

Comments
 (0)