Skip to content

Commit cde71fa

Browse files
committed
Implement RoleAssignment controller treating role assignments as relationships
rather than resources, since OpenStack doesn't assign IDs to role assignments. Key changes: - Custom reconciler: Ignores generic framework since role assignments lack OpenStack resource IDs - Component-based identification: Uses tuple (roleID, userID/groupID, projectID/domainID) stored in Status.Resource instead of UIDs - Status.ID intentionally nil: Components serve as natural identifiers - Immutable spec: Role assignments can't be modified after creation (matching Kubernetes RBAC behavior) - Deletion guards: All dependencies (Role, User/Group, Project/Domain) protected from deletion while in use E2E tests cover four actor-scope combinations: - roleassignment-create-user-project - roleassignment-create-user-domain - roleassignment-create-group-project - roleassignment-create-group-domain Plus roleassignment-dependency test verifying deletion guard behavior. Implementation details: - reconciler.go: Custom reconcile loop handling create/delete lifecycle - actuator.go: GetResourceByComponents() replaces GetOSResourceByID() Signed-off-by: Daniel Lawton <dlawton@redhat.com>
1 parent d80cf95 commit cde71fa

125 files changed

Lines changed: 4423 additions & 1983 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/settings.local.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"Bash(go build:*)",
77
"Bash(go fmt:*)",
88
"Bash(go doc:*)",
9-
"Bash(make test:*)"
9+
"Bash(make test:*)",
10+
"Bash(openstack *)"
1011
],
1112
"deny": [],
1213
"ask": []

PROJECT

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ resources:
112112
kind: Role
113113
path: github.com/k-orc/openstack-resource-controller/api/v1alpha1
114114
version: v1alpha1
115+
- api:
116+
crdVersion: v1
117+
namespaced: true
118+
domain: k-orc.cloud
119+
group: openstack
120+
kind: RoleAssignment
121+
path: github.com/k-orc/openstack-resource-controller/api/v1alpha1
122+
version: v1alpha1
115123
- api:
116124
crdVersion: v1
117125
namespaced: true

api/v1alpha1/roleassignment_types.go

Lines changed: 30 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -16,129 +16,89 @@ limitations under the License.
1616

1717
package v1alpha1
1818

19-
// RoleAssignmentResourceSpec contains the desired state of the resource.
19+
// RoleAssignmentResourceSpec defines the desired role assignment.
20+
// A role assignment grants a role to a user or group on a project or domain.
21+
// Role assignments are immutable once created and identified by the combination
22+
// of (role, actor, scope) rather than a separate ID.
23+
// +kubebuilder:validation:XValidation:rule="(has(self.userRef) && !has(self.groupRef)) || (!has(self.userRef) && has(self.groupRef))",message="exactly one of userRef or groupRef is required"
24+
// +kubebuilder:validation:XValidation:rule="(has(self.projectRef) && !has(self.domainRef)) || (!has(self.projectRef) && has(self.domainRef))",message="exactly one of projectRef or domainRef is required"
25+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="RoleAssignmentResourceSpec is immutable"
2026
type RoleAssignmentResourceSpec struct {
21-
// name will be the name of the created resource. If not specified, the
22-
// name of the ORC object will be used.
23-
// +optional
24-
Name *OpenStackName `json:"name,omitempty"`
25-
26-
// description is a human-readable description for the resource.
27-
// +kubebuilder:validation:MinLength:=1
28-
// +kubebuilder:validation:MaxLength:=255
29-
// +optional
30-
Description *string `json:"description,omitempty"`
31-
32-
// roleRef is a reference to the ORC Role which this resource is associated with.
27+
// roleRef references the Role being assigned.
3328
// +required
34-
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="roleRef is immutable"
3529
RoleRef KubernetesNameRef `json:"roleRef,omitempty"`
3630

37-
// userRef is a reference to the ORC User which this resource is associated with.
31+
// userRef references the User receiving the role assignment.
32+
// Exactly one of userRef or groupRef must be specified.
3833
// +optional
39-
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="userRef is immutable"
4034
UserRef *KubernetesNameRef `json:"userRef,omitempty"`
4135

42-
// groupRef is a reference to the ORC Group which this resource is associated with.
36+
// groupRef references the Group receiving the role assignment.
37+
// Exactly one of userRef or groupRef must be specified.
4338
// +optional
44-
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="groupRef is immutable"
4539
GroupRef *KubernetesNameRef `json:"groupRef,omitempty"`
4640

47-
// projectRef is a reference to the ORC Project which this resource is associated with.
41+
// projectRef references the Project scope for the assignment.
42+
// Exactly one of projectRef or domainRef must be specified.
4843
// +optional
49-
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="projectRef is immutable"
5044
ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"`
5145

52-
// domainRef is a reference to the ORC Domain which this resource is associated with.
46+
// domainRef references the Domain scope for the assignment.
47+
// Exactly one of projectRef or domainRef must be specified.
5348
// +optional
54-
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="domainRef is immutable"
5549
DomainRef *KubernetesNameRef `json:"domainRef,omitempty"`
56-
57-
// TODO(scaffolding): Add more types.
58-
// To see what is supported, you can take inspiration from the CreateOpts structure from
59-
// github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles
60-
//
61-
// Until you have implemented mutability for the field, you must add a CEL validation
62-
// preventing the field being modified:
63-
// `// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="<fieldname> is immutable"`
6450
}
6551

