Skip to content

[Bug]: aws_db_proxy_endpoint external-name template is reversed, wedging every ProxyEndpoint after creation #2195

Description

@itayvolo

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions