Skip to content

Commit 30a1d89

Browse files
committed
Fix API Gateway v1 external name oscillation causing perpetual delete/recreate
The 7 API Gateway v1 resources using FormattedIdentifierFromProvider("/", ...) had a mismatch between GetExternalNameFn (which reads tfstate["id"]) and GetIDFn (which joins parameters with "/"). The Terraform AWS provider stores internal IDs in a different format (prefixed, dash-separated) than the import format (slash-separated), causing the external-name annotation to change every reconciliation and triggering perpetual delete/recreate cycles and AWS 429 throttling. Fixes: #1974 Signed-off-by: Aditya Menon <amenon@canarytechnologies.com>
1 parent d6c6c20 commit 30a1d89

File tree

1 file changed

+53
-7
lines changed

1 file changed

+53
-7
lines changed

config/externalname.go

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -272,17 +272,17 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
272272
// API Gateway domain names can be imported using their name
273273
"aws_api_gateway_domain_name": config.IdentifierFromProvider,
274274
// aws_api_gateway_gateway_response can be imported using REST-API-ID/RESPONSE-TYPE
275-
"aws_api_gateway_gateway_response": FormattedIdentifierFromProvider("/", "rest_api_id", "response_type"),
275+
"aws_api_gateway_gateway_response": apiGatewayFormattedIdentifier("aggr", "rest_api_id", "response_type"),
276276
// aws_api_gateway_integration can be imported using REST-API-ID/RESOURCE-ID/HTTP-METHOD
277-
"aws_api_gateway_integration": FormattedIdentifierFromProvider("/", "rest_api_id", "resource_id", "http_method"),
277+
"aws_api_gateway_integration": apiGatewayFormattedIdentifier("agi", "rest_api_id", "resource_id", "http_method"),
278278
// aws_api_gateway_integration_response can be imported using REST-API-ID/RESOURCE-ID/HTTP-METHOD/STATUS-CODE
279-
"aws_api_gateway_integration_response": FormattedIdentifierFromProvider("/", "rest_api_id", "resource_id", "http_method", "status_code"),
279+
"aws_api_gateway_integration_response": apiGatewayFormattedIdentifier("agir", "rest_api_id", "resource_id", "http_method", "status_code"),
280280
// aws_api_gateway_method can be imported using REST-API-ID/RESOURCE-ID/HTTP-METHOD
281-
"aws_api_gateway_method": FormattedIdentifierFromProvider("/", "rest_api_id", "resource_id", "http_method"),
281+
"aws_api_gateway_method": apiGatewayFormattedIdentifier("agm", "rest_api_id", "resource_id", "http_method"),
282282
// aws_api_gateway_method_response can be imported using REST-API-ID/RESOURCE-ID/HTTP-METHOD/STATUS-CODE
283-
"aws_api_gateway_method_response": FormattedIdentifierFromProvider("/", "rest_api_id", "resource_id", "http_method", "status_code"),
283+
"aws_api_gateway_method_response": apiGatewayFormattedIdentifier("agmr", "rest_api_id", "resource_id", "http_method", "status_code"),
284284
// aws_api_gateway_method_settings can be imported using REST-API-ID/STAGE-NAME/METHOD-PATH
285-
"aws_api_gateway_method_settings": FormattedIdentifierFromProvider("/", "rest_api_id", "stage_name", "method_path"),
285+
"aws_api_gateway_method_settings": apiGatewayFormattedIdentifier("", "rest_api_id", "stage_name", "method_path"),
286286
// aws_api_gateway_model can be imported using REST-API-ID/NAME
287287
"aws_api_gateway_model": config.IdentifierFromProvider,
288288
// aws_api_gateway_request_validator can be imported using REST-API-ID/REQUEST-VALIDATOR-ID
@@ -294,7 +294,7 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
294294
// aws_api_gateway_rest_api_policy can be imported by using the REST API ID
295295
"aws_api_gateway_rest_api_policy": FormattedIdentifierFromProvider("", "rest_api_id"),
296296
// aws_api_gateway_stage can be imported using REST-API-ID/STAGE-NAME
297-
"aws_api_gateway_stage": FormattedIdentifierFromProvider("/", "rest_api_id", "stage_name"),
297+
"aws_api_gateway_stage": apiGatewayFormattedIdentifier("ags", "rest_api_id", "stage_name"),
298298
// AWS API Gateway Usage Plan can be imported using the id
299299
"aws_api_gateway_usage_plan": config.IdentifierFromProvider,
300300
// AWS API Gateway Usage Plan Key can be imported using the USAGE-PLAN-ID/USAGE-PLAN-KEY-ID
@@ -3376,6 +3376,52 @@ func apiGatewayAccount() config.ExternalName {
33763376
return e
33773377
}
33783378

3379+
// apiGatewayFormattedIdentifier constructs external name configs for API Gateway
3380+
// apiGatewayFormattedIdentifier configures external name for API Gateway
3381+
// v1 resources where the Terraform internal ID format differs from the import
3382+
// format. GetExternalNameFn reads tfstate fields directly (e.g.,
3383+
// tfstate["rest_api_id"]) and joins them with "/" separators. GetIDFn reads
3384+
// parameters and joins them with "-" separators, optionally adding a prefix.
3385+
// This ensures consistent values to prevent external name oscillation.
3386+
func apiGatewayFormattedIdentifier(prefix string, keys ...string) config.ExternalName {
3387+
e := config.IdentifierFromProvider
3388+
e.GetExternalNameFn = func(tfstate map[string]interface{}) (string, error) {
3389+
vals := make([]string, len(keys))
3390+
for i, key := range keys {
3391+
val, ok := tfstate[key]
3392+
if !ok {
3393+
return "", errors.Errorf("parameter %q cannot be empty", key)
3394+
}
3395+
s, ok := val.(string)
3396+
if !ok {
3397+
return "", errors.Errorf("parameter %q must be a string", key)
3398+
}
3399+
vals[i] = s
3400+
}
3401+
return strings.Join(vals, "/"), nil
3402+
}
3403+
e.GetIDFn = func(_ context.Context, _ string, parameters map[string]interface{}, _ map[string]interface{}) (string, error) {
3404+
vals := make([]string, len(keys))
3405+
for i, key := range keys {
3406+
val, ok := parameters[key]
3407+
if !ok {
3408+
return "", errors.Errorf("parameter %q cannot be empty", key)
3409+
}
3410+
s, ok := val.(string)
3411+
if !ok {
3412+
return "", errors.Errorf("parameter %q must be a string", key)
3413+
}
3414+
vals[i] = s
3415+
}
3416+
id := strings.Join(vals, "-")
3417+
if prefix != "" {
3418+
return prefix + "-" + id, nil
3419+
}
3420+
return id, nil
3421+
}
3422+
return e
3423+
}
3424+
33793425
// fullARNTemplate builds a templated string for constructing a terraform id component which is an ARN, which includes
33803426
// the aws partition, service, region, account id, and resource. This is by far the most common form of ARN.
33813427
// e.g. arn:aws:ec2:ap-south-1:123456789012:instance/i-1234567890ab

0 commit comments

Comments
 (0)