66-
// RoleAssignmentFilter defines an existing resource by its properties
52+
// RoleAssignmentFilter defines import filter criteria for existing role assignments.
6753
// +kubebuilder:validation:MinProperties:=1
6854
type RoleAssignmentFilter struct {
69-
// name of the existing resource
70-
// +optional
71-
Name *OpenStackName `json:"name,omitempty"`
72-
73-
// description of the existing resource
74-
// +kubebuilder:validation:MinLength:=1
75-
// +kubebuilder:validation:MaxLength:=255
76-
// +optional
77-
Description *string `json:"description,omitempty"`
78-
79-
// roleRef is a reference to the ORC Role which this resource is associated with.
55+
// roleRef filters by the referenced Role.
8056
// +optional
8157
RoleRef *KubernetesNameRef `json:"roleRef,omitempty"`
8258

83-
// userRef is a reference to the ORC User which this resource is associated with.
59+
// userRef filters by the referenced User.
8460
// +optional
8561
UserRef *KubernetesNameRef `json:"userRef,omitempty"`
8662

87-
// groupRef is a reference to the ORC Group which this resource is associated with.
63+
// groupRef filters by the referenced Group.
8864
// +optional
8965
GroupRef *KubernetesNameRef `json:"groupRef,omitempty"`
9066

91-
// projectRef is a reference to the ORC Project which this resource is associated with.
67+
// projectRef filters by the referenced Project scope.
9268
// +optional
9369
ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"`
9470

95-
// domainRef is a reference to the ORC Domain which this resource is associated with.
71+
// domainRef filters by the referenced Domain scope.
9672
// +optional
9773
DomainRef *KubernetesNameRef `json:"domainRef,omitempty"`
98-
99-
// TODO(scaffolding): Add more types.
100-
// To see what is supported, you can take inspiration from the ListOpts structure from
101-
// github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles
10274
}
10375

104-
// RoleAssignmentResourceStatus represents the observed state of the resource.
76+
// RoleAssignmentResourceStatus represents the observed state of the role assignment.
77+
// Note: Role assignments do not have a unique ID in OpenStack - they are identified
78+
// by the combination of role, actor (user/group), and scope (project/domain).
10579
type RoleAssignmentResourceStatus struct {
106-
// name is a Human-readable name for the resource. Might not be unique.
107-
// +kubebuilder:validation:MaxLength=1024
108-
// +optional
109-
Name string `json:"name,omitempty"`
110-
111-
// description is a human-readable description for the resource.
112-
// +kubebuilder:validation:MaxLength=1024
113-
// +optional
114-
Description string `json:"description,omitempty"`
115-
116-
// roleID is the ID of the Role to which the resource is associated.
80+
// roleID is the OpenStack ID of the assigned role.
11781
// +kubebuilder:validation:MaxLength=1024
11882
// +optional
11983
RoleID string `json:"roleID,omitempty"`
12084

121-
// userID is the ID of the User to which the resource is associated.
85+
// userID is the OpenStack ID of the user (if actorType is User).
12286
// +kubebuilder:validation:MaxLength=1024
12387
// +optional
12488
UserID string `json:"userID,omitempty"`
12589

126-
// groupID is the ID of the Group to which the resource is associated.
90+
// groupID is the OpenStack ID of the group (if actorType is Group).
12791
// +kubebuilder:validation:MaxLength=1024
12892
// +optional
12993
GroupID string `json:"groupID,omitempty"`
13094

131-
// projectID is the ID of the Project to which the resource is associated.
95+
// projectID is the OpenStack ID of the project scope (if scopeType is Project).
13296
// +kubebuilder:validation:MaxLength=1024
13397
// +optional
13498
ProjectID string `json:"projectID,omitempty"`
13599

136-
// domainID is the ID of the Domain to which the resource is associated.
100+
// domainID is the OpenStack ID of the domain scope (if scopeType is Domain).
137101
// +kubebuilder:validation:MaxLength=1024
138102
// +optional
139103
DomainID string `json:"domainID,omitempty"`
140-
141-
// TODO(scaffolding): Add more types.
142-
// To see what is supported, you can take inspiration from the RoleAssignment structure from
143-
// github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles
144104
}

0 commit comments

Comments
 (0)