Skip to content

feat: add domainIdRef/domainIdSelector support to identity resources - #176

Merged
Avatos merged 1 commit into
crossplane-contrib:mainfrom
JRaver:feature/add-domain-id-ref
Oct 7, 2026
Merged

Avatos merged 1 commit into
crossplane-contrib:mainfrom
JRaver:feature/add-domain-id-ref

Conversation

@JRaver

@JRaver JRaver commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Description of your changes

Add domainIdRef and domainIdSelector support to all identity resources that have a domainId field:

  • ProjectV3
  • UserV3
  • GroupV3
  • RoleV3
  • RoleAssignmentV3
  • InheritRoleAssignmentV3
    In OpenStack, a domain is represented as a ProjectV3 with isDomain: true. A custom ExtractDomainID() extractor ensures that references resolve only when the target ProjectV3 is actually a domain. Referencing a regular project returns an empty value and blocks resource creation.
    On top of that, an initializer-based validator provides a clear, actionable error message:
spec.forProvider.domainIdRef: referenced ProjectV3 "my-project" is a project, not a domain:
reference a ProjectV3 created with isDomain: true

instead of the generic referenced field was empty (referenced resource may not yet be ready).
Both domainIdRef (direct name reference) and domainIdSelector (label-based selection) are validated.

Usage example

# Domain
apiVersion: identity.openstack.crossplane.io/v1alpha1
kind: ProjectV3
metadata:
  name: example-domain
spec:
  forProvider:
    isDomain: true
    name: example-domain
---
# Project referencing that domain
apiVersion: identity.openstack.crossplane.io/v1alpha1
kind: ProjectV3
metadata:
  name: project-in-domain
spec:
  forProvider:
    name: project-in-domain
    domainIdRef:
      name: example-domain

I have:

  • Read and followed Crossplane's [contribution process].
  • Run make submodules; make reviewable test to ensure this PR is ready for review.

How has this code been tested

  • go build ./... — compiles successfully
  • go test -v ./internal/initializer/domain/ — 10 unit tests covering: ref to domain (pass), ref to project (error with clear message), ref to non-existent object (silent, left to resolver), already resolved value (skip), selector to domain (pass), selector to project (error), selector with no match (silent), initProvider ref validation, isDomain detected from status.atProvider
  • make generate — verified generated types (DomainIDRef/DomainIDSelector fields), resolvers (ExtractDomainID() usage), deepcopy, controllers (InitializerFns wired), and CRDs
  • Tested manually on a live OpenStack cluster: domain creation, project with domainIdRef to domain (resolves), project with domainIdRef to a regular project (clear error in status and events)
    [contribution process]: https://git.io/fj2m9

@JRaver
JRaver requested a review from Avatos as a code owner August 22, 2026 14:10
@JRaver

JRaver commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Hello, @Avatos! Could you please review this PR when you will have time, thank you

@JRaver
JRaver force-pushed the feature/add-domain-id-ref branch from 0febfc9 to 119a54a Compare August 26, 2026 11:27
@JRaver

JRaver commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Hello @Avatos just a reminder, please review this PR. Thanks

@Avatos

Avatos commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Hey @JRaver,
I'm sorry for the delayed response.

First of all, thanks for this contribution!

I identified one potential issue with the domainIdRef and domainIdSelector for namespaced resources.

Assuming we have these two resources:

apiVersion: identity.openstack.m.crossplane.io/v1alpha1
kind: UserV3
metadata:
  name: example-user
  namespace: application
spec:
  forProvider:
    domainIdRef:
      name: example-domain
      namespace: infrastructure
apiVersion: identity.openstack.m.crossplane.io/v1alpha1
kind: ProjectV3
metadata:
  name: example-domain
  namespace: application
spec:
  forProvider:
    isDomain: false

The custom validator's checkRef and selectProject functions always check domainIdRef in the referencing resource’s namespace, ignoring an explicitly configured target namespace.
If that lookup fails it silently continues and the standard resolver later uses the correct namespace, but if a same-named non-domain project exists locally (as in the ProjectV3 example above), the validator falsely rejects the valid cross-namespace reference before standard resolution runs.

It would be great if you were able to test this scenario on an OpenStack cluster.

Signed-off-by: Yevhen Artemenko <eartemenko@playtika.com>
@JRaver
JRaver force-pushed the feature/add-domain-id-ref branch from 119a54a to 93d5709 Compare September 11, 2026 14:23
@JRaver

JRaver commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for your comment @Avatos . I add this functionality and provide my tests on our Openstack. Please take a look one more time.

Testing: cross-namespace domainIdRef validation

Setup

Created two namespaces for namespaced resources:

$ kubectl get ns | grep infra
namespaced-infra                    Active   21m
namespaced-object-infra             Active   20m

Step 1: Create domain + project targets

# Cluster-scoped domain
apiVersion: identity.openstack.crossplane.io/v1alpha1
kind: ProjectV3
metadata:
  name: cluster-domain
