Skip to content

Commit 27a834d

Browse files
authored
Service endpoint alias fix (#57)
1 parent 07643c4 commit 27a834d

File tree

4 files changed

+116
-36
lines changed

4 files changed

+116
-36
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ please refer to the man pages of `terraform --help`.
4949

5050
## Change Log
5151

52+
* v0.18.2: Fix warning on aliased custom endpoint names
5253
* v0.18.1: Fix issue with not proxied commands
5354
* v0.18.0: Add `DRY_RUN` and patch S3 backend entrypoints
5455
* v0.17.1: Add `packaging` module to install requirements

bin/tflocal

Lines changed: 72 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,73 @@ terraform {
7070
}
7171
"""
7272
PROCESS = None
73+
# some services have aliases which are mutually exclusive to each other
74+
# see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/custom-service-endpoints#available-endpoint-customizations
75+
SERVICE_ALIASES = [
76+
("amp", "prometheus", "prometheusservice"),
77+
("appautoscaling", "applicationautoscaling"),
78+
("appintegrations", "appintegrationsservice"),
79+
("ce", "costexplorer"),
80+
("cloudcontrol", "cloudcontrolapi"),
81+
("cloudhsmv2", "cloudhsm"),
82+
("cognitoidp", "cognitoidentityprovider"),
83+
("configservice", "config"),
84+
("cur", "costandusagereportservice"),
85+
("deploy", "codedeploy"),
86+
("dms", "databasemigration", "databasemigrationservice"),
87+
("ds", "directoryservice"),
88+
("elasticbeanstalk", "beanstalk"),
89+
("elasticsearch", "es", "elasticsearchservice"),
90+
("elb", "elasticloadbalancing"),
91+
("elbv2", "elasticloadbalancingv2"),
92+
("events", "eventbridge", "cloudwatchevents"),
93+
("evidently", "cloudwatchevidently"),
94+
("grafana", "managedgrafana", "amg"),
95+
("inspector2", "inspectorv2"),
96+
("kafka", "msk"),
97+
("lexmodels", "lexmodelbuilding", "lexmodelbuildingservice", "lex"),
98+
("lexv2models", "lexmodelsv2"),
99+
("location", "locationservice"),
100+
("logs", "cloudwatchlog", "cloudwatchlogs"),
101+
("oam", "cloudwatchobservabilityaccessmanager"),
102+
("opensearch", "opensearchservice"),
103+
("osis", "opensearchingestion"),
104+
("rbin", "recyclebin"),
105+
("redshiftdata", "redshiftdataapiservice"),
106+
("resourcegroupstaggingapi", "resourcegroupstagging"),
107+
("rum", "cloudwatchrum"),
108+
("s3", "s3api"),
109+
("serverlessrepo", "serverlessapprepo", "serverlessapplicationrepository"),
110+
("servicecatalogappregistry", "appregistry"),
111+
("sfn", "stepfunctions"),
112+
("simpledb", "sdb"),
113+
("transcribe", "transcribeservice"),
114+
]
115+
# service names to be excluded (not yet available in TF)
116+
SERVICE_EXCLUSIONS = ["meteringmarketplace"]
117+
# maps services to be replaced with alternative names
118+
# skip services which do not have equivalent endpoint overrides
119+
# see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/custom-service-endpoints
120+
SERVICE_REPLACEMENTS = {
121+
"apigatewaymanagementapi": "",
122+
"appconfigdata": "",
123+
"ce": "costexplorer",
124+
"dynamodbstreams": "",
125+
"edge": "",
126+
"emrserverless": "",
127+
"iotdata": "",
128+
"ioteventsdata": "",
129+
"iotjobsdata": "",
130+
"iotwireless": "",
131+
"logs": "cloudwatchlogs",
132+
"mediastoredata": "",
133+
"qldbsession": "",
134+
"rdsdata": "",
135+
"sagemakerruntime": "",
136+
"support": "",
137+
"timestream": "",
138+
"timestreamquery": "",
139+
}
73140

74141

75142
# ---
@@ -79,40 +146,17 @@ PROCESS = None
79146
def create_provider_config_file(provider_aliases=None):
80147
provider_aliases = provider_aliases or []
81148

82-
# maps services to be replaced with alternative names
83-
# skip services which do not have equivalent endpoint overrides
84-
# see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/custom-service-endpoints
85-
service_replaces = {
86-
"apigatewaymanagementapi": "",
87-
"appconfigdata": "",
88-
"ce": "costexplorer",
89-
"dynamodbstreams": "",
90-
"edge": "",
91-
"emrserverless": "",
92-
"iotdata": "",
93-
"ioteventsdata": "",
94-
"iotjobsdata": "",
95-
"iotwireless": "",
96-
"logs": "cloudwatchlogs",
97-
"mediastoredata": "",
98-
"qldbsession": "",
99-
"rdsdata": "",
100-
"sagemakerruntime": "",
101-
"support": "",
102-
"timestream": "",
103-
"timestreamquery": "",
104-
}
105-
# service names to be excluded (not yet available in TF)
106-
service_excludes = ["meteringmarketplace"]
149+
# Force service alias replacements
150+
SERVICE_REPLACEMENTS.update({alias: alias_pairs[0] for alias_pairs in SERVICE_ALIASES for alias in alias_pairs if alias != alias_pairs[0]})
107151

108152
# create list of service names
109153
services = list(config.get_service_ports())
110-
services = [srvc for srvc in services if srvc not in service_excludes]
154+
services = [srvc for srvc in services if srvc not in SERVICE_EXCLUSIONS]
111155
services = [s.replace("-", "") for s in services]
112-
for old, new in service_replaces.items():
156+
for old, new in SERVICE_REPLACEMENTS.items():
113157
try:
114158
services.remove(old)
115-
if new:
159+
if new and new not in services:
116160
services.append(new)
117161
except ValueError:
118162
pass

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = terraform-local
3-
version = 0.18.1
3+
version = 0.18.2
44
url = https://github.com/localstack/terraform-local
55
author = LocalStack Team
66
author_email = info@localstack.cloud

tests/test_apply.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,9 @@ def test_access_key_override_by_provider(monkeypatch):
9999

100100

101101
def test_s3_path_addressing():
102-
bucket_name = f"bucket.{short_uid()}"
102+
# Temporarily change "." -> "-" as aws provider >5.55.0 fails with LocalStack
103+
# by calling aws-global pseudo region at S3 bucket creation instead of us-east-1
104+
bucket_name = f"bucket-{short_uid()}"
103105
config = """
104106
resource "aws_s3_bucket" "test-bucket" {
105107
bucket = "%s"
@@ -164,7 +166,9 @@ def test_provider_aliases():
164166
def test_s3_backend():
165167
state_bucket = f"tf-state-{short_uid()}"
166168
state_table = f"tf-state-{short_uid()}"
167-
bucket_name = f"bucket.{short_uid()}"
169+
# Temporarily change "." -> "-" as aws provider >5.55.0 fails with LocalStack
170+
# by calling aws-global pseudo region at S3 bucket creation instead of us-east-1
171+
bucket_name = f"bucket-{short_uid()}"
168172
config = """
169173
terraform {
170174
backend "s3" {
@@ -203,7 +207,9 @@ def test_dry_run(monkeypatch):
203207
monkeypatch.setenv("DRY_RUN", "1")
204208
state_bucket = "tf-state-dry-run"
205209
state_table = "tf-state-dry-run"
206-
bucket_name = "bucket.dry-run"
210+
# Temporarily change "." -> "-" as aws provider >5.55.0 fails with LocalStack
211+
# by calling aws-global pseudo region at S3 bucket creation instead of us-east-1
212+
bucket_name = "bucket-dry-run"
207213
config = """
208214
terraform {
209215
backend "s3" {
@@ -224,7 +230,7 @@ def test_dry_run(monkeypatch):
224230
override_file = os.path.join(temp_dir, "localstack_providers_override.tf")
225231
assert check_override_file_exists(override_file)
226232

227-
assert check_override_file_content(override_file, is_legacy=is_legacy_tf)
233+
assert check_override_file_backend_content(override_file, is_legacy=is_legacy_tf)
228234

229235
# assert that bucket with state file exists
230236
s3 = client("s3", region_name="us-east-2")
@@ -243,6 +249,33 @@ def test_dry_run(monkeypatch):
243249
s3.head_bucket(Bucket=bucket_name)
244250

245251

252+
def test_service_endpoint_alias_replacements(monkeypatch):
253+
monkeypatch.setenv("DRY_RUN", "1")
254+
config = """
255+
provider "aws" {
256+
region = "eu-west-1"
257+
}"""
258+
259+
temp_dir = deploy_tf_script(config, cleanup=False, user_input="yes")
260+
override_file = os.path.join(temp_dir, "localstack_providers_override.tf")
261+
assert check_override_file_content(override_file)
262+
rmtree(temp_dir)
263+
264+
265+
def check_override_file_content(override_file):
266+
try:
267+
with open(override_file, "r") as fp:
268+
result = hcl2.load(fp)
269+
result = result["provider"][0]["aws"]
270+
except Exception as e:
271+
raise Exception(f'Unable to parse "{override_file}" as HCL file: {e}')
272+
273+
endpoints = result["endpoints"][0]
274+
if "config" in endpoints and "configservice" in endpoints:
275+
return False
276+
return True
277+
278+
246279
@pytest.mark.parametrize("endpoints", [
247280
'',
248281
'endpoint = "http://s3-localhost.localstack.cloud:4566"',
@@ -255,7 +288,9 @@ def test_s3_backend_endpoints_merge(monkeypatch, endpoints: str):
255288
monkeypatch.setenv("DRY_RUN", "1")
256289
state_bucket = "tf-state-merge"
257290
state_table = "tf-state-merge"
258-
bucket_name = "bucket.merge"
291+
# Temporarily change "." -> "-" as aws provider >5.55.0 fails with LocalStack
292+
# by calling aws-global pseudo region at S3 bucket creation instead of us-east-1
293+
bucket_name = "bucket-merge"
259294
config = """
260295
terraform {
261296
backend "s3" {
@@ -279,15 +314,15 @@ def test_s3_backend_endpoints_merge(monkeypatch, endpoints: str):
279314
temp_dir = deploy_tf_script(config, cleanup=False, user_input="yes")
280315
override_file = os.path.join(temp_dir, "localstack_providers_override.tf")
281316
assert check_override_file_exists(override_file)
282-
assert check_override_file_content(override_file, is_legacy=is_legacy_tf)
317+
assert check_override_file_backend_content(override_file, is_legacy=is_legacy_tf)
283318
rmtree(temp_dir)
284319

285320

286321
def check_override_file_exists(override_file):
287322
return os.path.isfile(override_file)
288323

289324

290-
def check_override_file_content(override_file, is_legacy: bool = False):
325+
def check_override_file_backend_content(override_file, is_legacy: bool = False):
291326
legacy_options = (
292327
"endpoint",
293328
"iam_endpoint",

0 commit comments

Comments
 (0)