What happened?
The external-name template for aws_db_proxy_endpoint is reversed relative to the ID Terraform actually
produces. As a result, every ProxyEndpoint has its crossplane.io/external-name silently overwritten with
the proxy's name as soon as creation completes, after which the resource can never reconcile again:
refuse to update the external resource because the following update requires replacing it:
cannot change the value of the argument "db_proxy_endpoint_name"
from "my-proxy-read-only" to "my-proxy"
The endpoint exists and is healthy in AWS, and the MR reports Ready=True, so the breakage is quiet — it
shows up only as a permanently Synced=False resource.
Root cause
config/externalname.go (unchanged from at least v2.5.0 through main):
// DB proxy endpoints can be imported using the DB-PROXY-NAME/DB-PROXY-ENDPOINT-NAME
"aws_db_proxy_endpoint": config.TemplatedStringAsIdentifier(
"db_proxy_endpoint_name", "{{ .external_name }}/{{ .parameters.db_proxy_name }}"),
The comment is correct and the template contradicts it. Per the
AWS provider docs,
the ID is DB-PROXY-NAME/DB-PROXY-ENDPOINT-NAME — proxy first. The template emits external_name first.
Because TemplatedStringAsIdentifier reuses the same template to recover the external name,
.external_name sitting at index 0 with / to its right selects this branch of
GetExternalNameFromTemplated:
// {{ .external_name }}/someother
case leftSeparator == "" && rightSeparator != "":
return strings.Split(val, rightSeparator)[0], nil
so Split("my-proxy/my-proxy-read-only", "/")[0] returns my-proxy — the proxy, not the endpoint.
The same reversal breaks import in the other direction: GetIDFn builds endpoint/proxy, which is not a
valid import ID for this resource.
The docs example is example/example, where proxy and endpoint share a name — the one case in which the
reversal is invisible.
How can we reproduce it?
apiVersion: rds.aws.m.upbound.io/v1beta1
kind: ProxyEndpoint
metadata:
name: my-proxy-read-only
annotations:
crossplane.io/external-name: my-proxy-read-only
spec:
forProvider:
region: us-west-2
dbProxyName: my-proxy
targetRole: READ_ONLY
vpcSubnetIds: [subnet-a, subnet-b]
The endpoint is created correctly. Once it reaches available:
$ kubectl get proxyendpoint my-proxy-read-only \
-o jsonpath='{.metadata.annotations.crossplane\.io/external-name}{"\n"}{.status.atProvider.id}'
my-proxy
my-proxy/my-proxy-read-only
The annotation now holds the proxy name while the ID confirms the real order, and Synced goes to False
permanently.
Reproduced on two independent endpoints; both started with the correct annotation and flipped on
completion.
Suggested fix
Swap the template to match the documented ID:
"aws_db_proxy_endpoint": config.TemplatedStringAsIdentifier(
"db_proxy_endpoint_name", "{{ .parameters.db_proxy_name }}/{{ .external_name }}"),
That form lands in the leftSeparator != "" && rightSeparator == "" branch, which returns the last
segment — the endpoint name — and makes GetIDFn produce a valid import ID.
Note this will change the external name of resources already reconciled with the current template, so it
likely warrants a release note.
What environment did it happen in?
- provider-aws-rds
v2.5.0 (namespaced API, rds.aws.m.upbound.io/v1beta1)
- Neither
config/cluster/rds/config.go nor config/namespaced/rds/config.go overrides the external name
for this resource; both only set UseAsync = true
What happened?
The external-name template for
aws_db_proxy_endpointis reversed relative to the ID Terraform actuallyproduces. As a result, every
ProxyEndpointhas itscrossplane.io/external-namesilently overwritten withthe proxy's name as soon as creation completes, after which the resource can never reconcile again:
The endpoint exists and is healthy in AWS, and the MR reports
Ready=True, so the breakage is quiet — itshows up only as a permanently
Synced=Falseresource.Root cause
config/externalname.go(unchanged from at leastv2.5.0throughmain):The comment is correct and the template contradicts it. Per the
AWS provider docs,
the ID is
DB-PROXY-NAME/DB-PROXY-ENDPOINT-NAME— proxy first. The template emitsexternal_namefirst.Because
TemplatedStringAsIdentifierreuses the same template to recover the external name,.external_namesitting at index 0 with/to its right selects this branch ofGetExternalNameFromTemplated:so
Split("my-proxy/my-proxy-read-only", "/")[0]returnsmy-proxy— the proxy, not the endpoint.The same reversal breaks import in the other direction:
GetIDFnbuildsendpoint/proxy, which is not avalid import ID for this resource.
The docs example is
example/example, where proxy and endpoint share a name — the one case in which thereversal is invisible.
How can we reproduce it?
The endpoint is created correctly. Once it reaches
available:The annotation now holds the proxy name while the ID confirms the real order, and
Syncedgoes toFalsepermanently.
Reproduced on two independent endpoints; both started with the correct annotation and flipped on
completion.
Suggested fix
Swap the template to match the documented ID:
That form lands in the
leftSeparator != "" && rightSeparator == ""branch, which returns the lastsegment — the endpoint name — and makes
GetIDFnproduce a valid import ID.Note this will change the external name of resources already reconciled with the current template, so it
likely warrants a release note.
What environment did it happen in?
v2.5.0(namespaced API,rds.aws.m.upbound.io/v1beta1)config/cluster/rds/config.gonorconfig/namespaced/rds/config.gooverrides the external namefor this resource; both only set
UseAsync = true