spec:
  forProvider:
    isDomain: true
    name: cluster-domain
---
# Namespaced domain
apiVersion: identity.openstack.m.crossplane.io/v1alpha1
kind: ProjectV3
metadata:
  name: namespaced-domain
  namespace: namespaced-infra
spec:
  forProvider:
    isDomain: true
    name: namespaced-domain
---
# Namespaced regular project (NOT a domain)
apiVersion: identity.openstack.m.crossplane.io/v1alpha1
kind: ProjectV3
metadata:
  name: namespaced-project
  namespace: namespaced-infra
spec:
  forProvider:
    isDomain: false
    name: namespaced-project

All synced and ready:

$ kubectl get projectv3,projectv3s.identity.openstack.m -A
NAME                                                        SYNCED   READY   EXTERNAL-NAME                      AGE
projectv3.identity.openstack.crossplane.io/cluster-domain   True     True    83d1758a947a42c88594daa46b2c613a   15m

NAMESPACE          NAME                                                              SYNCED   READY   EXTERNAL-NAME                      AGE
namespaced-infra   projectv3.identity.openstack.m.crossplane.io/namespaced-domain    True     True    c10a771ca0bd48d381b5d71b600b70a4   15m
namespaced-infra   projectv3.identity.openstack.m.crossplane.io/namespaced-project   True     True    6d509c20df5345d18c81747ef4d3bfc9   2m47s

Step 2: Create users with different domainIdRef scenarios

# ✅ Cross-namespace ref to domain (explicit namespace)
apiVersion: identity.openstack.m.crossplane.io/v1alpha1
kind: UserV3
metadata:
  name: namespaced-user
  namespace: namespaced-object-infra
spec:
  forProvider:
    name: namespaced-user
    domainIdRef:
      name: namespaced-domain
      namespace: namespaced-infra
---
# ❌ No explicit namespace — will look for domain in own NS and fail
apiVersion: identity.openstack.m.crossplane.io/v1alpha1
kind: UserV3
metadata:
  name: wrong-namespaced-user
  namespace: namespaced-object-infra
spec:
  forProvider:
    name: wrong-namespaced-user
    domainIdRef:
      name: namespaced-domain
---
# ❌ Cross-namespace ref to a regular project (not a domain)
apiVersion: identity.openstack.m.crossplane.io/v1alpha1
kind: UserV3
metadata:
  name: namespaced-user-on-project
  namespace: namespaced-object-infra
spec:
  forProvider:
    name: namespaced-user-on-project
    domainIdRef:
      name: namespaced-project
      namespace: namespaced-infra
User Namespace domainIdRef Expected
namespaced-user namespaced-object-infra name: namespaced-domain, namespace: namespaced-infra ✅ Cross-NS ref to domain → resolves
wrong-namespaced-user namespaced-object-infra name: namespaced-domain (no namespace) ❌ Domain not found in own NS
namespaced-user-on-project namespaced-object-infra name: namespaced-project, namespace: namespaced-infra ❌ Target is a project, not a domain

Results

$ kubectl get userv3.identity.openstack.m.crossplane.io -A
NAMESPACE                 NAME                           SYNCED   READY   EXTERNAL-NAME                      AGE
namespaced-object-infra   namespaced-user                True     True    bea9217d667a47d290a7a792aaae933c   2m47s
namespaced-object-infra   namespaced-user-on-project     False                                               2m46s
namespaced-object-infra   wrong-namespaced-user          False                                               2m46s

✅ namespaced-user — cross-namespace reference resolved successfully (user in namespaced-object-infra → domain in namespaced-infra).

❌ namespaced-user-on-project — initializer blocks with a clear error:

Warning  CannotInitializeManagedResource  13s (x9 over 3m19s)
  spec.forProvider.domainIdRef: referenced ProjectV3 "namespaced-project" is a project,
  not a domain: reference a ProjectV3 created with isDomain: true

❌ wrong-namespaced-user — resolver correctly fails because namespaced-domain doesn't exist in the user's own namespace (namespaced-object-infra):

Warning  CannotResolveResourceReferences  40s (x9 over 3m46s)
  cannot resolve references: mg.Spec.ForProvider.DomainID: cannot get referenced resource:
  ProjectV3.identity.openstack.m.crossplane.io "namespaced-domain" not found

Summary

  • Cross-namespace domainIdRef.namespace works correctly — validator now reads explicit namespace from the ref (mirroring APINamespacedResolver behavior)
  • Referencing a regular project (not a domain) gives a clear CannotInitializeManagedResource error
  • Missing explicit namespace in cross-NS scenario correctly fails at resolver level (domain not found in own namespace)

@JRaver

JRaver commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

@Avatos Please take a look one more time) and If everything is ok, please merge and create new release, thanks)

@JRaver

JRaver commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

@Avatos Sorry for bothering you, but any changes to this PR will be reviewed and merged, please

@Avatos
Avatos merged commit 086d46d into crossplane-contrib:main Oct 7, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants