Skip to content

[Bug]: RoleAssignment stuck NotReady due to invalid observed status.atProvider.id format even when crossplane.io/external-name is correct after upgrade from provider 1.8 to 2.1 #287

@callum-stakater

Description

@callum-stakater

Is there an existing issue for this?

  • I have searched the existing issues

What I expected to happen:

If crossplane.io/external-name is corrected to the canonical AzureAD import ID format, the provider should:

 - Observe the external resource successfully using the corrected ID, and
 - Eventually reflect the canonical ID consistently in status.atProvider.id (or at least not continuously revert to an invalid/legacy format that breaks observe).

What actually happened

 - We corrected crossplane.io/external-name to the canonical ID format (e.g. /servicePrincipals/<spId>/appRoleAssignedTo/<assignmentId>).
 - However status.atProvider.id later shows (or reverts back to) the non-canonical / invalid ID format (missing the /servicePrincipals/.../appRoleAssignedTo/... prefix), and reconcile/observe fails again with the parsing error below.
 - Manually editing/removing status.atProvider.id is not durable; it is immediately reconciled back in incorrect format unless the roleassignment object is first forcefully deleted and recreated and reimported.

Affected Resource(s)

roleassignment.app.azuread.upbound.io/v1beta1

example MRs in failed state

apiVersion: app.azuread.upbound.io/v1beta1
kind: RoleAssignment
metadata:
  annotations:
    crossplane.io/composition-resource-name: foo
    crossplane.io/external-create-pending: "2025-12-16T11:02:08Z"
    crossplane.io/external-create-succeeded: "2025-12-16T11:02:08Z"
    crossplane.io/external-name: /servicePrincipals/fzzz49zz-2450-4z15-z738-0z35z7f9zz0b/appRoleAssignedTo/UzzR1ri5FkOz52DzzULUGuQzz2ia_xGzzImCpzzz
  creationTimestamp: "2025-12-16T11:02:07Z"
  finalizers:
  - finalizer.managedresource.crossplane.io
  generation: 2
  labels:
    crossplane.io/claim-name: foo
    crossplane.io/claim-namespace: foo
    crossplane.io/composite: foo
  name: foo
  ownerReferences:
  - apiVersion: iam.foo.com/v1alpha1
    blockOwnerDeletion: true
    controller: true
    kind: XServicePrincipal
    name: baa
    uid: 413271f4-a1b5-489d-9174-50e8e12345678
  resourceVersion: "83832769"
  uid: 50aa26c6-5efe-433f-b994-b76e1fcb25ff
spec:
  deletionPolicy: Delete
  forProvider:
    appRoleId: 9f8d1818-ec88-888a-bc6c-a8e88b108808
    principalObjectId: d1d14b12-b1b8-4311-1ee1-10e1ce150151
    resourceObjectId: fzzz49zz-2450-4z15-z738-0z35z7f9zz0b
  initProvider: {}
  managementPolicies:
  - '*'
  providerConfigRef:
    name: azuread-application
status:
  atProvider:
    appRoleId: 9f9d1318-ec28-589a-bc6c-a8e05b103308
    id: fzzz49zz-2450-4z15-z738-0z35z7f9zz0b/appRoleAssignment/UkvR1ri5FkOe52DgzmULUGuQag2ia_xGjswImCpttnc
    principalDisplayName: foo
    principalObjectId: q6qs4q52-q9q8-4316-9aa7-60a0aa650b50
    principalType: Group
    resourceDisplayName: foo
    resourceObjectId: fzzz49zz-2450-4z15-z738-0z35z7f9zz0b
  conditions:
  - lastTransitionTime: "2025-12-16T11:02:09Z"
    reason: Available
    status: "True"
    type: Ready
  - lastTransitionTime: "2025-12-22T11:16:34Z"
    message: |-
      observe failed: failed to observe the resource: [{0 Parsing App Role Assignment ID parsing "fzzz49zz-2450-4z15-z738-0z35z7f9zz0b/appRoleAssignment/UkvR1ri5FkOe52DgzmULUGuQag2ia_xGjswImCpttnc": parsing the ServicePrincipalIdAppRoleAssignedTo ID: the number of segments didn't match

      Expected a ServicePrincipalIdAppRoleAssignedTo ID that matched (containing 4 segments):

      > /servicePrincipals/servicePrincipalId/appRoleAssignedTo/appRoleAssignmentId

      However this value was provided (which was parsed into 0 segments):

      > fzzz49zz-2450-4z15-z738-0z35z7f9zz0b/appRoleAssignment/UkvR1ri5FkOe52DgzmULUGuQag2ia_xGjswImCpttnc

      The following Segments are expected:

      * Segment 0 - this should be the literal value "servicePrincipals"
      * Segment 1 - this should be the user specified value for this servicePrincipalId [for example "servicePrincipalId"]
      * Segment 2 - this should be the literal value "appRoleAssignedTo"
      * Segment 3 - this should be the user specified value for this appRoleAssignmentId [for example "appRoleAssignmentId"]

      The following Segments were parsed:

      * Segment 0 - not found
      * Segment 1 - not found
      * Segment 2 - not found
      * Segment 3 - not found
       [{{} id}]}]
    observedGeneration: 2
    reason: ReconcileError
    status: "False"
    type: Synced
  - lastTransitionTime: "2025-12-16T11:02:08Z"
    reason: Success
    status: "True"
    type: LastAsyncOperation

Steps to Reproduce

happened after migration of provider-azuread from 1.8.x to 2.2.0, the ID in external-name was updated to reflect the new format but the status.atProvider.id value is not being updated during refresh

What happened?

when deleting an existing roleassignmement in this broken state and letting it recreate it hangs, removing the finalizer and letting it create in conflict with the existing assignment and then again patching the external-name with the same new format results in successful import and the status.atProvider.id DOES then get populated with the correct NEW ID format

Relevant Error Output Snippet

    message: |-
      observe failed: failed to observe the resource: [{0 Parsing App Role Assignment ID parsing "fzzz49zz-2450-4z15-z738-0z35z7f9zz0b/appRoleAssignment/UkvR1ri5FkOe52DgzmULUGuQag2ia_xGjswImCpttnc": parsing the ServicePrincipalIdAppRoleAssignedTo ID: the number of segments didn't match

      Expected a ServicePrincipalIdAppRoleAssignedTo ID that matched (containing 4 segments):

      > /servicePrincipals/servicePrincipalId/appRoleAssignedTo/appRoleAssignmentId

      However this value was provided (which was parsed into 0 segments):

      > fzzz49zz-2450-4z15-z738-0z35z7f9zz0b/appRoleAssignment/UkvR1ri5FkOe52DgzmULUGuQag2ia_xGjswImCpttnc

      The following Segments are expected:

      * Segment 0 - this should be the literal value "servicePrincipals"
      * Segment 1 - this should be the user specified value for this servicePrincipalId [for example "servicePrincipalId"]
      * Segment 2 - this should be the literal value "appRoleAssignedTo"
      * Segment 3 - this should be the user specified value for this appRoleAssignmentId [for example "appRoleAssignmentId"]

      The following Segments were parsed:

      * Segment 0 - not found
      * Segment 1 - not found
      * Segment 2 - not found
      * Segment 3 - not found
       [{{} id}]}]

Crossplane Version

1.20

Provider Version

2.2.0

Kubernetes Version

No response

Kubernetes Distribution

No response

Additional Info

No response

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions