From d80cf9503f7c9756409440311aa262ef89c63481 Mon Sep 17 00:00:00 2001 From: Daniel Lawton Date: Thu, 2 Apr 2026 17:09:44 +0100 Subject: [PATCH 1/2] Initial Commit Used Scaffolding tool to create skeleton RoleAssignment Controller Signed-off-by: Daniel Lawton --- api/v1alpha1/roleassignment_types.go | 144 +++++++ .../openstack_v1alpha1_roleassignment.yaml | 14 + .../controllers/roleassignment/actuator.go | 359 ++++++++++++++++++ .../roleassignment/actuator_test.go | 119 ++++++ .../controllers/roleassignment/controller.go | 282 ++++++++++++++ internal/controllers/roleassignment/status.go | 68 ++++ .../roleassignment-create-full/00-assert.yaml | 53 +++ .../00-create-resource.yaml | 85 +++++ .../roleassignment-create-full/00-secret.yaml | 6 + .../roleassignment-create-full/README.md | 11 + .../00-assert.yaml | 32 ++ .../00-create-resource.yaml | 28 ++ .../00-secret.yaml | 6 + .../01-assert.yaml | 11 + .../01-delete-secret.yaml | 7 + .../roleassignment-create-minimal/README.md | 15 + .../roleassignment-dependency/00-assert.yaml | 90 +++++ .../00-create-resources-missing-deps.yaml | 98 +++++ .../roleassignment-dependency/00-secret.yaml | 6 + .../roleassignment-dependency/01-assert.yaml | 90 +++++ .../01-create-dependencies.yaml | 71 ++++ .../roleassignment-dependency/02-assert.yaml | 41 ++ .../02-delete-dependencies.yaml | 17 + .../roleassignment-dependency/03-assert.yaml | 17 + .../03-delete-resources.yaml | 22 ++ .../tests/roleassignment-dependency/README.md | 21 + .../00-assert.yaml | 25 ++ .../00-import-resource.yaml | 82 ++++ .../00-secret.yaml | 6 + .../01-assert.yaml | 40 ++ .../01-create-trap-resource.yaml | 84 ++++ .../02-assert.yaml | 54 +++ .../02-create-resource.yaml | 83 ++++ .../03-assert.yaml | 14 + .../03-delete-import-dependencies.yaml | 15 + .../04-assert.yaml | 6 + .../04-delete-resource.yaml | 7 + .../README.md | 29 ++ .../00-assert.yaml | 30 ++ .../00-create-resources.yaml | 43 +++ .../00-secret.yaml | 6 + .../01-assert.yaml | 15 + .../01-import-resource.yaml | 13 + .../roleassignment-import-error/README.md | 13 + .../roleassignment-import/00-assert.yaml | 15 + .../00-import-resource.yaml | 15 + .../roleassignment-import/00-secret.yaml | 6 + .../roleassignment-import/01-assert.yaml | 34 ++ .../01-create-trap-resource.yaml | 31 ++ .../roleassignment-import/02-assert.yaml | 33 ++ .../02-create-resource.yaml | 28 ++ .../tests/roleassignment-import/README.md | 18 + .../roleassignment-update/00-assert.yaml | 26 ++ .../00-minimal-resource.yaml | 28 ++ .../roleassignment-update/00-secret.yaml | 6 + .../roleassignment-update/01-assert.yaml | 17 + .../01-updated-resource.yaml | 10 + .../roleassignment-update/02-assert.yaml | 26 ++ .../02-reverted-resource.yaml | 7 + .../tests/roleassignment-update/README.md | 17 + internal/osclients/roleassignment.go | 104 +++++ test/apivalidations/roleassignment_test.go | 176 +++++++++ 62 files changed, 2875 insertions(+) create mode 100644 api/v1alpha1/roleassignment_types.go create mode 100644 config/samples/openstack_v1alpha1_roleassignment.yaml create mode 100644 internal/controllers/roleassignment/actuator.go create mode 100644 internal/controllers/roleassignment/actuator_test.go create mode 100644 internal/controllers/roleassignment/controller.go create mode 100644 internal/controllers/roleassignment/status.go create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-full/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-full/00-create-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-full/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-full/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-create-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/00-create-resources-missing-deps.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/01-create-dependencies.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/02-delete-dependencies.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/03-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/03-delete-resources.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-dependency/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/README.md create mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/00-minimal-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/01-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/01-updated-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/02-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/02-reverted-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/README.md create mode 100644 internal/osclients/roleassignment.go create mode 100644 test/apivalidations/roleassignment_test.go diff --git a/api/v1alpha1/roleassignment_types.go b/api/v1alpha1/roleassignment_types.go new file mode 100644 index 000000000..36e82ff05 --- /dev/null +++ b/api/v1alpha1/roleassignment_types.go @@ -0,0 +1,144 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +// RoleAssignmentResourceSpec contains the desired state of the resource. +type RoleAssignmentResourceSpec struct { + // name will be the name of the created resource. If not specified, the + // name of the ORC object will be used. + // +optional + Name *OpenStackName `json:"name,omitempty"` + + // description is a human-readable description for the resource. + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +optional + Description *string `json:"description,omitempty"` + + // roleRef is a reference to the ORC Role which this resource is associated with. + // +required + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="roleRef is immutable" + RoleRef KubernetesNameRef `json:"roleRef,omitempty"` + + // userRef is a reference to the ORC User which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="userRef is immutable" + UserRef *KubernetesNameRef `json:"userRef,omitempty"` + + // groupRef is a reference to the ORC Group which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="groupRef is immutable" + GroupRef *KubernetesNameRef `json:"groupRef,omitempty"` + + // projectRef is a reference to the ORC Project which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="projectRef is immutable" + ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` + + // domainRef is a reference to the ORC Domain which this resource is associated with. + // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="domainRef is immutable" + DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the CreateOpts structure from + // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles + // + // Until you have implemented mutability for the field, you must add a CEL validation + // preventing the field being modified: + // `// +kubebuilder:validation:XValidation:rule="self == oldSelf",message=" is immutable"` +} + +// RoleAssignmentFilter defines an existing resource by its properties +// +kubebuilder:validation:MinProperties:=1 +type RoleAssignmentFilter struct { + // name of the existing resource + // +optional + Name *OpenStackName `json:"name,omitempty"` + + // description of the existing resource + // +kubebuilder:validation:MinLength:=1 + // +kubebuilder:validation:MaxLength:=255 + // +optional + Description *string `json:"description,omitempty"` + + // roleRef is a reference to the ORC Role which this resource is associated with. + // +optional + RoleRef *KubernetesNameRef `json:"roleRef,omitempty"` + + // userRef is a reference to the ORC User which this resource is associated with. + // +optional + UserRef *KubernetesNameRef `json:"userRef,omitempty"` + + // groupRef is a reference to the ORC Group which this resource is associated with. + // +optional + GroupRef *KubernetesNameRef `json:"groupRef,omitempty"` + + // projectRef is a reference to the ORC Project which this resource is associated with. + // +optional + ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` + + // domainRef is a reference to the ORC Domain which this resource is associated with. + // +optional + DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the ListOpts structure from + // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles +} + +// RoleAssignmentResourceStatus represents the observed state of the resource. +type RoleAssignmentResourceStatus struct { + // name is a Human-readable name for the resource. Might not be unique. + // +kubebuilder:validation:MaxLength=1024 + // +optional + Name string `json:"name,omitempty"` + + // description is a human-readable description for the resource. + // +kubebuilder:validation:MaxLength=1024 + // +optional + Description string `json:"description,omitempty"` + + // roleID is the ID of the Role to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + RoleID string `json:"roleID,omitempty"` + + // userID is the ID of the User to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + UserID string `json:"userID,omitempty"` + + // groupID is the ID of the Group to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + GroupID string `json:"groupID,omitempty"` + + // projectID is the ID of the Project to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + ProjectID string `json:"projectID,omitempty"` + + // domainID is the ID of the Domain to which the resource is associated. + // +kubebuilder:validation:MaxLength=1024 + // +optional + DomainID string `json:"domainID,omitempty"` + + // TODO(scaffolding): Add more types. + // To see what is supported, you can take inspiration from the RoleAssignment structure from + // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles +} diff --git a/config/samples/openstack_v1alpha1_roleassignment.yaml b/config/samples/openstack_v1alpha1_roleassignment.yaml new file mode 100644 index 000000000..f1294f7ce --- /dev/null +++ b/config/samples/openstack_v1alpha1_roleassignment.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-sample +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: Sample RoleAssignment + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/roleassignment/actuator.go b/internal/controllers/roleassignment/actuator.go new file mode 100644 index 000000000..32fafce63 --- /dev/null +++ b/internal/controllers/roleassignment/actuator.go @@ -0,0 +1,359 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package roleassignment + +import ( + "context" + "iter" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles" + corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" + "github.com/k-orc/openstack-resource-controller/v2/internal/logging" + "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" + orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" +) + +// OpenStack resource types +type ( + osResourceT = roles.RoleAssignment + + createResourceActuator = interfaces.CreateResourceActuator[orcObjectPT, orcObjectT, filterT, osResourceT] + deleteResourceActuator = interfaces.DeleteResourceActuator[orcObjectPT, orcObjectT, osResourceT] + resourceReconciler = interfaces.ResourceReconciler[orcObjectPT, osResourceT] + helperFactory = interfaces.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT] +) + +type roleassignmentActuator struct { + osClient osclients.RoleAssignmentClient + k8sClient client.Client +} + +var _ createResourceActuator = roleassignmentActuator{} +var _ deleteResourceActuator = roleassignmentActuator{} + +func (roleassignmentActuator) GetResourceID(osResource *osResourceT) string { + return osResource.ID +} + +func (actuator roleassignmentActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { + resource, err := actuator.osClient.GetRoleAssignment(ctx, id) + if err != nil { + return nil, progress.WrapError(err) + } + return resource, nil +} + +func (actuator roleassignmentActuator) ListOSResourcesForAdoption(ctx context.Context, orcObject orcObjectPT) (iter.Seq2[*osResourceT, error], bool) { + resourceSpec := orcObject.Spec.Resource + if resourceSpec == nil { + return nil, false + } + + // TODO(scaffolding) If you need to filter resources on fields that the List() function + // of gophercloud does not support, it's possible to perform client-side filtering. + // Check osclients.ResourceFilter + + listOpts := roles.ListOpts{ + Name: getResourceName(orcObject), + Description: ptr.Deref(resourceSpec.Description, ""), + } + + return actuator.osClient.ListRoleAssignments(ctx, listOpts), true +} + +func (actuator roleassignmentActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { + // TODO(scaffolding) If you need to filter resources on fields that the List() function + // of gophercloud does not support, it's possible to perform client-side filtering. + // Check osclients.ResourceFilter + var reconcileStatus progress.ReconcileStatus + + role, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.RoleRef, "Role", + func(dep *orcv1alpha1.Role) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + user, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.UserRef, "User", + func(dep *orcv1alpha1.User) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + group, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.GroupRef, "Group", + func(dep *orcv1alpha1.Group) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + domain, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + filter.DomainRef, "Domain", + func(dep *orcv1alpha1.Domain) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + + listOpts := roles.ListOpts{ + Name: string(ptr.Deref(filter.Name, "")), + Description: string(ptr.Deref(filter.Description, "")), + RoleID: ptr.Deref(role.Status.ID, ""), + UserID: ptr.Deref(user.Status.ID, ""), + GroupID: ptr.Deref(group.Status.ID, ""), + ProjectID: ptr.Deref(project.Status.ID, ""), + DomainID: ptr.Deref(domain.Status.ID, ""), + // TODO(scaffolding): Add more import filters + } + + return actuator.osClient.ListRoleAssignments(ctx, listOpts), reconcileStatus +} + +func (actuator roleassignmentActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) { + resource := obj.Spec.Resource + + if resource == nil { + // Should have been caught by API validation + return nil, progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Creation requested, but spec.resource is not set")) + } + var reconcileStatus progress.ReconcileStatus + + var roleID string + role, roleDepRS := roleDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Role) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(roleDepRS) + if role != nil { + roleID = ptr.Deref(role.Status.ID, "") + } + + var userID string + if resource.UserRef != nil { + user, userDepRS := userDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.User) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(userDepRS) + if user != nil { + userID = ptr.Deref(user.Status.ID, "") + } + } + + var groupID string + if resource.GroupRef != nil { + group, groupDepRS := groupDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Group) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(groupDepRS) + if group != nil { + groupID = ptr.Deref(group.Status.ID, "") + } + } + + var projectID string + if resource.ProjectRef != nil { + project, projectDepRS := projectDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(projectDepRS) + if project != nil { + projectID = ptr.Deref(project.Status.ID, "") + } + } + + var domainID string + if resource.DomainRef != nil { + domain, domainDepRS := domainDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Domain) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(domainDepRS) + if domain != nil { + domainID = ptr.Deref(domain.Status.ID, "") + } + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return nil, reconcileStatus + } + createOpts := roles.CreateOpts{ + Name: getResourceName(obj), + Description: ptr.Deref(resource.Description, ""), + RoleID: roleID, + UserID: userID, + GroupID: groupID, + ProjectID: projectID, + DomainID: domainID, + // TODO(scaffolding): Add more fields + } + + osResource, err := actuator.osClient.CreateRoleAssignment(ctx, createOpts) + if err != nil { + // We should require the spec to be updated before retrying a create which returned a conflict + if !orcerrors.IsRetryable(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) + } + return nil, progress.WrapError(err) + } + + return osResource, nil +} + +func (actuator roleassignmentActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { + return progress.WrapError(actuator.osClient.DeleteRoleAssignment(ctx, resource.ID)) +} + +func (actuator roleassignmentActuator) updateResource(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { + log := ctrl.LoggerFrom(ctx) + resource := obj.Spec.Resource + if resource == nil { + // Should have been caught by API validation + return progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Update requested, but spec.resource is not set")) + } + + updateOpts := roles.UpdateOpts{} + + handleNameUpdate(&updateOpts, obj, osResource) + handleDescriptionUpdate(&updateOpts, resource, osResource) + + // TODO(scaffolding): add handler for all fields supporting mutability + + needsUpdate, err := needsUpdate(updateOpts) + if err != nil { + return progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err)) + } + if !needsUpdate { + log.V(logging.Debug).Info("No changes") + return nil + } + + _, err = actuator.osClient.UpdateRoleAssignment(ctx, osResource.ID, updateOpts) + + // We should require the spec to be updated before retrying an update which returned a conflict + if orcerrors.IsConflict(err) { + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + } + + if err != nil { + return progress.WrapError(err) + } + + return progress.NeedsRefresh() +} + +func needsUpdate(updateOpts roles.UpdateOpts) (bool, error) { + updateOptsMap, err := updateOpts.ToRoleAssignmentUpdateMap() + if err != nil { + return false, err + } + + updateMap, ok := updateOptsMap["role_assignment"].(map[string]any) + if !ok { + updateMap = make(map[string]any) + } + + return len(updateMap) > 0, nil +} + +func handleNameUpdate(updateOpts *roles.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { + name := getResourceName(obj) + if osResource.Name != name { + updateOpts.Name = &name + } +} + +func handleDescriptionUpdate(updateOpts *roles.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + description := ptr.Deref(resource.Description, "") + if osResource.Description != description { + updateOpts.Description = &description + } +} + +func (actuator roleassignmentActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { + return []resourceReconciler{ + actuator.updateResource, + }, nil +} + +type roleassignmentHelperFactory struct{} + +var _ helperFactory = roleassignmentHelperFactory{} + +func newActuator(ctx context.Context, orcObject *orcv1alpha1.RoleAssignment, controller interfaces.ResourceController) (roleassignmentActuator, progress.ReconcileStatus) { + log := ctrl.LoggerFrom(ctx) + + // Ensure credential secrets exist and have our finalizer + _, reconcileStatus := credentialsDependency.GetDependencies(ctx, controller.GetK8sClient(), orcObject, func(*corev1.Secret) bool { return true }) + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { + return roleassignmentActuator{}, reconcileStatus + } + + clientScope, err := controller.GetScopeFactory().NewClientScopeFromObject(ctx, controller.GetK8sClient(), log, orcObject) + if err != nil { + return roleassignmentActuator{}, progress.WrapError(err) + } + osClient, err := clientScope.NewRoleAssignmentClient() + if err != nil { + return roleassignmentActuator{}, progress.WrapError(err) + } + + return roleassignmentActuator{ + osClient: osClient, + k8sClient: controller.GetK8sClient(), + }, nil +} + +func (roleassignmentHelperFactory) NewAPIObjectAdapter(obj orcObjectPT) adapterI { + return roleassignmentAdapter{obj} +} + +func (roleassignmentHelperFactory) NewCreateActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (createResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} + +func (roleassignmentHelperFactory) NewDeleteActuator(ctx context.Context, orcObject orcObjectPT, controller interfaces.ResourceController) (deleteResourceActuator, progress.ReconcileStatus) { + return newActuator(ctx, orcObject, controller) +} diff --git a/internal/controllers/roleassignment/actuator_test.go b/internal/controllers/roleassignment/actuator_test.go new file mode 100644 index 000000000..0433ecf35 --- /dev/null +++ b/internal/controllers/roleassignment/actuator_test.go @@ -0,0 +1,119 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package roleassignment + +import ( + "testing" + + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles" + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "k8s.io/utils/ptr" +) + +func TestNeedsUpdate(t *testing.T) { + testCases := []struct { + name string + updateOpts roles.UpdateOpts + expectChange bool + }{ + { + name: "Empty base opts", + updateOpts: roles.UpdateOpts{}, + expectChange: false, + }, + { + name: "Updated opts", + updateOpts: roles.UpdateOpts{Name: ptr.To("updated")}, + expectChange: true, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + got, _ := needsUpdate(tt.updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + } +} + +func TestHandleNameUpdate(t *testing.T) { + ptrToName := ptr.To[orcv1alpha1.OpenStackName] + testCases := []struct { + name string + newValue *orcv1alpha1.OpenStackName + existingValue string + expectChange bool + }{ + {name: "Identical", newValue: ptrToName("name"), existingValue: "name", expectChange: false}, + {name: "Different", newValue: ptrToName("new-name"), existingValue: "name", expectChange: true}, + {name: "No value provided, existing is identical to object name", newValue: nil, existingValue: "object-name", expectChange: false}, + {name: "No value provided, existing is different from object name", newValue: nil, existingValue: "different-from-object-name", expectChange: true}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.RoleAssignment{} + resource.Name = "object-name" + resource.Spec = orcv1alpha1.RoleAssignmentSpec{ + Resource: &orcv1alpha1.RoleAssignmentResourceSpec{Name: tt.newValue}, + } + osResource := &osResourceT{Name: tt.existingValue} + + updateOpts := roles.UpdateOpts{} + handleNameUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + + } +} + +func TestHandleDescriptionUpdate(t *testing.T) { + ptrToDescription := ptr.To[string] + testCases := []struct { + name string + newValue *string + existingValue string + expectChange bool + }{ + {name: "Identical", newValue: ptrToDescription("desc"), existingValue: "desc", expectChange: false}, + {name: "Different", newValue: ptrToDescription("new-desc"), existingValue: "desc", expectChange: true}, + {name: "No value provided, existing is set", newValue: nil, existingValue: "desc", expectChange: true}, + {name: "No value provided, existing is empty", newValue: nil, existingValue: "", expectChange: false}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.RoleAssignmentResourceSpec{Description: tt.newValue} + osResource := &osResourceT{Description: tt.existingValue} + + updateOpts := roles.UpdateOpts{} + handleDescriptionUpdate(&updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) + } + }) + + } +} diff --git a/internal/controllers/roleassignment/controller.go b/internal/controllers/roleassignment/controller.go new file mode 100644 index 000000000..631ea1236 --- /dev/null +++ b/internal/controllers/roleassignment/controller.go @@ -0,0 +1,282 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package roleassignment + +import ( + "context" + "errors" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/controller" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/reconciler" + "github.com/k-orc/openstack-resource-controller/v2/internal/scope" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/credentials" + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" + "github.com/k-orc/openstack-resource-controller/v2/pkg/predicates" +) + +const controllerName = "roleassignment" + +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=roleassignments,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=openstack.k-orc.cloud,resources=roleassignments/status,verbs=get;update;patch + +type roleassignmentReconcilerConstructor struct { + scopeFactory scope.Factory +} + +func New(scopeFactory scope.Factory) interfaces.Controller { + return roleassignmentReconcilerConstructor{scopeFactory: scopeFactory} +} + +func (roleassignmentReconcilerConstructor) GetName() string { + return controllerName +} + +var roleDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Role]( + "spec.resource.roleRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Resource + if resource == nil { + return nil + } + return []string{string(resource.RoleRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var userDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.User]( + "spec.resource.userRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Resource + if resource == nil || resource.UserRef == nil { + return nil + } + return []string{string(*resource.UserRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var groupDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Group]( + "spec.resource.groupRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Resource + if resource == nil || resource.GroupRef == nil { + return nil + } + return []string{string(*resource.GroupRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var projectDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Project]( + "spec.resource.projectRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Resource + if resource == nil || resource.ProjectRef == nil { + return nil + } + return []string{string(*resource.ProjectRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var domainDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Domain]( + "spec.resource.domainRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Resource + if resource == nil || resource.DomainRef == nil { + return nil + } + return []string{string(*resource.DomainRef)} + }, + finalizer, externalObjectFieldOwner, +) + +var roleImportDependency = dependency.NewDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Role]( + "spec.import.filter.roleRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.RoleRef == nil { + return nil + } + return []string{string(*resource.Filter.RoleRef)} + }, +) + +var userImportDependency = dependency.NewDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.User]( + "spec.import.filter.userRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.UserRef == nil { + return nil + } + return []string{string(*resource.Filter.UserRef)} + }, +) + +var groupImportDependency = dependency.NewDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Group]( + "spec.import.filter.groupRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.GroupRef == nil { + return nil + } + return []string{string(*resource.Filter.GroupRef)} + }, +) + +var projectImportDependency = dependency.NewDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Project]( + "spec.import.filter.projectRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.ProjectRef == nil { + return nil + } + return []string{string(*resource.Filter.ProjectRef)} + }, +) + +var domainImportDependency = dependency.NewDependency[*orcv1alpha1.RoleAssignmentList, *orcv1alpha1.Domain]( + "spec.import.filter.domainRef", + func(roleassignment *orcv1alpha1.RoleAssignment) []string { + resource := roleassignment.Spec.Import + if resource == nil || resource.Filter == nil || resource.Filter.DomainRef == nil { + return nil + } + return []string{string(*resource.Filter.DomainRef)} + }, +) + +// SetupWithManager sets up the controller with the Manager. +func (c roleassignmentReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error { + log := ctrl.LoggerFrom(ctx) + k8sClient := mgr.GetClient() + + roleWatchEventHandler, err := roleDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + userWatchEventHandler, err := userDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + groupWatchEventHandler, err := groupDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + projectWatchEventHandler, err := projectDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + domainWatchEventHandler, err := domainDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + roleImportWatchEventHandler, err := roleImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + userImportWatchEventHandler, err := userImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + groupImportWatchEventHandler, err := groupImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + projectImportWatchEventHandler, err := projectImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + domainImportWatchEventHandler, err := domainImportDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + + builder := ctrl.NewControllerManagedBy(mgr). + WithOptions(options). + Watches(&orcv1alpha1.Role{}, roleWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Role{})), + ). + Watches(&orcv1alpha1.User{}, userWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.User{})), + ). + Watches(&orcv1alpha1.Group{}, groupWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Group{})), + ). + Watches(&orcv1alpha1.Project{}, projectWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), + ). + Watches(&orcv1alpha1.Domain{}, domainWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Role{}, roleImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Role{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.User{}, userImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.User{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Group{}, groupImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Group{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Project{}, projectImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), + ). + // A second watch is necessary because we need a different handler that omits deletion guards + Watches(&orcv1alpha1.Domain{}, domainImportWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})), + ). + For(&orcv1alpha1.RoleAssignment{}) + + if err := errors.Join( + roleDependency.AddToManager(ctx, mgr), + userDependency.AddToManager(ctx, mgr), + groupDependency.AddToManager(ctx, mgr), + projectDependency.AddToManager(ctx, mgr), + domainDependency.AddToManager(ctx, mgr), + roleImportDependency.AddToManager(ctx, mgr), + userImportDependency.AddToManager(ctx, mgr), + groupImportDependency.AddToManager(ctx, mgr), + projectImportDependency.AddToManager(ctx, mgr), + domainImportDependency.AddToManager(ctx, mgr), + credentialsDependency.AddToManager(ctx, mgr), + credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency), + ); err != nil { + return err + } + + r := reconciler.NewController(controllerName, mgr.GetClient(), c.scopeFactory, roleassignmentHelperFactory{}, roleassignmentStatusWriter{}) + return builder.Complete(&r) +} diff --git a/internal/controllers/roleassignment/status.go b/internal/controllers/roleassignment/status.go new file mode 100644 index 000000000..a3a720369 --- /dev/null +++ b/internal/controllers/roleassignment/status.go @@ -0,0 +1,68 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package roleassignment + +import ( + "github.com/go-logr/logr" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" + orcapplyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" +) + +type roleassignmentStatusWriter struct{} + +type objectApplyT = orcapplyconfigv1alpha1.RoleAssignmentApplyConfiguration +type statusApplyT = orcapplyconfigv1alpha1.RoleAssignmentStatusApplyConfiguration + +var _ interfaces.ResourceStatusWriter[*orcv1alpha1.RoleAssignment, *osResourceT, *objectApplyT, *statusApplyT] = roleassignmentStatusWriter{} + +func (roleassignmentStatusWriter) GetApplyConfig(name, namespace string) *objectApplyT { + return orcapplyconfigv1alpha1.RoleAssignment(name, namespace) +} + +func (roleassignmentStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.RoleAssignment, osResource *osResourceT) (metav1.ConditionStatus, progress.ReconcileStatus) { + if osResource == nil { + if orcObject.Status.ID == nil { + return metav1.ConditionFalse, nil + } else { + return metav1.ConditionUnknown, nil + } + } + return metav1.ConditionTrue, nil +} + +func (roleassignmentStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { + resourceStatus := orcapplyconfigv1alpha1.RoleAssignmentResourceStatus(). + WithRoleID(osResource.RoleID). + WithUserID(osResource.UserID). + WithGroupID(osResource.GroupID). + WithProjectID(osResource.ProjectID). + WithDomainID(osResource.DomainID). + WithName(osResource.Name) + + // TODO(scaffolding): add all of the fields supported in the RoleAssignmentResourceStatus struct + // If a zero-value isn't expected in the response, place it behind a conditional + + if osResource.Description != "" { + resourceStatus.WithDescription(osResource.Description) + } + + statusApply.WithResource(resourceStatus) +} diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-full/00-assert.yaml new file mode 100644 index 000000000..7da99c8d0 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-full/00-assert.yaml @@ -0,0 +1,53 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-create-full +status: + resource: + name: roleassignment-create-full-override + description: RoleAssignment from "create full" test + # TODO(scaffolding): Add all fields the resource supports + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-create-full + ref: roleassignment + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Role + name: roleassignment-create-full + ref: role + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: roleassignment-create-full + ref: user + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Group + name: roleassignment-create-full + ref: group + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: roleassignment-create-full + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: roleassignment-create-full + ref: domain +assertAll: + - celExpr: "roleassignment.status.id != ''" + - celExpr: "roleassignment.status.resource.roleID == role.status.id" + - celExpr: "roleassignment.status.resource.userID == user.status.id" + - celExpr: "roleassignment.status.resource.groupID == group.status.id" + - celExpr: "roleassignment.status.resource.projectID == project.status.id" + - celExpr: "roleassignment.status.resource.domainID == domain.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-full/00-create-resource.yaml new file mode 100644 index 000000000..ce3174c84 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-full/00-create-resource.yaml @@ -0,0 +1,85 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Group +metadata: + name: roleassignment-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: roleassignment-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-create-full +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-create-full-override + description: RoleAssignment from "create full" test + roleRef: roleassignment-create-full + userRef: roleassignment-create-full + groupRef: roleassignment-create-full + projectRef: roleassignment-create-full + domainRef: roleassignment-create-full + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-full/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-full/00-secret.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-full/README.md b/internal/controllers/roleassignment/tests/roleassignment-create-full/README.md new file mode 100644 index 000000000..74756b176 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-full/README.md @@ -0,0 +1,11 @@ +# Create a RoleAssignment with all the options + +## Step 00 + +Create a RoleAssignment using all available fields, and verify that the observed state corresponds to the spec. + +Also validate that the OpenStack resource uses the name from the spec when it is specified. + +## Reference + +https://k-orc.cloud/development/writing-tests/#create-full diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-assert.yaml new file mode 100644 index 000000000..4106e13f4 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-assert.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-create-minimal +status: + resource: + name: roleassignment-create-minimal + # TODO(scaffolding): Add all fields the resource supports + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-create-minimal + ref: roleassignment + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Role + name: roleassignment-create-minimal + ref: role +assertAll: + - celExpr: "roleassignment.status.id != ''" + - celExpr: "roleassignment.status.resource.roleID == role.status.id" + # TODO(scaffolding): Add more checks diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-create-resource.yaml new file mode 100644 index 000000000..28bf9a7aa --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-create-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-create-minimal +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-create-minimal +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Only add the mandatory fields. It's possible the resource + # doesn't have mandatory fields, in that case, leave it empty. + resource: + roleRef: roleassignment-create-minimal diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-secret.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-assert.yaml new file mode 100644 index 000000000..c4d367d4a --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-assert.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: v1 + kind: Secret + name: openstack-clouds + ref: secret +assertAll: + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/roleassignment' in secret.metadata.finalizers" diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-secret.yaml new file mode 100644 index 000000000..1620791b9 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-secret.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We expect the deletion to hang due to the finalizer, so use --wait=false + - command: kubectl delete secret openstack-clouds --wait=false + namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/README.md b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/README.md new file mode 100644 index 000000000..08f0e40c6 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/README.md @@ -0,0 +1,15 @@ +# Create a RoleAssignment with the minimum options + +## Step 00 + +Create a minimal RoleAssignment, that sets only the required fields, and verify that the observed state corresponds to the spec. + +Also validate that the OpenStack resource uses the name of the ORC object when no name is explicitly specified. + +## Step 01 + +Try deleting the secret and ensure that it is not deleted thanks to the finalizer. + +## Reference + +https://k-orc.cloud/development/writing-tests/#create-minimal diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-assert.yaml new file mode 100644 index 000000000..ce8bc6357 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-assert.yaml @@ -0,0 +1,90 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-secret +status: + conditions: + - type: Available + message: Waiting for Secret/roleassignment-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Secret/roleassignment-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-role +status: + conditions: + - type: Available + message: Waiting for Role/roleassignment-dependency-pending to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Role/roleassignment-dependency-pending to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-user +status: + conditions: + - type: Available + message: Waiting for User/roleassignment-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for User/roleassignment-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-group +status: + conditions: + - type: Available + message: Waiting for Group/roleassignment-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Group/roleassignment-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-project +status: + conditions: + - type: Available + message: Waiting for Project/roleassignment-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Project/roleassignment-dependency to be created + status: "True" + reason: Progressing +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-domain +status: + conditions: + - type: Available + message: Waiting for Domain/roleassignment-dependency to be created + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for Domain/roleassignment-dependency to be created + status: "True" + reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-create-resources-missing-deps.yaml new file mode 100644 index 000000000..9666ee6c6 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-create-resources-missing-deps.yaml @@ -0,0 +1,98 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-role +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-dependency-pending + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-user +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-dependency + userRef: roleassignment-dependency + # TODO(scaffolding): Add the necessary fields to create the resource--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-group +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-dependency + groupRef: roleassignment-dependency + # TODO(scaffolding): Add the necessary fields to create the resource--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-project +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-dependency + projectRef: roleassignment-dependency + # TODO(scaffolding): Add the necessary fields to create the resource--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-domain +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-dependency + domainRef: roleassignment-dependency + # TODO(scaffolding): Add the necessary fields to create the resource +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-secret +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: roleassignment-dependency + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: + roleRef: roleassignment-dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-secret.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/01-assert.yaml new file mode 100644 index 000000000..2fd8a79b1 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/01-assert.yaml @@ -0,0 +1,90 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-secret +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-role +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-user +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-group +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-project +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency-no-domain +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/01-create-dependencies.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/01-create-dependencies.yaml new file mode 100644 index 000000000..5e18664c5 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/01-create-dependencies.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic roleassignment-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-dependency-pending +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Group +metadata: + name: roleassignment-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: roleassignment-dependency +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml new file mode 100644 index 000000000..d91675d9d --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml @@ -0,0 +1,41 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Role + name: roleassignment-dependency + ref: role + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: roleassignment-dependency + ref: user + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Group + name: roleassignment-dependency + ref: group + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: roleassignment-dependency + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: roleassignment-dependency + ref: domain + - apiVersion: v1 + kind: Secret + name: roleassignment-dependency + ref: secret +assertAll: + - celExpr: "role.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/roleassignment' in role.metadata.finalizers" + - celExpr: "user.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/roleassignment' in user.metadata.finalizers" + - celExpr: "group.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/roleassignment' in group.metadata.finalizers" + - celExpr: "project.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/roleassignment' in project.metadata.finalizers" + - celExpr: "domain.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/roleassignment' in domain.metadata.finalizers" + - celExpr: "secret.metadata.deletionTimestamp != 0" + - celExpr: "'openstack.k-orc.cloud/roleassignment' in secret.metadata.finalizers" diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/02-delete-dependencies.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-delete-dependencies.yaml new file mode 100644 index 000000000..7d2647c44 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-delete-dependencies.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We expect the deletion to hang due to the finalizer, so use --wait=false + - command: kubectl delete role.openstack.k-orc.cloud roleassignment-dependency --wait=false + namespaced: true + - command: kubectl delete user.openstack.k-orc.cloud roleassignment-dependency --wait=false + namespaced: true + - command: kubectl delete group.openstack.k-orc.cloud roleassignment-dependency --wait=false + namespaced: true + - command: kubectl delete project.openstack.k-orc.cloud roleassignment-dependency --wait=false + namespaced: true + - command: kubectl delete domain.openstack.k-orc.cloud roleassignment-dependency --wait=false + namespaced: true + - command: kubectl delete secret roleassignment-dependency --wait=false + namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/03-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/03-assert.yaml new file mode 100644 index 000000000..c9d8176db --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/03-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +# Dependencies that were prevented deletion before should now be gone +- script: "! kubectl get role.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get user.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get group.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get project.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get domain.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get secret roleassignment-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/03-delete-resources.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/03-delete-resources.yaml new file mode 100644 index 000000000..bd5ab1aec --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/03-delete-resources.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-dependency-no-secret +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-dependency-no-role +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-dependency-no-user +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-dependency-no-group +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-dependency-no-project +- apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-dependency-no-domain diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/README.md b/internal/controllers/roleassignment/tests/roleassignment-dependency/README.md new file mode 100644 index 000000000..2682e773e --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/README.md @@ -0,0 +1,21 @@ +# Creation and deletion dependencies + +## Step 00 + +Create RoleAssignments referencing non-existing resources. Each RoleAssignment is dependent on other non-existing resource. Verify that the RoleAssignments are waiting for the needed resources to be created externally. + +## Step 01 + +Create the missing dependencies and verify all the RoleAssignments are available. + +## Step 02 + +Delete all the dependencies and check that ORC prevents deletion since there is still a resource that depends on them. + +## Step 03 + +Delete the RoleAssignments and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml new file mode 100644 index 000000000..d67606898 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Role/roleassignment-import-dependency to be ready + Waiting for User/roleassignment-import-dependency to be ready + Waiting for Group/roleassignment-import-dependency to be ready + Waiting for Project/roleassignment-import-dependency to be ready + Waiting for Domain/roleassignment-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Role/roleassignment-import-dependency to be ready + Waiting for User/roleassignment-import-dependency to be ready + Waiting for Group/roleassignment-import-dependency to be ready + Waiting for Project/roleassignment-import-dependency to be ready + Waiting for Domain/roleassignment-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml new file mode 100644 index 000000000..cbde79d20 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml @@ -0,0 +1,82 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: roleassignment-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: roleassignment-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Group +metadata: + name: roleassignment-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: roleassignment-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: roleassignment-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: roleassignment-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: roleassignment-import-dependency-external +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-dependency +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + roleRef: roleassignment-import-dependency + userRef: roleassignment-import-dependency + groupRef: roleassignment-import-dependency + projectRef: roleassignment-import-dependency + domainRef: roleassignment-import-dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml new file mode 100644 index 000000000..bf4fc3938 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml @@ -0,0 +1,40 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-dependency-not-this-one +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-dependency +status: + conditions: + - type: Available + message: |- + Waiting for Role/roleassignment-import-dependency to be ready + Waiting for User/roleassignment-import-dependency to be ready + Waiting for Group/roleassignment-import-dependency to be ready + Waiting for Project/roleassignment-import-dependency to be ready + Waiting for Domain/roleassignment-import-dependency to be ready + status: "False" + reason: Progressing + - type: Progressing + message: |- + Waiting for Role/roleassignment-import-dependency to be ready + Waiting for User/roleassignment-import-dependency to be ready + Waiting for Group/roleassignment-import-dependency to be ready + Waiting for Project/roleassignment-import-dependency to be ready + Waiting for Domain/roleassignment-import-dependency to be ready + status: "True" + reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml new file mode 100644 index 000000000..d7edf5088 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml @@ -0,0 +1,84 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Group +metadata: + name: roleassignment-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: roleassignment-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +# This `roleassignment-import-dependency-not-this-one` should not be picked by the import filter +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-dependency-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-import-dependency-not-this-one + userRef: roleassignment-import-dependency-not-this-one + groupRef: roleassignment-import-dependency-not-this-one + projectRef: roleassignment-import-dependency-not-this-one + domainRef: roleassignment-import-dependency-not-this-one + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml new file mode 100644 index 000000000..3c81e999a --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml @@ -0,0 +1,54 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-import-dependency + ref: roleassignment1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-import-dependency-not-this-one + ref: roleassignment2 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Role + name: roleassignment-import-dependency + ref: role + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: roleassignment-import-dependency + ref: user + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Group + name: roleassignment-import-dependency + ref: group + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: roleassignment-import-dependency + ref: project + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Domain + name: roleassignment-import-dependency + ref: domain +assertAll: + - celExpr: "roleassignment1.status.id != roleassignment2.status.id" + - celExpr: "roleassignment1.status.resource.roleID == role.status.id" + - celExpr: "roleassignment1.status.resource.userID == user.status.id" + - celExpr: "roleassignment1.status.resource.groupID == group.status.id" + - celExpr: "roleassignment1.status.resource.projectID == project.status.id" + - celExpr: "roleassignment1.status.resource.domainID == domain.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-dependency +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml new file mode 100644 index 000000000..aa6a4b273 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml @@ -0,0 +1,83 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Group +metadata: + name: roleassignment-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: roleassignment-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-dependency-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-import-dependency-external + userRef: roleassignment-import-dependency-external + groupRef: roleassignment-import-dependency-external + projectRef: roleassignment-import-dependency-external + domainRef: roleassignment-import-dependency-external + # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml new file mode 100644 index 000000000..5ae60674d --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get role.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get user.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get group.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get project.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" + skipLogOutput: true +- script: "! kubectl get domain.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml new file mode 100644 index 000000000..6779311b6 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + # We should be able to delete the import dependencies + - command: kubectl delete role.openstack.k-orc.cloud roleassignment-import-dependency + namespaced: true + - command: kubectl delete user.openstack.k-orc.cloud roleassignment-import-dependency + namespaced: true + - command: kubectl delete group.openstack.k-orc.cloud roleassignment-import-dependency + namespaced: true + - command: kubectl delete project.openstack.k-orc.cloud roleassignment-import-dependency + namespaced: true + - command: kubectl delete domain.openstack.k-orc.cloud roleassignment-import-dependency + namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml new file mode 100644 index 000000000..1b9333e8c --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: +- script: "! kubectl get roleassignment.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" + skipLogOutput: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml new file mode 100644 index 000000000..65a259c22 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-import-dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md new file mode 100644 index 000000000..e42814f39 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md @@ -0,0 +1,29 @@ +# Check dependency handling for imported RoleAssignment + +## Step 00 + +Import a RoleAssignment that references other imported resources. The referenced imported resources have no matching resources yet. +Verify the RoleAssignment is waiting for the dependency to be ready. + +## Step 01 + +Create a RoleAssignment matching the import filter, except for referenced resources, and verify that it's not being imported. + +## Step 02 + +Create the referenced resources and a RoleAssignment matching the import filters. + +Verify that the observed status on the imported RoleAssignment corresponds to the spec of the created RoleAssignment. + +## Step 03 + +Delete the referenced resources and check that ORC does not prevent deletion. The OpenStack resources still exist because they +were imported resources and we only deleted the ORC representation of it. + +## Step 04 + +Delete the RoleAssignment and validate that all resources are gone. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml new file mode 100644 index 000000000..a0fbff00c --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-error-external-1 +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-error-external-2 +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml new file mode 100644 index 000000000..a85f3b243 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-error +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-error-external-1 +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: RoleAssignment from "import error" test + roleRef: roleassignment-import-error + # TODO(scaffolding): add any required field +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-error-external-2 +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: RoleAssignment from "import error" test + roleRef: roleassignment-import-error + # TODO(scaffolding): add any required field diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml new file mode 100644 index 000000000..1f7e3a893 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-error +status: + conditions: + - type: Available + message: found more than one matching OpenStack resource during import + status: "False" + reason: InvalidConfiguration + - type: Progressing + message: found more than one matching OpenStack resource during import + status: "False" + reason: InvalidConfiguration diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml new file mode 100644 index 000000000..5589b99b8 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-error +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + description: RoleAssignment from "import error" test diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/README.md b/internal/controllers/roleassignment/tests/roleassignment-import-error/README.md new file mode 100644 index 000000000..26ca68ce4 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import-error/README.md @@ -0,0 +1,13 @@ +# Import RoleAssignment with more than one matching resources + +## Step 00 + +Create two RoleAssignments with identical specs. + +## Step 01 + +Ensure that an imported RoleAssignment with a filter matching the resources returns an error. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml new file mode 100644 index 000000000..fdc36fdd0 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import +status: + conditions: + - type: Available + message: Waiting for OpenStack resource to be created externally + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for OpenStack resource to be created externally + status: "True" + reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml new file mode 100644 index 000000000..e4d6ce579 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: unmanaged + import: + filter: + name: roleassignment-import-external + description: RoleAssignment roleassignment-import-external from "roleassignment-import" test + # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml new file mode 100644 index 000000000..9a908403b --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-external-not-this-one +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success + resource: + name: roleassignment-import-external-not-this-one + description: RoleAssignment roleassignment-import-external from "roleassignment-import" test + # TODO(scaffolding): Add fields necessary to match filter +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import +status: + conditions: + - type: Available + message: Waiting for OpenStack resource to be created externally + status: "False" + reason: Progressing + - type: Progressing + message: Waiting for OpenStack resource to be created externally + status: "True" + reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml new file mode 100644 index 000000000..a991d3d80 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml @@ -0,0 +1,31 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import-external-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +# This `roleassignment-import-external-not-this-one` resource serves two purposes: +# - ensure that we can successfully create another resource which name is a substring of it (i.e. it's not being adopted) +# - ensure that importing a resource which name is a substring of it will not pick this one. +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-external-not-this-one +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: RoleAssignment roleassignment-import-external from "roleassignment-import" test + roleRef: roleassignment-import-external-not-this-one + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml new file mode 100644 index 000000000..b5b5eb744 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-import-external + ref: roleassignment1 + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-import-external-not-this-one + ref: roleassignment2 +assertAll: + - celExpr: "roleassignment1.status.id != roleassignment2.status.id" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import +status: + conditions: + - type: Available + message: OpenStack resource is available + status: "True" + reason: Success + - type: Progressing + message: OpenStack resource is up to date + status: "False" + reason: Success + resource: + name: roleassignment-import-external + description: RoleAssignment roleassignment-import-external from "roleassignment-import" test + # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml new file mode 100644 index 000000000..a4613b0a5 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-import +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-import-external +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + description: RoleAssignment roleassignment-import-external from "roleassignment-import" test + roleRef: roleassignment-import + # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/README.md b/internal/controllers/roleassignment/tests/roleassignment-import/README.md new file mode 100644 index 000000000..0d655cbb5 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-import/README.md @@ -0,0 +1,18 @@ +# Import RoleAssignment + +## Step 00 + +Import a roleassignment that matches all fields in the filter, and verify it is waiting for the external resource to be created. + +## Step 01 + +Create a roleassignment whose name is a superstring of the one specified in the import filter, otherwise matching the filter, and verify that it's not being imported. + +## Step 02 + +Create a roleassignment matching the filter and verify that the observed status on the imported roleassignment corresponds to the spec of the created roleassignment. +Also, confirm that it does not adopt any roleassignment whose name is a superstring of its own. + +## Reference + +https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/00-assert.yaml new file mode 100644 index 000000000..09171f4ed --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-update/00-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-update + ref: roleassignment +assertAll: + - celExpr: "!has(roleassignment.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-update +status: + resource: + name: roleassignment-update + # TODO(scaffolding): Add matches for more fields + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/00-minimal-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/00-minimal-resource.yaml new file mode 100644 index 000000000..dded7b069 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-update/00-minimal-resource.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-update +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Add the necessary fields to create the resource + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-update +spec: + cloudCredentialsRef: + # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created or updated + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + # TODO(scaffolding): Only add the mandatory fields. It's possible the resource + # doesn't have mandatory fields, in that case, leave it empty. + resource: + roleRef: roleassignment-update diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/00-secret.yaml new file mode 100644 index 000000000..045711ee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-update/00-secret.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} + namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/01-assert.yaml new file mode 100644 index 000000000..6ac85e51b --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-update/01-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-update +status: + resource: + name: roleassignment-update-updated + description: roleassignment-update-updated + # TODO(scaffolding): match all fields that were modified + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/01-updated-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/01-updated-resource.yaml new file mode 100644 index 000000000..d07ade78c --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-update/01-updated-resource.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-update +spec: + resource: + name: roleassignment-update-updated + description: roleassignment-update-updated + # TODO(scaffolding): update all mutable fields diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/02-assert.yaml new file mode 100644 index 000000000..15536b2d3 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-update/02-assert.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-update + ref: roleassignment +assertAll: + - celExpr: "!has(roleassignment.status.resource.description)" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-update +status: + resource: + name: roleassignment-update + # TODO(scaffolding): validate that updated fields were all reverted to their original value + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/02-reverted-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/02-reverted-resource.yaml new file mode 100644 index 000000000..2c6c253ff --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-update/02-reverted-resource.yaml @@ -0,0 +1,7 @@ +# NOTE: kuttl only does patch updates, which means we can't delete a field. +# We have to use a kubectl apply command instead. +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - command: kubectl replace -f 00-minimal-resource.yaml + namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/README.md b/internal/controllers/roleassignment/tests/roleassignment-update/README.md new file mode 100644 index 000000000..8c0d2dee7 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-update/README.md @@ -0,0 +1,17 @@ +# Update RoleAssignment + +## Step 00 + +Create a RoleAssignment using only mandatory fields. + +## Step 01 + +Update all mutable fields. + +## Step 02 + +Revert the resource to its original value and verify that the resulting object matches its state when first created. + +## Reference + +https://k-orc.cloud/development/writing-tests/#update diff --git a/internal/osclients/roleassignment.go b/internal/osclients/roleassignment.go new file mode 100644 index 000000000..62da755cd --- /dev/null +++ b/internal/osclients/roleassignment.go @@ -0,0 +1,104 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package osclients + +import ( + "context" + "fmt" + "iter" + + "github.com/gophercloud/gophercloud/v2" + "github.com/gophercloud/gophercloud/v2/openstack" + "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles" + "github.com/gophercloud/utils/v2/openstack/clientconfig" +) + +type RoleAssignmentClient interface { + ListRoleAssignments(ctx context.Context, listOpts roles.ListOptsBuilder) iter.Seq2[*roles.RoleAssignment, error] + CreateRoleAssignment(ctx context.Context, opts roles.CreateOptsBuilder) (*roles.RoleAssignment, error) + DeleteRoleAssignment(ctx context.Context, resourceID string) error + GetRoleAssignment(ctx context.Context, resourceID string) (*roles.RoleAssignment, error) + UpdateRoleAssignment(ctx context.Context, id string, opts roles.UpdateOptsBuilder) (*roles.RoleAssignment, error) +} + +type roleassignmentClient struct{ client *gophercloud.ServiceClient } + +// NewRoleAssignmentClient returns a new OpenStack client. +func NewRoleAssignmentClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (RoleAssignmentClient, error) { + client, err := openstack.NewIdentityV3(providerClient, gophercloud.EndpointOpts{ + Region: providerClientOpts.RegionName, + Availability: clientconfig.GetEndpointType(providerClientOpts.EndpointType), + }) + + if err != nil { + return nil, fmt.Errorf("failed to create roleassignment service client: %v", err) + } + + return &roleassignmentClient{client}, nil +} + +func (c roleassignmentClient) ListRoleAssignments(ctx context.Context, listOpts roles.ListOptsBuilder) iter.Seq2[*roles.RoleAssignment, error] { + pager := roles.List(c.client, listOpts) + return func(yield func(*roles.RoleAssignment, error) bool) { + _ = pager.EachPage(ctx, yieldPage(roles.ExtractRoleAssignments, yield)) + } +} + +func (c roleassignmentClient) CreateRoleAssignment(ctx context.Context, opts roles.CreateOptsBuilder) (*roles.RoleAssignment, error) { + return roles.Create(ctx, c.client, opts).Extract() +} + +func (c roleassignmentClient) DeleteRoleAssignment(ctx context.Context, resourceID string) error { + return roles.Delete(ctx, c.client, resourceID).ExtractErr() +} + +func (c roleassignmentClient) GetRoleAssignment(ctx context.Context, resourceID string) (*roles.RoleAssignment, error) { + return roles.Get(ctx, c.client, resourceID).Extract() +} + +func (c roleassignmentClient) UpdateRoleAssignment(ctx context.Context, id string, opts roles.UpdateOptsBuilder) (*roles.RoleAssignment, error) { + return roles.Update(ctx, c.client, id, opts).Extract() +} + +type roleassignmentErrorClient struct{ error } + +// NewRoleAssignmentErrorClient returns a RoleAssignmentClient in which every method returns the given error. +func NewRoleAssignmentErrorClient(e error) RoleAssignmentClient { + return roleassignmentErrorClient{e} +} + +func (e roleassignmentErrorClient) ListRoleAssignments(_ context.Context, _ roles.ListOptsBuilder) iter.Seq2[*roles.RoleAssignment, error] { + return func(yield func(*roles.RoleAssignment, error) bool) { + yield(nil, e.error) + } +} + +func (e roleassignmentErrorClient) CreateRoleAssignment(_ context.Context, _ roles.CreateOptsBuilder) (*roles.RoleAssignment, error) { + return nil, e.error +} + +func (e roleassignmentErrorClient) DeleteRoleAssignment(_ context.Context, _ string) error { + return e.error +} + +func (e roleassignmentErrorClient) GetRoleAssignment(_ context.Context, _ string) (*roles.RoleAssignment, error) { + return nil, e.error +} + +func (e roleassignmentErrorClient) UpdateRoleAssignment(_ context.Context, _ string, _ roles.UpdateOptsBuilder) (*roles.RoleAssignment, error) { + return nil, e.error +} diff --git a/test/apivalidations/roleassignment_test.go b/test/apivalidations/roleassignment_test.go new file mode 100644 index 000000000..64efff717 --- /dev/null +++ b/test/apivalidations/roleassignment_test.go @@ -0,0 +1,176 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package apivalidations + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + applyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" +) + +const ( + roleassignmentName = "roleassignment" + roleassignmentID = "265c9e4f-0f5a-46e4-9f3f-fb8de25ae120" +) + +func roleassignmentStub(namespace *corev1.Namespace) *orcv1alpha1.RoleAssignment { + obj := &orcv1alpha1.RoleAssignment{} + obj.Name = roleassignmentName + obj.Namespace = namespace.Name + return obj +} + +func testRoleAssignmentResource() *applyconfigv1alpha1.RoleAssignmentResourceSpecApplyConfiguration { + return applyconfigv1alpha1.RoleAssignmentResourceSpec(). + WithRoleRef("role") +} + +func baseRoleAssignmentPatch(obj client.Object) *applyconfigv1alpha1.RoleAssignmentApplyConfiguration { + return applyconfigv1alpha1.RoleAssignment(obj.GetName(), obj.GetNamespace()). + WithSpec(applyconfigv1alpha1.RoleAssignmentSpec(). + WithCloudCredentialsRef(testCredentials())) +} + +func testRoleAssignmentImport() *applyconfigv1alpha1.RoleAssignmentImportApplyConfiguration { + return applyconfigv1alpha1.RoleAssignmentImport().WithID(roleassignmentID) +} + +var _ = Describe("ORC RoleAssignment API validations", func() { + var namespace *corev1.Namespace + BeforeEach(func() { + namespace = createNamespace() + }) + + runManagementPolicyTests(func() *corev1.Namespace { return namespace }, managementPolicyTestArgs[*applyconfigv1alpha1.RoleAssignmentApplyConfiguration]{ + createObject: func(ns *corev1.Namespace) client.Object { return roleassignmentStub(ns) }, + basePatch: func(obj client.Object) *applyconfigv1alpha1.RoleAssignmentApplyConfiguration { + return baseRoleAssignmentPatch(obj) + }, + applyResource: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { + p.Spec.WithResource(testRoleAssignmentResource()) + }, + applyImport: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { + p.Spec.WithImport(testRoleAssignmentImport()) + }, + applyEmptyImport: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.RoleAssignmentImport()) + }, + applyEmptyFilter: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.RoleAssignmentImport().WithFilter(applyconfigv1alpha1.RoleAssignmentFilter())) + }, + applyValidFilter: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { + p.Spec.WithImport(applyconfigv1alpha1.RoleAssignmentImport().WithFilter(applyconfigv1alpha1.RoleAssignmentFilter().WithName("foo"))) + }, + applyManaged: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) + }, + applyUnmanaged: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { + p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyUnmanaged) + }, + applyManagedOptions: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { + p.Spec.WithManagedOptions(applyconfigv1alpha1.ManagedOptions().WithOnDelete(orcv1alpha1.OnDeleteDetach)) + }, + getManagementPolicy: func(obj client.Object) orcv1alpha1.ManagementPolicy { + return obj.(*orcv1alpha1.RoleAssignment).Spec.ManagementPolicy + }, + getOnDelete: func(obj client.Object) orcv1alpha1.OnDelete { + return obj.(*orcv1alpha1.RoleAssignment).Spec.ManagedOptions.OnDelete + }, + }) + + It("should reject a roleassignment without required fields", func(ctx context.Context) { + obj := roleassignmentStub(namespace) + patch := baseRoleAssignmentPatch(obj) + patch.Spec.WithResource(applyconfigv1alpha1.RoleAssignmentResourceSpec()) + Expect(applyObj(ctx, obj, patch)).NotTo(Succeed()) + }) + + It("should have immutable roleRef", func(ctx context.Context) { + obj := roleassignmentStub(namespace) + patch := baseRoleAssignmentPatch(obj) + patch.Spec.WithResource(testRoleAssignmentResource(). + WithRoleRef("role-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(testRoleAssignmentResource(). + WithRoleRef("role-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("roleRef is immutable"))) + }) + + It("should have immutable userRef", func(ctx context.Context) { + obj := roleassignmentStub(namespace) + patch := baseRoleAssignmentPatch(obj) + patch.Spec.WithResource(testRoleAssignmentResource(). + WithUserRef("user-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(testRoleAssignmentResource(). + WithUserRef("user-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("userRef is immutable"))) + }) + + It("should have immutable groupRef", func(ctx context.Context) { + obj := roleassignmentStub(namespace) + patch := baseRoleAssignmentPatch(obj) + patch.Spec.WithResource(testRoleAssignmentResource(). + WithGroupRef("group-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(testRoleAssignmentResource(). + WithGroupRef("group-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("groupRef is immutable"))) + }) + + It("should have immutable projectRef", func(ctx context.Context) { + obj := roleassignmentStub(namespace) + patch := baseRoleAssignmentPatch(obj) + patch.Spec.WithResource(testRoleAssignmentResource(). + WithProjectRef("project-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(testRoleAssignmentResource(). + WithProjectRef("project-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("projectRef is immutable"))) + }) + + It("should have immutable domainRef", func(ctx context.Context) { + obj := roleassignmentStub(namespace) + patch := baseRoleAssignmentPatch(obj) + patch.Spec.WithResource(testRoleAssignmentResource(). + WithDomainRef("domain-a")) + Expect(applyObj(ctx, obj, patch)).To(Succeed()) + + patch.Spec.WithResource(testRoleAssignmentResource(). + WithDomainRef("domain-b")) + Expect(applyObj(ctx, obj, patch)).To(MatchError(ContainSubstring("domainRef is immutable"))) + }) + + // TODO(scaffolding): Add more resource-specific validation tests. + // Some common things to test: + // - Immutability of fields with `self == oldSelf` validation + // - Enum validation (valid and invalid values) + // - Numeric range validation (min/max bounds) + // - Tag uniqueness (if the resource has tags with listType=set) + // - Format validation (CIDR, UUID, etc.) + // - Cross-field validation rules +}) From 149453837c47d285bde3591f28dbfb771c76eee6 Mon Sep 17 00:00:00 2001 From: Daniel Lawton Date: Fri, 17 Apr 2026 15:46:06 +0100 Subject: [PATCH 2/2] RoleAssignmet Controller: Immutable spec: Role assignments can't be modified after creation (matching Kubernetes RBAC) Synthetic ID: Format role::user::project: since OpenStack doesn't assign IDs to role assignments Deletion guards: All dependencies (Role, User/Group, Project/Domain) are protected from deletion while in use Signed-off-by: Daniel Lawton --- PROJECT | 8 + api/v1alpha1/roleassignment_types.go | 100 ++-- api/v1alpha1/zz_generated.deepcopy.go | 237 ++++++++++ .../zz_generated.roleassignment-resource.go | 179 +++++++ cmd/manager/main.go | 2 + cmd/models-schema/zz_generated.openapi.go | 383 +++++++++++++++ cmd/resource-generator/main.go | 4 + ...openstack.k-orc.cloud_roleassignments.yaml | 348 ++++++++++++++ config/crd/kustomization.yaml | 1 + config/rbac/role.yaml | 2 + config/samples/kustomization.yaml | 1 + .../openstack_v1alpha1_roleassignment.yaml | 26 +- .../controllers/roleassignment/actuator.go | 442 ++++++++++++------ .../roleassignment/actuator_test.go | 103 +--- internal/controllers/roleassignment/status.go | 48 +- .../roleassignment-create-full/00-assert.yaml | 53 --- .../00-create-resource.yaml | 85 ---- .../roleassignment-create-full/00-secret.yaml | 6 - .../roleassignment-create-full/README.md | 11 - .../00-assert.yaml | 62 ++- .../00-create-resource.yaml | 46 +- .../00-secret.yaml | 23 +- .../01-assert.yaml | 40 +- .../01-delete-roleassignment.yaml} | 2 +- .../01-delete-secret.yaml | 7 - .../roleassignment-create-minimal/README.md | 8 +- .../roleassignment-dependency/00-assert.yaml | 91 +--- .../00-create-resources-missing-deps.yaml | 95 +--- .../roleassignment-dependency/00-secret.yaml | 23 +- .../roleassignment-dependency/01-assert.yaml | 110 ++--- .../01-create-dependencies.yaml | 60 +-- .../roleassignment-dependency/02-assert.yaml | 60 +-- .../02-delete-dependencies.yaml | 19 +- .../roleassignment-dependency/03-assert.yaml | 15 +- .../03-delete-resources.yaml | 22 +- .../tests/roleassignment-dependency/README.md | 14 +- .../00-assert.yaml | 35 ++ .../00-create-resource.yaml | 54 +++ .../00-secret.yaml | 19 + .../roleassignment-group-domain/README.md | 11 + .../00-assert.yaml | 25 - .../00-import-resource.yaml | 82 ---- .../00-secret.yaml | 6 - .../01-assert.yaml | 40 -- .../01-create-trap-resource.yaml | 84 ---- .../02-assert.yaml | 54 --- .../02-create-resource.yaml | 83 ---- .../03-assert.yaml | 14 - .../03-delete-import-dependencies.yaml | 15 - .../04-assert.yaml | 6 - .../README.md | 29 -- .../00-assert.yaml | 30 -- .../00-create-resources.yaml | 43 -- .../00-secret.yaml | 6 - .../01-assert.yaml | 15 - .../01-import-resource.yaml | 13 - .../roleassignment-import-error/README.md | 13 - .../roleassignment-import/00-assert.yaml | 15 - .../00-import-resource.yaml | 15 - .../roleassignment-import/00-secret.yaml | 6 - .../roleassignment-import/01-assert.yaml | 34 -- .../01-create-trap-resource.yaml | 31 -- .../roleassignment-import/02-assert.yaml | 33 -- .../02-create-resource.yaml | 28 -- .../tests/roleassignment-import/README.md | 18 - .../roleassignment-update/00-assert.yaml | 26 -- .../00-minimal-resource.yaml | 28 -- .../roleassignment-update/00-secret.yaml | 6 - .../roleassignment-update/01-assert.yaml | 17 - .../01-updated-resource.yaml | 10 - .../roleassignment-update/02-assert.yaml | 26 -- .../02-reverted-resource.yaml | 7 - .../tests/roleassignment-update/README.md | 17 - .../roleassignment/zz_generated.adapter.go | 78 ++++ .../roleassignment/zz_generated.controller.go | 45 ++ internal/osclients/mock/doc.go | 3 + internal/osclients/mock/roleassignment.go | 100 ++++ internal/osclients/roleassignment.go | 48 +- internal/scope/mock.go | 7 + internal/scope/provider.go | 4 + internal/scope/scope.go | 1 + kuttl-test.yaml | 1 + .../api/v1alpha1/roleassignment.go | 281 +++++++++++ .../api/v1alpha1/roleassignmentfilter.go | 79 ++++ .../api/v1alpha1/roleassignmentimport.go | 48 ++ .../v1alpha1/roleassignmentresourcespec.go | 79 ++++ .../v1alpha1/roleassignmentresourcestatus.go | 75 +++ .../api/v1alpha1/roleassignmentspec.go | 79 ++++ .../api/v1alpha1/roleassignmentstatus.go | 66 +++ .../applyconfiguration/internal/internal.go | 120 +++++ pkg/clients/applyconfiguration/utils.go | 14 + .../typed/api/v1alpha1/api_client.go | 5 + .../api/v1alpha1/fake/fake_api_client.go | 4 + .../api/v1alpha1/fake/fake_roleassignment.go | 53 +++ .../typed/api/v1alpha1/generated_expansion.go | 2 + .../typed/api/v1alpha1/roleassignment.go | 74 +++ .../api/v1alpha1/interface.go | 7 + .../api/v1alpha1/roleassignment.go | 102 ++++ .../informers/externalversions/generic.go | 2 + .../api/v1alpha1/expansion_generated.go | 8 + .../listers/api/v1alpha1/roleassignment.go | 70 +++ test/apivalidations/roleassignment_test.go | 2 +- website/docs/crd-reference.md | 149 ++++++ 103 files changed, 3500 insertions(+), 1836 deletions(-) create mode 100644 api/v1alpha1/zz_generated.roleassignment-resource.go create mode 100644 config/crd/bases/openstack.k-orc.cloud_roleassignments.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-full/00-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-full/00-create-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-full/00-secret.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-full/README.md rename internal/controllers/roleassignment/tests/{roleassignment-import-dependency/04-delete-resource.yaml => roleassignment-create-minimal/01-delete-roleassignment.yaml} (75%) delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-group-domain/00-assert.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-group-domain/00-create-resource.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-group-domain/00-secret.yaml create mode 100644 internal/controllers/roleassignment/tests/roleassignment-group-domain/README.md delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import-error/README.md delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-import/README.md delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/00-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/00-minimal-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/00-secret.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/01-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/01-updated-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/02-assert.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/02-reverted-resource.yaml delete mode 100644 internal/controllers/roleassignment/tests/roleassignment-update/README.md create mode 100644 internal/controllers/roleassignment/zz_generated.adapter.go create mode 100644 internal/controllers/roleassignment/zz_generated.controller.go create mode 100644 internal/osclients/mock/roleassignment.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/roleassignment.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentfilter.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentimport.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentresourcespec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentresourcestatus.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentspec.go create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentstatus.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_roleassignment.go create mode 100644 pkg/clients/clientset/clientset/typed/api/v1alpha1/roleassignment.go create mode 100644 pkg/clients/informers/externalversions/api/v1alpha1/roleassignment.go create mode 100644 pkg/clients/listers/api/v1alpha1/roleassignment.go diff --git a/PROJECT b/PROJECT index 73daa42d0..cd63db01a 100644 --- a/PROJECT +++ b/PROJECT @@ -112,6 +112,14 @@ resources: kind: Role path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + domain: k-orc.cloud + group: openstack + kind: RoleAssignment + path: github.com/k-orc/openstack-resource-controller/api/v1alpha1 + version: v1alpha1 - api: crdVersion: v1 namespaced: true diff --git a/api/v1alpha1/roleassignment_types.go b/api/v1alpha1/roleassignment_types.go index 36e82ff05..b2a947975 100644 --- a/api/v1alpha1/roleassignment_types.go +++ b/api/v1alpha1/roleassignment_types.go @@ -16,129 +16,89 @@ limitations under the License. package v1alpha1 -// RoleAssignmentResourceSpec contains the desired state of the resource. +// RoleAssignmentResourceSpec defines the desired role assignment. +// A role assignment grants a role to a user or group on a project or domain. +// Role assignments are immutable once created and identified by the combination +// of (role, actor, scope) rather than a separate ID. +// +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" +// +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" +// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="RoleAssignmentResourceSpec is immutable" type RoleAssignmentResourceSpec struct { - // name will be the name of the created resource. If not specified, the - // name of the ORC object will be used. - // +optional - Name *OpenStackName `json:"name,omitempty"` - - // description is a human-readable description for the resource. - // +kubebuilder:validation:MinLength:=1 - // +kubebuilder:validation:MaxLength:=255 - // +optional - Description *string `json:"description,omitempty"` - - // roleRef is a reference to the ORC Role which this resource is associated with. + // roleRef references the Role being assigned. // +required - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="roleRef is immutable" RoleRef KubernetesNameRef `json:"roleRef,omitempty"` - // userRef is a reference to the ORC User which this resource is associated with. + // userRef references the User receiving the role assignment. + // Exactly one of userRef or groupRef must be specified. // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="userRef is immutable" UserRef *KubernetesNameRef `json:"userRef,omitempty"` - // groupRef is a reference to the ORC Group which this resource is associated with. + // groupRef references the Group receiving the role assignment. + // Exactly one of userRef or groupRef must be specified. // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="groupRef is immutable" GroupRef *KubernetesNameRef `json:"groupRef,omitempty"` - // projectRef is a reference to the ORC Project which this resource is associated with. + // projectRef references the Project scope for the assignment. + // Exactly one of projectRef or domainRef must be specified. // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="projectRef is immutable" ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` - // domainRef is a reference to the ORC Domain which this resource is associated with. + // domainRef references the Domain scope for the assignment. + // Exactly one of projectRef or domainRef must be specified. // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="domainRef is immutable" DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` - - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the CreateOpts structure from - // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles - // - // Until you have implemented mutability for the field, you must add a CEL validation - // preventing the field being modified: - // `// +kubebuilder:validation:XValidation:rule="self == oldSelf",message=" is immutable"` } -// RoleAssignmentFilter defines an existing resource by its properties +// RoleAssignmentFilter defines import filter criteria for existing role assignments. // +kubebuilder:validation:MinProperties:=1 type RoleAssignmentFilter struct { - // name of the existing resource - // +optional - Name *OpenStackName `json:"name,omitempty"` - - // description of the existing resource - // +kubebuilder:validation:MinLength:=1 - // +kubebuilder:validation:MaxLength:=255 - // +optional - Description *string `json:"description,omitempty"` - - // roleRef is a reference to the ORC Role which this resource is associated with. + // roleRef filters by the referenced Role. // +optional RoleRef *KubernetesNameRef `json:"roleRef,omitempty"` - // userRef is a reference to the ORC User which this resource is associated with. + // userRef filters by the referenced User. // +optional UserRef *KubernetesNameRef `json:"userRef,omitempty"` - // groupRef is a reference to the ORC Group which this resource is associated with. + // groupRef filters by the referenced Group. // +optional GroupRef *KubernetesNameRef `json:"groupRef,omitempty"` - // projectRef is a reference to the ORC Project which this resource is associated with. + // projectRef filters by the referenced Project scope. // +optional ProjectRef *KubernetesNameRef `json:"projectRef,omitempty"` - // domainRef is a reference to the ORC Domain which this resource is associated with. + // domainRef filters by the referenced Domain scope. // +optional DomainRef *KubernetesNameRef `json:"domainRef,omitempty"` - - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the ListOpts structure from - // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles } -// RoleAssignmentResourceStatus represents the observed state of the resource. +// RoleAssignmentResourceStatus represents the observed state of the role assignment. +// Note: Role assignments do not have a unique ID in OpenStack - they are identified +// by the combination of role, actor (user/group), and scope (project/domain). type RoleAssignmentResourceStatus struct { - // name is a Human-readable name for the resource. Might not be unique. - // +kubebuilder:validation:MaxLength=1024 - // +optional - Name string `json:"name,omitempty"` - - // description is a human-readable description for the resource. - // +kubebuilder:validation:MaxLength=1024 - // +optional - Description string `json:"description,omitempty"` - - // roleID is the ID of the Role to which the resource is associated. + // roleID is the OpenStack ID of the assigned role. // +kubebuilder:validation:MaxLength=1024 // +optional RoleID string `json:"roleID,omitempty"` - // userID is the ID of the User to which the resource is associated. + // userID is the OpenStack ID of the user (if actorType is User). // +kubebuilder:validation:MaxLength=1024 // +optional UserID string `json:"userID,omitempty"` - // groupID is the ID of the Group to which the resource is associated. + // groupID is the OpenStack ID of the group (if actorType is Group). // +kubebuilder:validation:MaxLength=1024 // +optional GroupID string `json:"groupID,omitempty"` - // projectID is the ID of the Project to which the resource is associated. + // projectID is the OpenStack ID of the project scope (if scopeType is Project). // +kubebuilder:validation:MaxLength=1024 // +optional ProjectID string `json:"projectID,omitempty"` - // domainID is the ID of the Domain to which the resource is associated. + // domainID is the OpenStack ID of the domain scope (if scopeType is Domain). // +kubebuilder:validation:MaxLength=1024 // +optional DomainID string `json:"domainID,omitempty"` - - // TODO(scaffolding): Add more types. - // To see what is supported, you can take inspiration from the RoleAssignment structure from - // github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 6f5bf196d..05c878671 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3745,6 +3745,243 @@ func (in *Role) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleAssignment) DeepCopyInto(out *RoleAssignment) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleAssignment. +func (in *RoleAssignment) DeepCopy() *RoleAssignment { + if in == nil { + return nil + } + out := new(RoleAssignment) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RoleAssignment) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleAssignmentFilter) DeepCopyInto(out *RoleAssignmentFilter) { + *out = *in + if in.RoleRef != nil { + in, out := &in.RoleRef, &out.RoleRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.UserRef != nil { + in, out := &in.UserRef, &out.UserRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.GroupRef != nil { + in, out := &in.GroupRef, &out.GroupRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.ProjectRef != nil { + in, out := &in.ProjectRef, &out.ProjectRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.DomainRef != nil { + in, out := &in.DomainRef, &out.DomainRef + *out = new(KubernetesNameRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleAssignmentFilter. +func (in *RoleAssignmentFilter) DeepCopy() *RoleAssignmentFilter { + if in == nil { + return nil + } + out := new(RoleAssignmentFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleAssignmentImport) DeepCopyInto(out *RoleAssignmentImport) { + *out = *in + if in.ID != nil { + in, out := &in.ID, &out.ID + *out = new(string) + **out = **in + } + if in.Filter != nil { + in, out := &in.Filter, &out.Filter + *out = new(RoleAssignmentFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleAssignmentImport. +func (in *RoleAssignmentImport) DeepCopy() *RoleAssignmentImport { + if in == nil { + return nil + } + out := new(RoleAssignmentImport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleAssignmentList) DeepCopyInto(out *RoleAssignmentList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RoleAssignment, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleAssignmentList. +func (in *RoleAssignmentList) DeepCopy() *RoleAssignmentList { + if in == nil { + return nil + } + out := new(RoleAssignmentList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RoleAssignmentList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleAssignmentResourceSpec) DeepCopyInto(out *RoleAssignmentResourceSpec) { + *out = *in + if in.UserRef != nil { + in, out := &in.UserRef, &out.UserRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.GroupRef != nil { + in, out := &in.GroupRef, &out.GroupRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.ProjectRef != nil { + in, out := &in.ProjectRef, &out.ProjectRef + *out = new(KubernetesNameRef) + **out = **in + } + if in.DomainRef != nil { + in, out := &in.DomainRef, &out.DomainRef + *out = new(KubernetesNameRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleAssignmentResourceSpec. +func (in *RoleAssignmentResourceSpec) DeepCopy() *RoleAssignmentResourceSpec { + if in == nil { + return nil + } + out := new(RoleAssignmentResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleAssignmentResourceStatus) DeepCopyInto(out *RoleAssignmentResourceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleAssignmentResourceStatus. +func (in *RoleAssignmentResourceStatus) DeepCopy() *RoleAssignmentResourceStatus { + if in == nil { + return nil + } + out := new(RoleAssignmentResourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleAssignmentSpec) DeepCopyInto(out *RoleAssignmentSpec) { + *out = *in + if in.Import != nil { + in, out := &in.Import, &out.Import + *out = new(RoleAssignmentImport) + (*in).DeepCopyInto(*out) + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(RoleAssignmentResourceSpec) + (*in).DeepCopyInto(*out) + } + if in.ManagedOptions != nil { + in, out := &in.ManagedOptions, &out.ManagedOptions + *out = new(ManagedOptions) + **out = **in + } + out.CloudCredentialsRef = in.CloudCredentialsRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleAssignmentSpec. +func (in *RoleAssignmentSpec) DeepCopy() *RoleAssignmentSpec { + if in == nil { + return nil + } + out := new(RoleAssignmentSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoleAssignmentStatus) DeepCopyInto(out *RoleAssignmentStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ID != nil { + in, out := &in.ID, &out.ID + *out = new(string) + **out = **in + } + if in.Resource != nil { + in, out := &in.Resource, &out.Resource + *out = new(RoleAssignmentResourceStatus) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoleAssignmentStatus. +func (in *RoleAssignmentStatus) DeepCopy() *RoleAssignmentStatus { + if in == nil { + return nil + } + out := new(RoleAssignmentStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RoleFilter) DeepCopyInto(out *RoleFilter) { *out = *in diff --git a/api/v1alpha1/zz_generated.roleassignment-resource.go b/api/v1alpha1/zz_generated.roleassignment-resource.go new file mode 100644 index 000000000..03d1eee39 --- /dev/null +++ b/api/v1alpha1/zz_generated.roleassignment-resource.go @@ -0,0 +1,179 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// RoleAssignmentImport specifies an existing resource which will be imported instead of +// creating a new one +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +type RoleAssignmentImport struct { + // id contains the unique identifier of an existing OpenStack resource. Note + // that when specifying an import by ID, the resource MUST already exist. + // The ORC object will enter an error state if the resource does not exist. + // +kubebuilder:validation:Format:=uuid + // +kubebuilder:validation:MaxLength:=36 + // +optional + ID *string `json:"id,omitempty"` //nolint:kubeapilinter + + // filter contains a resource query which is expected to return a single + // result. The controller will continue to retry if filter returns no + // results. If filter returns multiple results the controller will set an + // error state and will not continue to retry. + // +optional + Filter *RoleAssignmentFilter `json:"filter,omitempty"` +} + +// RoleAssignmentSpec defines the desired state of an ORC object. +// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'managed' ? has(self.resource) : true",message="resource must be specified when policy is managed" +// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'managed' ? !has(self.__import__) : true",message="import may not be specified when policy is managed" +// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'unmanaged' ? !has(self.resource) : true",message="resource may not be specified when policy is unmanaged" +// +kubebuilder:validation:XValidation:rule="self.managementPolicy == 'unmanaged' ? has(self.__import__) : true",message="import must be specified when policy is unmanaged" +// +kubebuilder:validation:XValidation:rule="has(self.managedOptions) ? self.managementPolicy == 'managed' : true",message="managedOptions may only be provided when policy is managed" +type RoleAssignmentSpec struct { + // import refers to an existing OpenStack resource which will be imported instead of + // creating a new one. + // +optional + Import *RoleAssignmentImport `json:"import,omitempty"` + + // resource specifies the desired state of the resource. + // + // resource may not be specified if the management policy is `unmanaged`. + // + // resource must be specified if the management policy is `managed`. + // +optional + Resource *RoleAssignmentResourceSpec `json:"resource,omitempty"` + + // managementPolicy defines how ORC will treat the object. Valid values are + // `managed`: ORC will create, update, and delete the resource; `unmanaged`: + // ORC will import an existing resource, and will not apply updates to it or + // delete it. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="managementPolicy is immutable" + // +kubebuilder:default:=managed + // +optional + ManagementPolicy ManagementPolicy `json:"managementPolicy,omitempty"` + + // managedOptions specifies options which may be applied to managed objects. + // +optional + ManagedOptions *ManagedOptions `json:"managedOptions,omitempty"` + + // cloudCredentialsRef points to a secret containing OpenStack credentials + // +required + CloudCredentialsRef CloudCredentialsReference `json:"cloudCredentialsRef,omitzero"` +} + +// RoleAssignmentStatus defines the observed state of an ORC resource. +type RoleAssignmentStatus struct { + // conditions represents the observed status of the object. + // Known .status.conditions.type are: "Available", "Progressing" + // + // Available represents the availability of the OpenStack resource. If it is + // true then the resource is ready for use. + // + // Progressing indicates whether the controller is still attempting to + // reconcile the current state of the OpenStack resource to the desired + // state. Progressing will be False either because the desired state has + // been achieved, or because some terminal error prevents it from ever being + // achieved and the controller is no longer attempting to reconcile. If + // Progressing is True, an observer waiting on the resource should continue + // to wait. + // + // +kubebuilder:validation:MaxItems:=32 + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + + // id is the unique identifier of the OpenStack resource. + // +kubebuilder:validation:MaxLength:=1024 + // +optional + ID *string `json:"id,omitempty"` + + // resource contains the observed state of the OpenStack resource. + // +optional + Resource *RoleAssignmentResourceStatus `json:"resource,omitempty"` +} + +var _ ObjectWithConditions = &RoleAssignment{} + +func (i *RoleAssignment) GetConditions() []metav1.Condition { + return i.Status.Conditions +} + +// +genclient +// +kubebuilder:object:root=true +// +kubebuilder:resource:categories=openstack +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="ID",type="string",JSONPath=".status.id",description="Resource ID" +// +kubebuilder:printcolumn:name="Available",type="string",JSONPath=".status.conditions[?(@.type=='Available')].status",description="Availability status of resource" +// +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[?(@.type=='Progressing')].message",description="Message describing current progress status" + +// RoleAssignment is the Schema for an ORC resource. +type RoleAssignment struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // spec specifies the desired state of the resource. + // +required + Spec RoleAssignmentSpec `json:"spec,omitzero"` + + // status defines the observed state of the resource. + // +optional + Status RoleAssignmentStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// RoleAssignmentList contains a list of RoleAssignment. +type RoleAssignmentList struct { + metav1.TypeMeta `json:",inline"` + + // metadata contains the list metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + // items contains a list of RoleAssignment. + // +required + Items []RoleAssignment `json:"items"` +} + +func (l *RoleAssignmentList) GetItems() []RoleAssignment { + return l.Items +} + +func init() { + SchemeBuilder.Register(&RoleAssignment{}, &RoleAssignmentList{}) +} + +func (i *RoleAssignment) GetCloudCredentialsRef() (*string, *CloudCredentialsReference) { + if i == nil { + return nil, nil + } + + return &i.Namespace, &i.Spec.CloudCredentialsRef +} + +var _ CloudCredentialsRefProvider = &RoleAssignment{} diff --git a/cmd/manager/main.go b/cmd/manager/main.go index c8a624acc..8ec80848a 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -41,6 +41,7 @@ import ( "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/port" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/project" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/role" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/roleassignment" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/router" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/routerinterface" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/securitygroup" @@ -136,6 +137,7 @@ func main() { keypair.New(scopeFactory), group.New(scopeFactory), role.New(scopeFactory), + roleassignment.New(scopeFactory), } restConfig := ctrl.GetConfigOrDie() diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 557911992..5f8061c70 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -158,6 +158,14 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProjectStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProjectStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ProviderPropertiesStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ProviderPropertiesStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Role": schema_openstack_resource_controller_v2_api_v1alpha1_Role(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignment": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignment(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentFilter": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentFilter(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentImport": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentImport(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentList": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentList(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentResourceSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentResourceStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentSpec": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentSpec(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentStatus": schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleFilter": schema_openstack_resource_controller_v2_api_v1alpha1_RoleFilter(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleImport": schema_openstack_resource_controller_v2_api_v1alpha1_RoleImport(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleList": schema_openstack_resource_controller_v2_api_v1alpha1_RoleList(ref), @@ -7125,6 +7133,381 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Role(ref common.Refere } } +func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignment(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleAssignment is the Schema for an ORC resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "metadata contains the object metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "spec specifies the desired state of the resource.", + Default: map[string]interface{}{}, + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status defines the observed state of the resource.", + Default: map[string]interface{}{}, + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleAssignmentFilter defines import filter criteria for existing role assignments.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "roleRef": { + SchemaProps: spec.SchemaProps{ + Description: "roleRef filters by the referenced Role.", + Type: []string{"string"}, + Format: "", + }, + }, + "userRef": { + SchemaProps: spec.SchemaProps{ + Description: "userRef filters by the referenced User.", + Type: []string{"string"}, + Format: "", + }, + }, + "groupRef": { + SchemaProps: spec.SchemaProps{ + Description: "groupRef filters by the referenced Group.", + Type: []string{"string"}, + Format: "", + }, + }, + "projectRef": { + SchemaProps: spec.SchemaProps{ + Description: "projectRef filters by the referenced Project scope.", + Type: []string{"string"}, + Format: "", + }, + }, + "domainRef": { + SchemaProps: spec.SchemaProps{ + Description: "domainRef filters by the referenced Domain scope.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentImport(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleAssignmentImport specifies an existing resource which will be imported instead of creating a new one", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "id": { + SchemaProps: spec.SchemaProps{ + Description: "id contains the unique identifier of an existing OpenStack resource. Note that when specifying an import by ID, the resource MUST already exist. The ORC object will enter an error state if the resource does not exist.", + Type: []string{"string"}, + Format: "", + }, + }, + "filter": { + SchemaProps: spec.SchemaProps{ + Description: "filter contains a resource query which is expected to return a single result. The controller will continue to retry if filter returns no results. If filter returns multiple results the controller will set an error state and will not continue to retry.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentFilter"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentFilter"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleAssignmentList contains a list of RoleAssignment.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "metadata contains the list metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "items contains a list of RoleAssignment.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignment"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignment", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentResourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleAssignmentResourceSpec defines the desired role assignment. A role assignment grants a role to a user or group on a project or domain. Role assignments are immutable once created and identified by the combination of (role, actor, scope) rather than a separate ID.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "roleRef": { + SchemaProps: spec.SchemaProps{ + Description: "roleRef references the Role being assigned.", + Type: []string{"string"}, + Format: "", + }, + }, + "userRef": { + SchemaProps: spec.SchemaProps{ + Description: "userRef references the User receiving the role assignment. Exactly one of userRef or groupRef must be specified.", + Type: []string{"string"}, + Format: "", + }, + }, + "groupRef": { + SchemaProps: spec.SchemaProps{ + Description: "groupRef references the Group receiving the role assignment. Exactly one of userRef or groupRef must be specified.", + Type: []string{"string"}, + Format: "", + }, + }, + "projectRef": { + SchemaProps: spec.SchemaProps{ + Description: "projectRef references the Project scope for the assignment. Exactly one of projectRef or domainRef must be specified.", + Type: []string{"string"}, + Format: "", + }, + }, + "domainRef": { + SchemaProps: spec.SchemaProps{ + Description: "domainRef references the Domain scope for the assignment. Exactly one of projectRef or domainRef must be specified.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"roleRef"}, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleAssignmentResourceStatus represents the observed state of the role assignment. Note: Role assignments do not have a unique ID in OpenStack - they are identified by the combination of role, actor (user/group), and scope (project/domain).", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "roleID": { + SchemaProps: spec.SchemaProps{ + Description: "roleID is the OpenStack ID of the assigned role.", + Type: []string{"string"}, + Format: "", + }, + }, + "userID": { + SchemaProps: spec.SchemaProps{ + Description: "userID is the OpenStack ID of the user (if actorType is User).", + Type: []string{"string"}, + Format: "", + }, + }, + "groupID": { + SchemaProps: spec.SchemaProps{ + Description: "groupID is the OpenStack ID of the group (if actorType is Group).", + Type: []string{"string"}, + Format: "", + }, + }, + "projectID": { + SchemaProps: spec.SchemaProps{ + Description: "projectID is the OpenStack ID of the project scope (if scopeType is Project).", + Type: []string{"string"}, + Format: "", + }, + }, + "domainID": { + SchemaProps: spec.SchemaProps{ + Description: "domainID is the OpenStack ID of the domain scope (if scopeType is Domain).", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleAssignmentSpec defines the desired state of an ORC object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "import": { + SchemaProps: spec.SchemaProps{ + Description: "import refers to an existing OpenStack resource which will be imported instead of creating a new one.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentImport"), + }, + }, + "resource": { + SchemaProps: spec.SchemaProps{ + Description: "resource specifies the desired state of the resource.\n\nresource may not be specified if the management policy is `unmanaged`.\n\nresource must be specified if the management policy is `managed`.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentResourceSpec"), + }, + }, + "managementPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "managementPolicy defines how ORC will treat the object. Valid values are `managed`: ORC will create, update, and delete the resource; `unmanaged`: ORC will import an existing resource, and will not apply updates to it or delete it.", + Type: []string{"string"}, + Format: "", + }, + }, + "managedOptions": { + SchemaProps: spec.SchemaProps{ + Description: "managedOptions specifies options which may be applied to managed objects.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions"), + }, + }, + "cloudCredentialsRef": { + SchemaProps: spec.SchemaProps{ + Description: "cloudCredentialsRef points to a secret containing OpenStack credentials", + Default: map[string]interface{}{}, + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference"), + }, + }, + }, + Required: []string{"cloudCredentialsRef"}, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ManagedOptions", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentImport", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentResourceSpec"}, + } +} + +func schema_openstack_resource_controller_v2_api_v1alpha1_RoleAssignmentStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RoleAssignmentStatus defines the observed state of an ORC resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "conditions represents the observed status of the object. Known .status.conditions.type are: \"Available\", \"Progressing\"\n\nAvailable represents the availability of the OpenStack resource. If it is true then the resource is ready for use.\n\nProgressing indicates whether the controller is still attempting to reconcile the current state of the OpenStack resource to the desired state. Progressing will be False either because the desired state has been achieved, or because some terminal error prevents it from ever being achieved and the controller is no longer attempting to reconcile. If Progressing is True, an observer waiting on the resource should continue to wait.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"), + }, + }, + }, + }, + }, + "id": { + SchemaProps: spec.SchemaProps{ + Description: "id is the unique identifier of the OpenStack resource.", + Type: []string{"string"}, + Format: "", + }, + }, + "resource": { + SchemaProps: spec.SchemaProps{ + Description: "resource contains the observed state of the OpenStack resource.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentResourceStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.RoleAssignmentResourceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_RoleFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/cmd/resource-generator/main.go b/cmd/resource-generator/main.go index 2ff83df6e..7e1bd4147 100644 --- a/cmd/resource-generator/main.go +++ b/cmd/resource-generator/main.go @@ -124,6 +124,10 @@ var resources []templateFields = []templateFields{ { Name: "Role", }, + { + Name: "RoleAssignment", + IsNotNamed: true, + }, { Name: "Router", ExistingOSClient: true, diff --git a/config/crd/bases/openstack.k-orc.cloud_roleassignments.yaml b/config/crd/bases/openstack.k-orc.cloud_roleassignments.yaml new file mode 100644 index 000000000..2c3123c35 --- /dev/null +++ b/config/crd/bases/openstack.k-orc.cloud_roleassignments.yaml @@ -0,0 +1,348 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: roleassignments.openstack.k-orc.cloud +spec: + group: openstack.k-orc.cloud + names: + categories: + - openstack + kind: RoleAssignment + listKind: RoleAssignmentList + plural: roleassignments + singular: roleassignment + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Resource ID + jsonPath: .status.id + name: ID + type: string + - description: Availability status of resource + jsonPath: .status.conditions[?(@.type=='Available')].status + name: Available + type: string + - description: Message describing current progress status + jsonPath: .status.conditions[?(@.type=='Progressing')].message + name: Message + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: RoleAssignment is the Schema for an ORC resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec specifies the desired state of the resource. + properties: + cloudCredentialsRef: + description: cloudCredentialsRef points to a secret containing OpenStack + credentials + properties: + cloudName: + description: cloudName specifies the name of the entry in the + clouds.yaml file to use. + maxLength: 256 + minLength: 1 + type: string + secretName: + description: |- + secretName is the name of a secret in the same namespace as the resource being provisioned. + The secret must contain a key named `clouds.yaml` which contains an OpenStack clouds.yaml file. + The secret may optionally contain a key named `cacert` containing a PEM-encoded CA certificate. + maxLength: 253 + minLength: 1 + type: string + required: + - cloudName + - secretName + type: object + import: + description: |- + import refers to an existing OpenStack resource which will be imported instead of + creating a new one. + maxProperties: 1 + minProperties: 1 + properties: + filter: + description: |- + filter contains a resource query which is expected to return a single + result. The controller will continue to retry if filter returns no + results. If filter returns multiple results the controller will set an + error state and will not continue to retry. + minProperties: 1 + properties: + domainRef: + description: domainRef filters by the referenced Domain scope. + maxLength: 253 + minLength: 1 + type: string + groupRef: + description: groupRef filters by the referenced Group. + maxLength: 253 + minLength: 1 + type: string + projectRef: + description: projectRef filters by the referenced Project + scope. + maxLength: 253 + minLength: 1 + type: string + roleRef: + description: roleRef filters by the referenced Role. + maxLength: 253 + minLength: 1 + type: string + userRef: + description: userRef filters by the referenced User. + maxLength: 253 + minLength: 1 + type: string + type: object + id: + description: |- + id contains the unique identifier of an existing OpenStack resource. Note + that when specifying an import by ID, the resource MUST already exist. + The ORC object will enter an error state if the resource does not exist. + format: uuid + maxLength: 36 + type: string + type: object + managedOptions: + description: managedOptions specifies options which may be applied + to managed objects. + properties: + onDelete: + default: delete + description: |- + onDelete specifies the behaviour of the controller when the ORC + object is deleted. Options are `delete` - delete the OpenStack resource; + `detach` - do not delete the OpenStack resource. If not specified, the + default is `delete`. + enum: + - delete + - detach + type: string + type: object + managementPolicy: + default: managed + description: |- + managementPolicy defines how ORC will treat the object. Valid values are + `managed`: ORC will create, update, and delete the resource; `unmanaged`: + ORC will import an existing resource, and will not apply updates to it or + delete it. + enum: + - managed + - unmanaged + type: string + x-kubernetes-validations: + - message: managementPolicy is immutable + rule: self == oldSelf + resource: + description: |- + resource specifies the desired state of the resource. + + resource may not be specified if the management policy is `unmanaged`. + + resource must be specified if the management policy is `managed`. + properties: + domainRef: + description: |- + domainRef references the Domain scope for the assignment. + Exactly one of projectRef or domainRef must be specified. + maxLength: 253 + minLength: 1 + type: string + groupRef: + description: |- + groupRef references the Group receiving the role assignment. + Exactly one of userRef or groupRef must be specified. + maxLength: 253 + minLength: 1 + type: string + projectRef: + description: |- + projectRef references the Project scope for the assignment. + Exactly one of projectRef or domainRef must be specified. + maxLength: 253 + minLength: 1 + type: string + roleRef: + description: roleRef references the Role being assigned. + maxLength: 253 + minLength: 1 + type: string + userRef: + description: |- + userRef references the User receiving the role assignment. + Exactly one of userRef or groupRef must be specified. + maxLength: 253 + minLength: 1 + type: string + required: + - roleRef + type: object + x-kubernetes-validations: + - message: exactly one of userRef or groupRef is required + rule: (has(self.userRef) && !has(self.groupRef)) || (!has(self.userRef) + && has(self.groupRef)) + - message: exactly one of projectRef or domainRef is required + rule: (has(self.projectRef) && !has(self.domainRef)) || (!has(self.projectRef) + && has(self.domainRef)) + - message: RoleAssignmentResourceSpec is immutable + rule: self == oldSelf + required: + - cloudCredentialsRef + type: object + x-kubernetes-validations: + - message: resource must be specified when policy is managed + rule: 'self.managementPolicy == ''managed'' ? has(self.resource) : true' + - message: import may not be specified when policy is managed + rule: 'self.managementPolicy == ''managed'' ? !has(self.__import__) + : true' + - message: resource may not be specified when policy is unmanaged + rule: 'self.managementPolicy == ''unmanaged'' ? !has(self.resource) + : true' + - message: import must be specified when policy is unmanaged + rule: 'self.managementPolicy == ''unmanaged'' ? has(self.__import__) + : true' + - message: managedOptions may only be provided when policy is managed + rule: 'has(self.managedOptions) ? self.managementPolicy == ''managed'' + : true' + status: + description: status defines the observed state of the resource. + properties: + conditions: + description: |- + conditions represents the observed status of the object. + Known .status.conditions.type are: "Available", "Progressing" + + Available represents the availability of the OpenStack resource. If it is + true then the resource is ready for use. + + Progressing indicates whether the controller is still attempting to + reconcile the current state of the OpenStack resource to the desired + state. Progressing will be False either because the desired state has + been achieved, or because some terminal error prevents it from ever being + achieved and the controller is no longer attempting to reconcile. If + Progressing is True, an observer waiting on the resource should continue + to wait. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 32 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + id: + description: id is the unique identifier of the OpenStack resource. + maxLength: 1024 + type: string + resource: + description: resource contains the observed state of the OpenStack + resource. + properties: + domainID: + description: domainID is the OpenStack ID of the domain scope + (if scopeType is Domain). + maxLength: 1024 + type: string + groupID: + description: groupID is the OpenStack ID of the group (if actorType + is Group). + maxLength: 1024 + type: string + projectID: + description: projectID is the OpenStack ID of the project scope + (if scopeType is Project). + maxLength: 1024 + type: string + roleID: + description: roleID is the OpenStack ID of the assigned role. + maxLength: 1024 + type: string + userID: + description: userID is the OpenStack ID of the user (if actorType + is User). + maxLength: 1024 + type: string + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 85a318b42..23338effb 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -16,6 +16,7 @@ resources: - bases/openstack.k-orc.cloud_ports.yaml - bases/openstack.k-orc.cloud_projects.yaml - bases/openstack.k-orc.cloud_roles.yaml +- bases/openstack.k-orc.cloud_roleassignments.yaml - bases/openstack.k-orc.cloud_routers.yaml - bases/openstack.k-orc.cloud_routerinterfaces.yaml - bases/openstack.k-orc.cloud_securitygroups.yaml diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 3b67eb9fa..1f7a41a45 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -29,6 +29,7 @@ rules: - networks - ports - projects + - roleassignments - roles - routerinterfaces - routers @@ -64,6 +65,7 @@ rules: - networks/status - ports/status - projects/status + - roleassignments/status - roles/status - routerinterfaces/status - routers/status diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 4b86755db..b20a311e7 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -14,6 +14,7 @@ resources: - openstack_v1alpha1_port.yaml - openstack_v1alpha1_project.yaml - openstack_v1alpha1_role.yaml +- openstack_v1alpha1_roleassignment.yaml - openstack_v1alpha1_router.yaml - openstack_v1alpha1_routerinterface.yaml - openstack_v1alpha1_securitygroup.yaml diff --git a/config/samples/openstack_v1alpha1_roleassignment.yaml b/config/samples/openstack_v1alpha1_roleassignment.yaml index f1294f7ce..a608fef87 100644 --- a/config/samples/openstack_v1alpha1_roleassignment.yaml +++ b/config/samples/openstack_v1alpha1_roleassignment.yaml @@ -1,14 +1,30 @@ --- +# Example: Assign the 'member' role to a user on a project apiVersion: openstack.k-orc.cloud/v1alpha1 kind: RoleAssignment metadata: - name: roleassignment-sample + name: bob-member spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: devstack-admin # Requires admin credentials secretName: openstack-clouds managementPolicy: managed resource: - description: Sample RoleAssignment - # TODO(scaffolding): Add all fields the resource supports + roleRef: assign-role-test # Reference to Role object + userRef: bob # Reference to User object + projectRef: assign-role-test # Reference to Project object +--- +# Example: Assign the 'admin' role to a group on a domain +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: admins-admin-on-default +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: admin + groupRef: admins + domainRef: default diff --git a/internal/controllers/roleassignment/actuator.go b/internal/controllers/roleassignment/actuator.go index 32fafce63..9a4d4a0b0 100644 --- a/internal/controllers/roleassignment/actuator.go +++ b/internal/controllers/roleassignment/actuator.go @@ -19,6 +19,7 @@ package roleassignment import ( "context" "iter" + "strings" "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles" corev1 "k8s.io/api/core/v1" @@ -29,7 +30,6 @@ import ( orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress" - "github.com/k-orc/openstack-resource-controller/v2/internal/logging" "github.com/k-orc/openstack-resource-controller/v2/internal/osclients" "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors" @@ -41,7 +41,6 @@ type ( createResourceActuator = interfaces.CreateResourceActuator[orcObjectPT, orcObjectT, filterT, osResourceT] deleteResourceActuator = interfaces.DeleteResourceActuator[orcObjectPT, orcObjectT, osResourceT] - resourceReconciler = interfaces.ResourceReconciler[orcObjectPT, osResourceT] helperFactory = interfaces.ResourceHelperFactory[orcObjectPT, orcObjectT, resourceSpecT, filterT, osResourceT] ) @@ -53,16 +52,84 @@ type roleassignmentActuator struct { var _ createResourceActuator = roleassignmentActuator{} var _ deleteResourceActuator = roleassignmentActuator{} +// GetResourceID creates a synthetic ID from the tuple (role, actor, scope). +// OpenStack doesn't assign IDs to role assignments - they're identified by the tuple. +// Format: role::user::project: +// or: role::group::domain: func (roleassignmentActuator) GetResourceID(osResource *osResourceT) string { - return osResource.ID + var parts []string + + // Role + parts = append(parts, "role", osResource.Role.ID) + + // Actor (user or group) + if osResource.User.ID != "" { + parts = append(parts, "user", osResource.User.ID) + } else if osResource.Group.ID != "" { + parts = append(parts, "group", osResource.Group.ID) + } + + // Scope (project or domain) + if osResource.Scope.Project.ID != "" { + parts = append(parts, "project", osResource.Scope.Project.ID) + } else if osResource.Scope.Domain.ID != "" { + parts = append(parts, "domain", osResource.Scope.Domain.ID) + } + + return strings.Join(parts, ":") } +// GetOSResourceByID queries for the role assignment by parsing the synthetic ID back to tuple components. +// The ID format is: role::user::project: (or group/domain variants) func (actuator roleassignmentActuator) GetOSResourceByID(ctx context.Context, id string) (*osResourceT, progress.ReconcileStatus) { - resource, err := actuator.osClient.GetRoleAssignment(ctx, id) - if err != nil { - return nil, progress.WrapError(err) + if id == "" { + return nil, progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, + "cannot retrieve role assignment with empty ID")) + } + + // Parse the synthetic ID back to components + parts := strings.Split(id, ":") + if len(parts) < 4 { + return nil, progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, + "invalid role assignment ID format")) + } + + // Build query options from parsed ID + listOpts := roles.ListAssignmentsOpts{ + Effective: ptr.To(false), } - return resource, nil + + // Parse key:value pairs + for i := 0; i < len(parts)-1; i += 2 { + key := parts[i] + value := parts[i+1] + + switch key { + case "role": + listOpts.RoleID = value + case "user": + listOpts.UserID = value + case "group": + listOpts.GroupID = value + case "project": + listOpts.ScopeProjectID = value + case "domain": + listOpts.ScopeDomainID = value + } + } + + // Query with exact filters - should return exactly one result + for assignment, err := range actuator.osClient.ListRoleAssignments(ctx, listOpts) { + if err != nil { + return nil, progress.WrapError(err) + } + return assignment, nil + } + + // Not found + return nil, nil } func (actuator roleassignmentActuator) ListOSResourcesForAdoption(ctx context.Context, orcObject orcObjectPT) (iter.Seq2[*osResourceT, error], bool) { @@ -71,75 +138,181 @@ func (actuator roleassignmentActuator) ListOSResourcesForAdoption(ctx context.Co return nil, false } - // TODO(scaffolding) If you need to filter resources on fields that the List() function - // of gophercloud does not support, it's possible to perform client-side filtering. - // Check osclients.ResourceFilter + // Fetch all dependencies to build the exact filter + var roleID, userID, groupID, projectID, domainID string + + // Role dependency (required) + role, _ := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, &resourceSpec.RoleRef, "Role", + func(dep *orcv1alpha1.Role) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if role == nil { + return nil, false // Not ready + } + roleID = ptr.Deref(role.Status.ID, "") + + // Actor dependency (user XOR group) + if resourceSpec.UserRef != nil { + user, _ := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.UserRef, "User", + func(dep *orcv1alpha1.User) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if user == nil { + return nil, false // Not ready + } + userID = ptr.Deref(user.Status.ID, "") + } else { + group, _ := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.GroupRef, "Group", + func(dep *orcv1alpha1.Group) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if group == nil { + return nil, false // Not ready + } + groupID = ptr.Deref(group.Status.ID, "") + } + + // Scope dependency (project XOR domain) + if resourceSpec.ProjectRef != nil { + project, _ := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if project == nil { + return nil, false // Not ready + } + projectID = ptr.Deref(project.Status.ID, "") + } else { + domain, _ := dependency.FetchDependency( + ctx, actuator.k8sClient, orcObject.Namespace, resourceSpec.DomainRef, "Domain", + func(dep *orcv1alpha1.Domain) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + if domain == nil { + return nil, false // Not ready + } + domainID = ptr.Deref(domain.Status.ID, "") + } + + // Build query - only set fields that have values + listOpts := roles.ListAssignmentsOpts{ + RoleID: roleID, + Effective: ptr.To(false), // Only list direct assignments, not inherited/group-derived + } - listOpts := roles.ListOpts{ - Name: getResourceName(orcObject), - Description: ptr.Deref(resourceSpec.Description, ""), + // Set actor (user OR group, never both) + if userID != "" { + listOpts.UserID = userID + } else if groupID != "" { + listOpts.GroupID = groupID + } + + // Set scope (project OR domain, never both) + if projectID != "" { + listOpts.ScopeProjectID = projectID + } else if domainID != "" { + listOpts.ScopeDomainID = domainID } return actuator.osClient.ListRoleAssignments(ctx, listOpts), true } func (actuator roleassignmentActuator) ListOSResourcesForImport(ctx context.Context, obj orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) { - // TODO(scaffolding) If you need to filter resources on fields that the List() function - // of gophercloud does not support, it's possible to perform client-side filtering. - // Check osclients.ResourceFilter var reconcileStatus progress.ReconcileStatus - role, rs := dependency.FetchDependency( - ctx, actuator.k8sClient, obj.Namespace, - filter.RoleRef, "Role", - func(dep *orcv1alpha1.Role) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + // Build ListAssignmentsOpts from filter references + var roleID, userID, groupID, projectID, domainID string - user, rs := dependency.FetchDependency( - ctx, actuator.k8sClient, obj.Namespace, - filter.UserRef, "User", - func(dep *orcv1alpha1.User) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + if filter.RoleRef != nil { + role, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.RoleRef, "Role", + func(dep *orcv1alpha1.Role) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + if role != nil && role.Status.ID != nil { + roleID = *role.Status.ID + } + } - group, rs := dependency.FetchDependency( - ctx, actuator.k8sClient, obj.Namespace, - filter.GroupRef, "Group", - func(dep *orcv1alpha1.Group) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + if filter.UserRef != nil { + user, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.UserRef, "User", + func(dep *orcv1alpha1.User) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + if user != nil && user.Status.ID != nil { + userID = *user.Status.ID + } + } - project, rs := dependency.FetchDependency( - ctx, actuator.k8sClient, obj.Namespace, - filter.ProjectRef, "Project", - func(dep *orcv1alpha1.Project) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + if filter.GroupRef != nil { + group, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.GroupRef, "Group", + func(dep *orcv1alpha1.Group) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + if group != nil && group.Status.ID != nil { + groupID = *group.Status.ID + } + } - domain, rs := dependency.FetchDependency( - ctx, actuator.k8sClient, obj.Namespace, - filter.DomainRef, "Domain", - func(dep *orcv1alpha1.Domain) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + if filter.ProjectRef != nil { + project, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.ProjectRef, "Project", + func(dep *orcv1alpha1.Project) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + if project != nil && project.Status.ID != nil { + projectID = *project.Status.ID + } + } + + if filter.DomainRef != nil { + domain, rs := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, filter.DomainRef, "Domain", + func(dep *orcv1alpha1.Domain) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(rs) + if domain != nil && domain.Status.ID != nil { + domainID = *domain.Status.ID + } + } if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus } - listOpts := roles.ListOpts{ - Name: string(ptr.Deref(filter.Name, "")), - Description: string(ptr.Deref(filter.Description, "")), - RoleID: ptr.Deref(role.Status.ID, ""), - UserID: ptr.Deref(user.Status.ID, ""), - GroupID: ptr.Deref(group.Status.ID, ""), - ProjectID: ptr.Deref(project.Status.ID, ""), - DomainID: ptr.Deref(domain.Status.ID, ""), - // TODO(scaffolding): Add more import filters + // Build query - only set fields that have values (filter fields are optional) + listOpts := roles.ListAssignmentsOpts{ + Effective: ptr.To(false), // Only list direct assignments, not inherited/group-derived + } + + if roleID != "" { + listOpts.RoleID = roleID + } + if userID != "" { + listOpts.UserID = userID + } + if groupID != "" { + listOpts.GroupID = groupID + } + if projectID != "" { + listOpts.ScopeProjectID = projectID + } + if domainID != "" { + listOpts.ScopeDomainID = domainID } - return actuator.osClient.ListRoleAssignments(ctx, listOpts), reconcileStatus + return actuator.osClient.ListRoleAssignments(ctx, listOpts), nil } func (actuator roleassignmentActuator) CreateResource(ctx context.Context, obj orcObjectPT) (*osResourceT, progress.ReconcileStatus) { @@ -152,18 +325,20 @@ func (actuator roleassignmentActuator) CreateResource(ctx context.Context, obj o } var reconcileStatus progress.ReconcileStatus + // Fetch role dependency (required) + role, roleDepRS := roleDependency.GetDependency( + ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Role) bool { + return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(roleDepRS) var roleID string - role, roleDepRS := roleDependency.GetDependency( - ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Role) bool { - return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil - }, - ) - reconcileStatus = reconcileStatus.WithReconcileStatus(roleDepRS) - if role != nil { - roleID = ptr.Deref(role.Status.ID, "") - } - - var userID string + if role != nil { + roleID = ptr.Deref(role.Status.ID, "") + } + + // Fetch actor dependency (user XOR group) + var userID, groupID string if resource.UserRef != nil { user, userDepRS := userDependency.GetDependency( ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.User) bool { @@ -174,10 +349,7 @@ func (actuator roleassignmentActuator) CreateResource(ctx context.Context, obj o if user != nil { userID = ptr.Deref(user.Status.ID, "") } - } - - var groupID string - if resource.GroupRef != nil { + } else { group, groupDepRS := groupDependency.GetDependency( ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Group) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil @@ -189,7 +361,8 @@ func (actuator roleassignmentActuator) CreateResource(ctx context.Context, obj o } } - var projectID string + // Fetch scope dependency (project XOR domain) + var projectID, domainID string if resource.ProjectRef != nil { project, projectDepRS := projectDependency.GetDependency( ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Project) bool { @@ -200,10 +373,7 @@ func (actuator roleassignmentActuator) CreateResource(ctx context.Context, obj o if project != nil { projectID = ptr.Deref(project.Status.ID, "") } - } - - var domainID string - if resource.DomainRef != nil { + } else { domain, domainDepRS := domainDependency.GetDependency( ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Domain) bool { return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil @@ -214,108 +384,72 @@ func (actuator roleassignmentActuator) CreateResource(ctx context.Context, obj o domainID = ptr.Deref(domain.Status.ID, "") } } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus } - createOpts := roles.CreateOpts{ - Name: getResourceName(obj), - Description: ptr.Deref(resource.Description, ""), - RoleID: roleID, - UserID: userID, - GroupID: groupID, - ProjectID: projectID, + + // Build AssignOpts + assignOpts := roles.AssignOpts{ + UserID: userID, + GroupID: groupID, + ProjectID: projectID, DomainID: domainID, - // TODO(scaffolding): Add more fields } - osResource, err := actuator.osClient.CreateRoleAssignment(ctx, createOpts) + // Assign the role (idempotent - returns 204 even if already exists) + err := actuator.osClient.AssignRole(ctx, roleID, assignOpts) if err != nil { - // We should require the spec to be updated before retrying a create which returned a conflict if !orcerrors.IsRetryable(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating resource: "+err.Error(), err) + err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration creating role assignment: "+err.Error(), err) } return nil, progress.WrapError(err) } - return osResource, nil -} - -func (actuator roleassignmentActuator) DeleteResource(ctx context.Context, _ orcObjectPT, resource *osResourceT) progress.ReconcileStatus { - return progress.WrapError(actuator.osClient.DeleteRoleAssignment(ctx, resource.ID)) -} - -func (actuator roleassignmentActuator) updateResource(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { - log := ctrl.LoggerFrom(ctx) - resource := obj.Spec.Resource - if resource == nil { - // Should have been caught by API validation - return progress.WrapError( - orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Update requested, but spec.resource is not set")) - } - - updateOpts := roles.UpdateOpts{} - - handleNameUpdate(&updateOpts, obj, osResource) - handleDescriptionUpdate(&updateOpts, resource, osResource) - - // TODO(scaffolding): add handler for all fields supporting mutability - - needsUpdate, err := needsUpdate(updateOpts) - if err != nil { - return progress.WrapError( - orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err)) + // Verify the assignment was created by listing with exact filters + listOpts := roles.ListAssignmentsOpts{ + RoleID: roleID, + Effective: ptr.To(false), // Only list direct assignments, not inherited/group-derived } - if !needsUpdate { - log.V(logging.Debug).Info("No changes") - return nil - } - - _, err = actuator.osClient.UpdateRoleAssignment(ctx, osResource.ID, updateOpts) - // We should require the spec to be updated before retrying an update which returned a conflict - if orcerrors.IsConflict(err) { - err = orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "invalid configuration updating resource: "+err.Error(), err) + // Set actor (user OR group, never both) + if userID != "" { + listOpts.UserID = userID + } else if groupID != "" { + listOpts.GroupID = groupID } - if err != nil { - return progress.WrapError(err) - } - - return progress.NeedsRefresh() -} - -func needsUpdate(updateOpts roles.UpdateOpts) (bool, error) { - updateOptsMap, err := updateOpts.ToRoleAssignmentUpdateMap() - if err != nil { - return false, err + // Set scope (project OR domain, never both) + if projectID != "" { + listOpts.ScopeProjectID = projectID + } else if domainID != "" { + listOpts.ScopeDomainID = domainID } - updateMap, ok := updateOptsMap["role_assignment"].(map[string]any) - if !ok { - updateMap = make(map[string]any) + // Get the first matching assignment to return + for assignment, err := range actuator.osClient.ListRoleAssignments(ctx, listOpts) { + if err != nil { + return nil, progress.WrapError(err) + } + return assignment, nil } - return len(updateMap) > 0, nil + // This shouldn't happen - we just assigned it + return nil, progress.WrapError( + orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, + "role assignment succeeded but could not be found in OpenStack")) } -func handleNameUpdate(updateOpts *roles.UpdateOpts, obj orcObjectPT, osResource *osResourceT) { - name := getResourceName(obj) - if osResource.Name != name { - updateOpts.Name = &name +func (actuator roleassignmentActuator) DeleteResource(ctx context.Context, _ orcObjectPT, osResource *osResourceT) progress.ReconcileStatus { + // Build UnassignOpts from the osResource + unassignOpts := roles.UnassignOpts{ + UserID: osResource.User.ID, + GroupID: osResource.Group.ID, + ProjectID: osResource.Scope.Project.ID, + DomainID: osResource.Scope.Domain.ID, } -} -func handleDescriptionUpdate(updateOpts *roles.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { - description := ptr.Deref(resource.Description, "") - if osResource.Description != description { - updateOpts.Description = &description - } -} - -func (actuator roleassignmentActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { - return []resourceReconciler{ - actuator.updateResource, - }, nil + return progress.WrapError(actuator.osClient.UnassignRole(ctx, osResource.Role.ID, unassignOpts)) } type roleassignmentHelperFactory struct{} diff --git a/internal/controllers/roleassignment/actuator_test.go b/internal/controllers/roleassignment/actuator_test.go index 0433ecf35..c37d536c8 100644 --- a/internal/controllers/roleassignment/actuator_test.go +++ b/internal/controllers/roleassignment/actuator_test.go @@ -16,104 +16,5 @@ limitations under the License. package roleassignment -import ( - "testing" - - "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles" - orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" - "k8s.io/utils/ptr" -) - -func TestNeedsUpdate(t *testing.T) { - testCases := []struct { - name string - updateOpts roles.UpdateOpts - expectChange bool - }{ - { - name: "Empty base opts", - updateOpts: roles.UpdateOpts{}, - expectChange: false, - }, - { - name: "Updated opts", - updateOpts: roles.UpdateOpts{Name: ptr.To("updated")}, - expectChange: true, - }, - } - - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - got, _ := needsUpdate(tt.updateOpts) - if got != tt.expectChange { - t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) - } - }) - } -} - -func TestHandleNameUpdate(t *testing.T) { - ptrToName := ptr.To[orcv1alpha1.OpenStackName] - testCases := []struct { - name string - newValue *orcv1alpha1.OpenStackName - existingValue string - expectChange bool - }{ - {name: "Identical", newValue: ptrToName("name"), existingValue: "name", expectChange: false}, - {name: "Different", newValue: ptrToName("new-name"), existingValue: "name", expectChange: true}, - {name: "No value provided, existing is identical to object name", newValue: nil, existingValue: "object-name", expectChange: false}, - {name: "No value provided, existing is different from object name", newValue: nil, existingValue: "different-from-object-name", expectChange: true}, - } - - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - resource := &orcv1alpha1.RoleAssignment{} - resource.Name = "object-name" - resource.Spec = orcv1alpha1.RoleAssignmentSpec{ - Resource: &orcv1alpha1.RoleAssignmentResourceSpec{Name: tt.newValue}, - } - osResource := &osResourceT{Name: tt.existingValue} - - updateOpts := roles.UpdateOpts{} - handleNameUpdate(&updateOpts, resource, osResource) - - got, _ := needsUpdate(updateOpts) - if got != tt.expectChange { - t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) - } - }) - - } -} - -func TestHandleDescriptionUpdate(t *testing.T) { - ptrToDescription := ptr.To[string] - testCases := []struct { - name string - newValue *string - existingValue string - expectChange bool - }{ - {name: "Identical", newValue: ptrToDescription("desc"), existingValue: "desc", expectChange: false}, - {name: "Different", newValue: ptrToDescription("new-desc"), existingValue: "desc", expectChange: true}, - {name: "No value provided, existing is set", newValue: nil, existingValue: "desc", expectChange: true}, - {name: "No value provided, existing is empty", newValue: nil, existingValue: "", expectChange: false}, - } - - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - resource := &orcv1alpha1.RoleAssignmentResourceSpec{Description: tt.newValue} - osResource := &osResourceT{Description: tt.existingValue} - - updateOpts := roles.UpdateOpts{} - handleDescriptionUpdate(&updateOpts, resource, osResource) - - got, _ := needsUpdate(updateOpts) - if got != tt.expectChange { - t.Errorf("Expected change: %v, got: %v", tt.expectChange, got) - } - }) - - } -} +// NOTE: RoleAssignment is fully immutable, so there are no update functions to test. +// Tests for creation and deletion logic should be added as KUTTL E2E tests in the tests/ directory. diff --git a/internal/controllers/roleassignment/status.go b/internal/controllers/roleassignment/status.go index a3a720369..c7587b320 100644 --- a/internal/controllers/roleassignment/status.go +++ b/internal/controllers/roleassignment/status.go @@ -37,31 +37,47 @@ func (roleassignmentStatusWriter) GetApplyConfig(name, namespace string) *object return orcapplyconfigv1alpha1.RoleAssignment(name, namespace) } +// ResourceAvailableStatus returns the availability status of the role assignment. +// Role assignments don't have Status.ID, so we just check if osResource exists. func (roleassignmentStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.RoleAssignment, osResource *osResourceT) (metav1.ConditionStatus, progress.ReconcileStatus) { if osResource == nil { - if orcObject.Status.ID == nil { - return metav1.ConditionFalse, nil - } else { + // Check if we have any status IDs set (indicates we may have created it but can't find it) + if orcObject.Status.Resource != nil && + (orcObject.Status.Resource.RoleID != "" || + orcObject.Status.Resource.UserID != "" || + orcObject.Status.Resource.GroupID != "" || + orcObject.Status.Resource.ProjectID != "" || + orcObject.Status.Resource.DomainID != "") { return metav1.ConditionUnknown, nil } + return metav1.ConditionFalse, nil } return metav1.ConditionTrue, nil } +// ApplyResourceStatus extracts the role assignment details and applies them to status. func (roleassignmentStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResourceT, statusApply *statusApplyT) { - resourceStatus := orcapplyconfigv1alpha1.RoleAssignmentResourceStatus(). - WithRoleID(osResource.RoleID). - WithUserID(osResource.UserID). - WithGroupID(osResource.GroupID). - WithProjectID(osResource.ProjectID). - WithDomainID(osResource.DomainID). - WithName(osResource.Name) - - // TODO(scaffolding): add all of the fields supported in the RoleAssignmentResourceStatus struct - // If a zero-value isn't expected in the response, place it behind a conditional - - if osResource.Description != "" { - resourceStatus.WithDescription(osResource.Description) + resourceStatus := orcapplyconfigv1alpha1.RoleAssignmentResourceStatus() + + // Extract role ID + if osResource.Role.ID != "" { + resourceStatus.WithRoleID(osResource.Role.ID) + } + + // Extract actor ID (user XOR group) + if osResource.User.ID != "" { + resourceStatus.WithUserID(osResource.User.ID) + } + if osResource.Group.ID != "" { + resourceStatus.WithGroupID(osResource.Group.ID) + } + + // Extract scope ID (project XOR domain) + if osResource.Scope.Project.ID != "" { + resourceStatus.WithProjectID(osResource.Scope.Project.ID) + } + if osResource.Scope.Domain.ID != "" { + resourceStatus.WithDomainID(osResource.Scope.Domain.ID) } statusApply.WithResource(resourceStatus) diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-full/00-assert.yaml deleted file mode 100644 index 7da99c8d0..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-assert.yaml +++ /dev/null @@ -1,53 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-create-full -status: - resource: - name: roleassignment-create-full-override - description: RoleAssignment from "create full" test - # TODO(scaffolding): Add all fields the resource supports - conditions: - - type: Available - status: "True" - reason: Success - - type: Progressing - status: "False" - reason: Success ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-create-full - ref: roleassignment - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Role - name: roleassignment-create-full - ref: role - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: User - name: roleassignment-create-full - ref: user - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Group - name: roleassignment-create-full - ref: group - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Project - name: roleassignment-create-full - ref: project - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Domain - name: roleassignment-create-full - ref: domain -assertAll: - - celExpr: "roleassignment.status.id != ''" - - celExpr: "roleassignment.status.resource.roleID == role.status.id" - - celExpr: "roleassignment.status.resource.userID == user.status.id" - - celExpr: "roleassignment.status.resource.groupID == group.status.id" - - celExpr: "roleassignment.status.resource.projectID == project.status.id" - - celExpr: "roleassignment.status.resource.domainID == domain.status.id" - # TODO(scaffolding): Add more checks diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-full/00-create-resource.yaml deleted file mode 100644 index ce3174c84..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-create-resource.yaml +++ /dev/null @@ -1,85 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-create-full -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: User -metadata: - name: roleassignment-create-full -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Group -metadata: - name: roleassignment-create-full -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Project -metadata: - name: roleassignment-create-full -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Domain -metadata: - name: roleassignment-create-full -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-create-full -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - name: roleassignment-create-full-override - description: RoleAssignment from "create full" test - roleRef: roleassignment-create-full - userRef: roleassignment-create-full - groupRef: roleassignment-create-full - projectRef: roleassignment-create-full - domainRef: roleassignment-create-full - # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-full/00-secret.yaml deleted file mode 100644 index 045711ee7..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-create-full/00-secret.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} - namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-full/README.md b/internal/controllers/roleassignment/tests/roleassignment-create-full/README.md deleted file mode 100644 index 74756b176..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-create-full/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Create a RoleAssignment with all the options - -## Step 00 - -Create a RoleAssignment using all available fields, and verify that the observed state corresponds to the spec. - -Also validate that the OpenStack resource uses the name from the spec when it is specified. - -## Reference - -https://k-orc.cloud/development/writing-tests/#create-full diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-assert.yaml index 4106e13f4..65c71f85d 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-assert.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-assert.yaml @@ -1,12 +1,52 @@ --- +# Assert Role is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-test-role +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Assert User is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-test-user +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Assert Project is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-test-project +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Assert RoleAssignment is available apiVersion: openstack.k-orc.cloud/v1alpha1 kind: RoleAssignment metadata: name: roleassignment-create-minimal status: - resource: - name: roleassignment-create-minimal - # TODO(scaffolding): Add all fields the resource supports conditions: - type: Available status: "True" @@ -15,6 +55,7 @@ status: status: "False" reason: Success --- +# Validate RoleAssignment status fields apiVersion: kuttl.dev/v1beta1 kind: TestAssert resourceRefs: @@ -22,11 +63,14 @@ resourceRefs: kind: RoleAssignment name: roleassignment-create-minimal ref: roleassignment - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Role - name: roleassignment-create-minimal - ref: role assertAll: + # Verify synthetic ID is set (format: role::user::project:) - celExpr: "roleassignment.status.id != ''" - - celExpr: "roleassignment.status.resource.roleID == role.status.id" - # TODO(scaffolding): Add more checks + - celExpr: "roleassignment.status.id.startsWith('role:')" + # Verify all component IDs are populated + - celExpr: "roleassignment.status.resource.roleID != ''" + - celExpr: "roleassignment.status.resource.userID != ''" + - celExpr: "roleassignment.status.resource.projectID != ''" + # Verify group and domain are not set (since we used user and project) + - celExpr: "!has(roleassignment.status.resource.groupID) || roleassignment.status.resource.groupID == ''" + - celExpr: "!has(roleassignment.status.resource.domainID) || roleassignment.status.resource.domainID == ''" diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-create-resource.yaml index 28bf9a7aa..4032195dd 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-create-resource.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-create-resource.yaml @@ -1,28 +1,54 @@ --- +# Create a test role apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Role metadata: - name: roleassignment-create-minimal + name: roleassignment-test-role +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-test-role +--- +# Create a test user +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-test-user spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + name: roleassignment-test-user +--- +# Create a test project +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-test-project +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-test-project --- +# Create the role assignment apiVersion: openstack.k-orc.cloud/v1alpha1 kind: RoleAssignment metadata: name: roleassignment-create-minimal spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Only add the mandatory fields. It's possible the resource - # doesn't have mandatory fields, in that case, leave it empty. resource: - roleRef: roleassignment-create-minimal + roleRef: roleassignment-test-role + userRef: roleassignment-test-user + projectRef: roleassignment-test-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-secret.yaml index 045711ee7..99d9cec3d 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-secret.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/00-secret.yaml @@ -1,6 +1,19 @@ --- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} - namespaced: true +apiVersion: v1 +kind: Secret +metadata: + name: openstack-clouds +type: Opaque +stringData: + clouds.yaml: | + clouds: + openstack-admin: + auth: + auth_url: ${E2E_OS_AUTH_URL} + project_name: ${E2E_OS_PROJECT_NAME} + username: ${E2E_OS_USERNAME} + password: ${E2E_OS_PASSWORD} + domain_name: ${E2E_OS_DOMAIN_NAME} + user_domain_name: ${E2E_OS_USER_DOMAIN_NAME} + project_domain_name: ${E2E_OS_PROJECT_DOMAIN_NAME} + region_name: ${E2E_OS_REGION_NAME} diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-assert.yaml index c4d367d4a..c034cffb2 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-assert.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-assert.yaml @@ -1,11 +1,35 @@ --- apiVersion: kuttl.dev/v1beta1 kind: TestAssert -resourceRefs: - - apiVersion: v1 - kind: Secret - name: openstack-clouds - ref: secret -assertAll: - - celExpr: "secret.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/roleassignment' in secret.metadata.finalizers" +commands: +# Verify RoleAssignment is deleted +- script: "! kubectl get roleassignment roleassignment-create-minimal --namespace $NAMESPACE" + skipLogOutput: true +--- +# Verify dependencies still exist (deletion guard should keep them) +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-test-role +status: + conditions: + - type: Available + status: "True" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: roleassignment-test-user +status: + conditions: + - type: Available + status: "True" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-test-project +status: + conditions: + - type: Available + status: "True" diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-roleassignment.yaml similarity index 75% rename from internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml rename to internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-roleassignment.yaml index 65a259c22..1f02da2fc 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-delete-resource.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-roleassignment.yaml @@ -4,4 +4,4 @@ kind: TestStep delete: - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: RoleAssignment - name: roleassignment-import-dependency + name: roleassignment-create-minimal diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-secret.yaml deleted file mode 100644 index 1620791b9..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/01-delete-secret.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - # We expect the deletion to hang due to the finalizer, so use --wait=false - - command: kubectl delete secret openstack-clouds --wait=false - namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/README.md b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/README.md index 08f0e40c6..3b92d0da2 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-create-minimal/README.md +++ b/internal/controllers/roleassignment/tests/roleassignment-create-minimal/README.md @@ -1,14 +1,14 @@ -# Create a RoleAssignment with the minimum options +# Create a RoleAssignment with minimum options ## Step 00 -Create a minimal RoleAssignment, that sets only the required fields, and verify that the observed state corresponds to the spec. +Create dependencies (Role, User, Project) and a minimal RoleAssignment that assigns a role to a user on a project. -Also validate that the OpenStack resource uses the name of the ORC object when no name is explicitly specified. +Verify that the observed state corresponds to the spec and the role assignment exists in OpenStack. ## Step 01 -Try deleting the secret and ensure that it is not deleted thanks to the finalizer. +Delete the RoleAssignment and verify it's removed from OpenStack. ## Reference diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-assert.yaml index ce8bc6357..ea04127cd 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-assert.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-assert.yaml @@ -1,90 +1,13 @@ --- +# Verify RoleAssignment is Progressing (waiting for dependencies) apiVersion: openstack.k-orc.cloud/v1alpha1 kind: RoleAssignment metadata: - name: roleassignment-dependency-no-secret + name: roleassignment-dependency status: conditions: - - type: Available - message: Waiting for Secret/roleassignment-dependency to be created - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for Secret/roleassignment-dependency to be created - status: "True" - reason: Progressing ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-role -status: - conditions: - - type: Available - message: Waiting for Role/roleassignment-dependency-pending to be created - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for Role/roleassignment-dependency-pending to be created - status: "True" - reason: Progressing ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-user -status: - conditions: - - type: Available - message: Waiting for User/roleassignment-dependency to be created - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for User/roleassignment-dependency to be created - status: "True" - reason: Progressing ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-group -status: - conditions: - - type: Available - message: Waiting for Group/roleassignment-dependency to be created - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for Group/roleassignment-dependency to be created - status: "True" - reason: Progressing ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-project -status: - conditions: - - type: Available - message: Waiting for Project/roleassignment-dependency to be created - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for Project/roleassignment-dependency to be created - status: "True" - reason: Progressing ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-domain -status: - conditions: - - type: Available - message: Waiting for Domain/roleassignment-dependency to be created - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for Domain/roleassignment-dependency to be created - status: "True" - reason: Progressing + - type: Available + status: "False" + - type: Progressing + status: "True" + reason: WaitingForDependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-create-resources-missing-deps.yaml index 9666ee6c6..412216039 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-create-resources-missing-deps.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-create-resources-missing-deps.yaml @@ -1,98 +1,15 @@ --- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-dependency -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-role -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - roleRef: roleassignment-dependency-pending - # TODO(scaffolding): Add the necessary fields to create the resource ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-user -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - roleRef: roleassignment-dependency - userRef: roleassignment-dependency - # TODO(scaffolding): Add the necessary fields to create the resource--- +# Create RoleAssignment with missing dependencies apiVersion: openstack.k-orc.cloud/v1alpha1 kind: RoleAssignment metadata: - name: roleassignment-dependency-no-group -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - roleRef: roleassignment-dependency - groupRef: roleassignment-dependency - # TODO(scaffolding): Add the necessary fields to create the resource--- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-project -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - roleRef: roleassignment-dependency - projectRef: roleassignment-dependency - # TODO(scaffolding): Add the necessary fields to create the resource--- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-domain + name: roleassignment-dependency spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed resource: - roleRef: roleassignment-dependency - domainRef: roleassignment-dependency - # TODO(scaffolding): Add the necessary fields to create the resource ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-secret -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: roleassignment-dependency - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: - roleRef: roleassignment-dependency + roleRef: roleassignment-dep-role + userRef: roleassignment-dep-user + projectRef: roleassignment-dep-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-secret.yaml index 045711ee7..99d9cec3d 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/00-secret.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/00-secret.yaml @@ -1,6 +1,19 @@ --- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} - namespaced: true +apiVersion: v1 +kind: Secret +metadata: + name: openstack-clouds +type: Opaque +stringData: + clouds.yaml: | + clouds: + openstack-admin: + auth: + auth_url: ${E2E_OS_AUTH_URL} + project_name: ${E2E_OS_PROJECT_NAME} + username: ${E2E_OS_USERNAME} + password: ${E2E_OS_PASSWORD} + domain_name: ${E2E_OS_DOMAIN_NAME} + user_domain_name: ${E2E_OS_USER_DOMAIN_NAME} + project_domain_name: ${E2E_OS_PROJECT_DOMAIN_NAME} + region_name: ${E2E_OS_REGION_NAME} diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/01-assert.yaml index 2fd8a79b1..7521a48ca 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/01-assert.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/01-assert.yaml @@ -1,90 +1,64 @@ --- +# Verify dependencies are Available apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-secret -status: - conditions: - - type: Available - message: OpenStack resource is available - status: "True" - reason: Success - - type: Progressing - message: OpenStack resource is up to date - status: "False" - reason: Success ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment +kind: Role metadata: - name: roleassignment-dependency-no-role + name: roleassignment-dep-role status: conditions: - - type: Available - message: OpenStack resource is available - status: "True" - reason: Success - - type: Progressing - message: OpenStack resource is up to date - status: "False" - reason: Success + - type: Available + status: "True" --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment +kind: User metadata: - name: roleassignment-dependency-no-user + name: roleassignment-dep-user status: conditions: - - type: Available - message: OpenStack resource is available - status: "True" - reason: Success - - type: Progressing - message: OpenStack resource is up to date - status: "False" - reason: Success + - type: Available + status: "True" --- apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment +kind: Project metadata: - name: roleassignment-dependency-no-group + name: roleassignment-dep-project status: conditions: - - type: Available - message: OpenStack resource is available - status: "True" - reason: Success - - type: Progressing - message: OpenStack resource is up to date - status: "False" - reason: Success + - type: Available + status: "True" --- +# Verify RoleAssignment is now Available apiVersion: openstack.k-orc.cloud/v1alpha1 kind: RoleAssignment metadata: - name: roleassignment-dependency-no-project + name: roleassignment-dependency status: conditions: - - type: Available - message: OpenStack resource is available - status: "True" - reason: Success - - type: Progressing - message: OpenStack resource is up to date - status: "False" - reason: Success + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success --- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-dependency-no-domain -status: - conditions: - - type: Available - message: OpenStack resource is available - status: "True" - reason: Success - - type: Progressing - message: OpenStack resource is up to date - status: "False" - reason: Success +# Verify deletion guard finalizers are set on dependencies +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Role + name: roleassignment-dep-role + ref: role + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: roleassignment-dep-user + ref: user + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: roleassignment-dep-project + ref: project +assertAll: + # Check that deletion guard finalizers are present + - celExpr: "role.metadata.finalizers.exists(f, f.startsWith('openstack.k-orc.cloud/roleassignment'))" + - celExpr: "user.metadata.finalizers.exists(f, f.startsWith('openstack.k-orc.cloud/roleassignment'))" + - celExpr: "project.metadata.finalizers.exists(f, f.startsWith('openstack.k-orc.cloud/roleassignment'))" diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/01-create-dependencies.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/01-create-dependencies.yaml index 5e18664c5..a113632a8 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/01-create-dependencies.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/01-create-dependencies.yaml @@ -1,71 +1,37 @@ --- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - command: kubectl create secret generic roleassignment-dependency --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} - namespaced: true ---- +# Create the dependencies apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Role metadata: - name: roleassignment-dependency-pending + name: roleassignment-dep-role spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + name: roleassignment-dep-role --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: User metadata: - name: roleassignment-dependency -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Group -metadata: - name: roleassignment-dependency + name: roleassignment-dep-user spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + name: roleassignment-dep-user --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Project metadata: - name: roleassignment-dependency -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Domain -metadata: - name: roleassignment-dependency + name: roleassignment-dep-project spec: cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack + cloudName: openstack-admin secretName: openstack-clouds managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} + resource: + name: roleassignment-dep-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml index d91675d9d..806667ee9 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-assert.yaml @@ -1,41 +1,21 @@ --- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Role - name: roleassignment-dependency - ref: role - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: User - name: roleassignment-dependency - ref: user - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Group - name: roleassignment-dependency - ref: group - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Project - name: roleassignment-dependency - ref: project - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Domain - name: roleassignment-dependency - ref: domain - - apiVersion: v1 - kind: Secret - name: roleassignment-dependency - ref: secret -assertAll: - - celExpr: "role.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/roleassignment' in role.metadata.finalizers" - - celExpr: "user.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/roleassignment' in user.metadata.finalizers" - - celExpr: "group.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/roleassignment' in group.metadata.finalizers" - - celExpr: "project.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/roleassignment' in project.metadata.finalizers" - - celExpr: "domain.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/roleassignment' in domain.metadata.finalizers" - - celExpr: "secret.metadata.deletionTimestamp != 0" - - celExpr: "'openstack.k-orc.cloud/roleassignment' in secret.metadata.finalizers" +# Verify Project still exists (deletion blocked by finalizer) +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: roleassignment-dep-project + # deletionTimestamp should be set, but resource should still exist +status: + conditions: + - type: Available + status: "True" +--- +# Verify RoleAssignment still Available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-dependency +status: + conditions: + - type: Available + status: "True" diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/02-delete-dependencies.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-delete-dependencies.yaml index 7d2647c44..612feaecf 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/02-delete-dependencies.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/02-delete-dependencies.yaml @@ -1,17 +1,8 @@ --- +# Try to delete a dependency (should be blocked by finalizer) apiVersion: kuttl.dev/v1beta1 kind: TestStep -commands: - # We expect the deletion to hang due to the finalizer, so use --wait=false - - command: kubectl delete role.openstack.k-orc.cloud roleassignment-dependency --wait=false - namespaced: true - - command: kubectl delete user.openstack.k-orc.cloud roleassignment-dependency --wait=false - namespaced: true - - command: kubectl delete group.openstack.k-orc.cloud roleassignment-dependency --wait=false - namespaced: true - - command: kubectl delete project.openstack.k-orc.cloud roleassignment-dependency --wait=false - namespaced: true - - command: kubectl delete domain.openstack.k-orc.cloud roleassignment-dependency --wait=false - namespaced: true - - command: kubectl delete secret roleassignment-dependency --wait=false - namespaced: true +delete: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Project + name: roleassignment-dep-project diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/03-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/03-assert.yaml index c9d8176db..1a99c8e08 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/03-assert.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/03-assert.yaml @@ -2,16 +2,9 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert commands: -# Dependencies that were prevented deletion before should now be gone -- script: "! kubectl get role.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" +# Verify RoleAssignment is deleted +- script: "! kubectl get roleassignment roleassignment-dependency --namespace $NAMESPACE" skipLogOutput: true -- script: "! kubectl get user.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get group.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get project.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get domain.openstack.k-orc.cloud roleassignment-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get secret roleassignment-dependency --namespace $NAMESPACE" +# Verify Project can now be deleted (finalizer removed) +- script: "! kubectl get project roleassignment-dep-project --namespace $NAMESPACE" skipLogOutput: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/03-delete-resources.yaml b/internal/controllers/roleassignment/tests/roleassignment-dependency/03-delete-resources.yaml index bd5ab1aec..cc50ec3db 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/03-delete-resources.yaml +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/03-delete-resources.yaml @@ -1,22 +1,8 @@ --- +# Delete RoleAssignment first apiVersion: kuttl.dev/v1beta1 kind: TestStep delete: -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-dependency-no-secret -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-dependency-no-role -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-dependency-no-user -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-dependency-no-group -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-dependency-no-project -- apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-dependency-no-domain + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-dependency/README.md b/internal/controllers/roleassignment/tests/roleassignment-dependency/README.md index 2682e773e..49d537638 100644 --- a/internal/controllers/roleassignment/tests/roleassignment-dependency/README.md +++ b/internal/controllers/roleassignment/tests/roleassignment-dependency/README.md @@ -1,21 +1,23 @@ -# Creation and deletion dependencies +# Test RoleAssignment dependency handling ## Step 00 -Create RoleAssignments referencing non-existing resources. Each RoleAssignment is dependent on other non-existing resource. Verify that the RoleAssignments are waiting for the needed resources to be created externally. +Create a RoleAssignment that references Role, User, and Project that don't exist yet. +Verify that it enters Progressing state waiting for dependencies. ## Step 01 -Create the missing dependencies and verify all the RoleAssignments are available. +Create the dependencies and verify the RoleAssignment becomes Available. ## Step 02 -Delete all the dependencies and check that ORC prevents deletion since there is still a resource that depends on them. +Try to delete a dependency (Project) while it's still referenced by the RoleAssignment. +Verify the deletion is blocked by the finalizer. ## Step 03 -Delete the RoleAssignments and validate that all resources are gone. +Delete the RoleAssignment first, then verify dependencies can be deleted. ## Reference -https://k-orc.cloud/development/writing-tests/#dependency +https://k-orc.cloud/development/writing-tests/#dependencies diff --git a/internal/controllers/roleassignment/tests/roleassignment-group-domain/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-group-domain/00-assert.yaml new file mode 100644 index 000000000..b25ed3e6b --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-group-domain/00-assert.yaml @@ -0,0 +1,35 @@ +--- +# Assert RoleAssignment is available +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-group-domain +status: + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success +--- +# Validate status has group and domain IDs (not user and project) +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: RoleAssignment + name: roleassignment-group-domain + ref: roleassignment +assertAll: + # Verify synthetic ID format for group+domain + - celExpr: "roleassignment.status.id != ''" + - celExpr: "roleassignment.status.id.contains(':group:')" + - celExpr: "roleassignment.status.id.contains(':domain:')" + # Verify component IDs + - celExpr: "roleassignment.status.resource.roleID != ''" + - celExpr: "roleassignment.status.resource.groupID != ''" + - celExpr: "roleassignment.status.resource.domainID != ''" + # Verify user and project are NOT set + - celExpr: "!has(roleassignment.status.resource.userID) || roleassignment.status.resource.userID == ''" + - celExpr: "!has(roleassignment.status.resource.projectID) || roleassignment.status.resource.projectID == ''" diff --git a/internal/controllers/roleassignment/tests/roleassignment-group-domain/00-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-group-domain/00-create-resource.yaml new file mode 100644 index 000000000..c8261b43f --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-group-domain/00-create-resource.yaml @@ -0,0 +1,54 @@ +--- +# Create a test role +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Role +metadata: + name: roleassignment-gd-role +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-gd-role +--- +# Create a test group +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Group +metadata: + name: roleassignment-gd-group +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-gd-group +--- +# Create a test domain +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: roleassignment-gd-domain +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + name: roleassignment-gd-domain +--- +# Create role assignment (group on domain) +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: RoleAssignment +metadata: + name: roleassignment-group-domain +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + roleRef: roleassignment-gd-role + groupRef: roleassignment-gd-group + domainRef: roleassignment-gd-domain diff --git a/internal/controllers/roleassignment/tests/roleassignment-group-domain/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-group-domain/00-secret.yaml new file mode 100644 index 000000000..99d9cec3d --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-group-domain/00-secret.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: openstack-clouds +type: Opaque +stringData: + clouds.yaml: | + clouds: + openstack-admin: + auth: + auth_url: ${E2E_OS_AUTH_URL} + project_name: ${E2E_OS_PROJECT_NAME} + username: ${E2E_OS_USERNAME} + password: ${E2E_OS_PASSWORD} + domain_name: ${E2E_OS_DOMAIN_NAME} + user_domain_name: ${E2E_OS_USER_DOMAIN_NAME} + project_domain_name: ${E2E_OS_PROJECT_DOMAIN_NAME} + region_name: ${E2E_OS_REGION_NAME} diff --git a/internal/controllers/roleassignment/tests/roleassignment-group-domain/README.md b/internal/controllers/roleassignment/tests/roleassignment-group-domain/README.md new file mode 100644 index 000000000..0174b48c5 --- /dev/null +++ b/internal/controllers/roleassignment/tests/roleassignment-group-domain/README.md @@ -0,0 +1,11 @@ +# Test RoleAssignment with Group and Domain + +## Step 00 + +Create a RoleAssignment that assigns a role to a group on a domain (instead of user on project). + +Verify the XOR validation works and the assignment is created correctly. + +## Reference + +https://k-orc.cloud/development/writing-tests/ diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml deleted file mode 100644 index d67606898..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-assert.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-dependency -status: - conditions: - - type: Available - message: |- - Waiting for Role/roleassignment-import-dependency to be ready - Waiting for User/roleassignment-import-dependency to be ready - Waiting for Group/roleassignment-import-dependency to be ready - Waiting for Project/roleassignment-import-dependency to be ready - Waiting for Domain/roleassignment-import-dependency to be ready - status: "False" - reason: Progressing - - type: Progressing - message: |- - Waiting for Role/roleassignment-import-dependency to be ready - Waiting for User/roleassignment-import-dependency to be ready - Waiting for Group/roleassignment-import-dependency to be ready - Waiting for Project/roleassignment-import-dependency to be ready - Waiting for Domain/roleassignment-import-dependency to be ready - status: "True" - reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml deleted file mode 100644 index cbde79d20..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-import-resource.yaml +++ /dev/null @@ -1,82 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-import-dependency -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - name: roleassignment-import-dependency-external ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: User -metadata: - name: roleassignment-import-dependency -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - name: roleassignment-import-dependency-external ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Group -metadata: - name: roleassignment-import-dependency -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - name: roleassignment-import-dependency-external ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Project -metadata: - name: roleassignment-import-dependency -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - name: roleassignment-import-dependency-external ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Domain -metadata: - name: roleassignment-import-dependency -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - name: roleassignment-import-dependency-external ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-dependency -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - roleRef: roleassignment-import-dependency - userRef: roleassignment-import-dependency - groupRef: roleassignment-import-dependency - projectRef: roleassignment-import-dependency - domainRef: roleassignment-import-dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml deleted file mode 100644 index 045711ee7..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/00-secret.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} - namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml deleted file mode 100644 index bf4fc3938..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-assert.yaml +++ /dev/null @@ -1,40 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-dependency-not-this-one -status: - conditions: - - type: Available - message: OpenStack resource is available - status: "True" - reason: Success - - type: Progressing - message: OpenStack resource is up to date - status: "False" - reason: Success ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-dependency -status: - conditions: - - type: Available - message: |- - Waiting for Role/roleassignment-import-dependency to be ready - Waiting for User/roleassignment-import-dependency to be ready - Waiting for Group/roleassignment-import-dependency to be ready - Waiting for Project/roleassignment-import-dependency to be ready - Waiting for Domain/roleassignment-import-dependency to be ready - status: "False" - reason: Progressing - - type: Progressing - message: |- - Waiting for Role/roleassignment-import-dependency to be ready - Waiting for User/roleassignment-import-dependency to be ready - Waiting for Group/roleassignment-import-dependency to be ready - Waiting for Project/roleassignment-import-dependency to be ready - Waiting for Domain/roleassignment-import-dependency to be ready - status: "True" - reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml deleted file mode 100644 index d7edf5088..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/01-create-trap-resource.yaml +++ /dev/null @@ -1,84 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-import-dependency-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: User -metadata: - name: roleassignment-import-dependency-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Group -metadata: - name: roleassignment-import-dependency-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Project -metadata: - name: roleassignment-import-dependency-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Domain -metadata: - name: roleassignment-import-dependency-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -# This `roleassignment-import-dependency-not-this-one` should not be picked by the import filter -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-dependency-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - roleRef: roleassignment-import-dependency-not-this-one - userRef: roleassignment-import-dependency-not-this-one - groupRef: roleassignment-import-dependency-not-this-one - projectRef: roleassignment-import-dependency-not-this-one - domainRef: roleassignment-import-dependency-not-this-one - # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml deleted file mode 100644 index 3c81e999a..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-assert.yaml +++ /dev/null @@ -1,54 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-import-dependency - ref: roleassignment1 - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-import-dependency-not-this-one - ref: roleassignment2 - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Role - name: roleassignment-import-dependency - ref: role - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: User - name: roleassignment-import-dependency - ref: user - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Group - name: roleassignment-import-dependency - ref: group - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Project - name: roleassignment-import-dependency - ref: project - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: Domain - name: roleassignment-import-dependency - ref: domain -assertAll: - - celExpr: "roleassignment1.status.id != roleassignment2.status.id" - - celExpr: "roleassignment1.status.resource.roleID == role.status.id" - - celExpr: "roleassignment1.status.resource.userID == user.status.id" - - celExpr: "roleassignment1.status.resource.groupID == group.status.id" - - celExpr: "roleassignment1.status.resource.projectID == project.status.id" - - celExpr: "roleassignment1.status.resource.domainID == domain.status.id" ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-dependency -status: - conditions: - - type: Available - message: OpenStack resource is available - status: "True" - reason: Success - - type: Progressing - message: OpenStack resource is up to date - status: "False" - reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml deleted file mode 100644 index aa6a4b273..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/02-create-resource.yaml +++ /dev/null @@ -1,83 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-import-dependency-external -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: User -metadata: - name: roleassignment-import-dependency-external -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Group -metadata: - name: roleassignment-import-dependency-external -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Project -metadata: - name: roleassignment-import-dependency-external -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Domain -metadata: - name: roleassignment-import-dependency-external -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-dependency-external -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - roleRef: roleassignment-import-dependency-external - userRef: roleassignment-import-dependency-external - groupRef: roleassignment-import-dependency-external - projectRef: roleassignment-import-dependency-external - domainRef: roleassignment-import-dependency-external - # TODO(scaffolding): Add the necessary fields to create the resource diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml deleted file mode 100644 index 5ae60674d..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-assert.yaml +++ /dev/null @@ -1,14 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -commands: -- script: "! kubectl get role.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get user.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get group.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get project.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" - skipLogOutput: true -- script: "! kubectl get domain.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" - skipLogOutput: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml deleted file mode 100644 index 6779311b6..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/03-delete-import-dependencies.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - # We should be able to delete the import dependencies - - command: kubectl delete role.openstack.k-orc.cloud roleassignment-import-dependency - namespaced: true - - command: kubectl delete user.openstack.k-orc.cloud roleassignment-import-dependency - namespaced: true - - command: kubectl delete group.openstack.k-orc.cloud roleassignment-import-dependency - namespaced: true - - command: kubectl delete project.openstack.k-orc.cloud roleassignment-import-dependency - namespaced: true - - command: kubectl delete domain.openstack.k-orc.cloud roleassignment-import-dependency - namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml deleted file mode 100644 index 1b9333e8c..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/04-assert.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -commands: -- script: "! kubectl get roleassignment.openstack.k-orc.cloud roleassignment-import-dependency --namespace $NAMESPACE" - skipLogOutput: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md b/internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md deleted file mode 100644 index e42814f39..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-dependency/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Check dependency handling for imported RoleAssignment - -## Step 00 - -Import a RoleAssignment that references other imported resources. The referenced imported resources have no matching resources yet. -Verify the RoleAssignment is waiting for the dependency to be ready. - -## Step 01 - -Create a RoleAssignment matching the import filter, except for referenced resources, and verify that it's not being imported. - -## Step 02 - -Create the referenced resources and a RoleAssignment matching the import filters. - -Verify that the observed status on the imported RoleAssignment corresponds to the spec of the created RoleAssignment. - -## Step 03 - -Delete the referenced resources and check that ORC does not prevent deletion. The OpenStack resources still exist because they -were imported resources and we only deleted the ORC representation of it. - -## Step 04 - -Delete the RoleAssignment and validate that all resources are gone. - -## Reference - -https://k-orc.cloud/development/writing-tests/#import-dependency diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml deleted file mode 100644 index a0fbff00c..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-assert.yaml +++ /dev/null @@ -1,30 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-error-external-1 -status: - conditions: - - type: Available - message: OpenStack resource is available - status: "True" - reason: Success - - type: Progressing - message: OpenStack resource is up to date - status: "False" - reason: Success ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-error-external-2 -status: - conditions: - - type: Available - message: OpenStack resource is available - status: "True" - reason: Success - - type: Progressing - message: OpenStack resource is up to date - status: "False" - reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml deleted file mode 100644 index a85f3b243..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-create-resources.yaml +++ /dev/null @@ -1,43 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-import-error -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-error-external-1 -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - description: RoleAssignment from "import error" test - roleRef: roleassignment-import-error - # TODO(scaffolding): add any required field ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-error-external-2 -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - description: RoleAssignment from "import error" test - roleRef: roleassignment-import-error - # TODO(scaffolding): add any required field diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml deleted file mode 100644 index 045711ee7..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-error/00-secret.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} - namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml deleted file mode 100644 index 1f7e3a893..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-error/01-assert.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-error -status: - conditions: - - type: Available - message: found more than one matching OpenStack resource during import - status: "False" - reason: InvalidConfiguration - - type: Progressing - message: found more than one matching OpenStack resource during import - status: "False" - reason: InvalidConfiguration diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml deleted file mode 100644 index 5589b99b8..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-error/01-import-resource.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-error -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - description: RoleAssignment from "import error" test diff --git a/internal/controllers/roleassignment/tests/roleassignment-import-error/README.md b/internal/controllers/roleassignment/tests/roleassignment-import-error/README.md deleted file mode 100644 index 26ca68ce4..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import-error/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Import RoleAssignment with more than one matching resources - -## Step 00 - -Create two RoleAssignments with identical specs. - -## Step 01 - -Ensure that an imported RoleAssignment with a filter matching the resources returns an error. - -## Reference - -https://k-orc.cloud/development/writing-tests/#import-error diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml deleted file mode 100644 index fdc36fdd0..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import/00-assert.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import -status: - conditions: - - type: Available - message: Waiting for OpenStack resource to be created externally - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for OpenStack resource to be created externally - status: "True" - reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml deleted file mode 100644 index e4d6ce579..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import/00-import-resource.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import -spec: - cloudCredentialsRef: - cloudName: openstack - secretName: openstack-clouds - managementPolicy: unmanaged - import: - filter: - name: roleassignment-import-external - description: RoleAssignment roleassignment-import-external from "roleassignment-import" test - # TODO(scaffolding): Add all fields supported by the filter diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml deleted file mode 100644 index 045711ee7..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import/00-secret.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} - namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml deleted file mode 100644 index 9a908403b..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import/01-assert.yaml +++ /dev/null @@ -1,34 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-external-not-this-one -status: - conditions: - - type: Available - message: OpenStack resource is available - status: "True" - reason: Success - - type: Progressing - message: OpenStack resource is up to date - status: "False" - reason: Success - resource: - name: roleassignment-import-external-not-this-one - description: RoleAssignment roleassignment-import-external from "roleassignment-import" test - # TODO(scaffolding): Add fields necessary to match filter ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import -status: - conditions: - - type: Available - message: Waiting for OpenStack resource to be created externally - status: "False" - reason: Progressing - - type: Progressing - message: Waiting for OpenStack resource to be created externally - status: "True" - reason: Progressing diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml deleted file mode 100644 index a991d3d80..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import/01-create-trap-resource.yaml +++ /dev/null @@ -1,31 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-import-external-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -# This `roleassignment-import-external-not-this-one` resource serves two purposes: -# - ensure that we can successfully create another resource which name is a substring of it (i.e. it's not being adopted) -# - ensure that importing a resource which name is a substring of it will not pick this one. -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-external-not-this-one -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - description: RoleAssignment roleassignment-import-external from "roleassignment-import" test - roleRef: roleassignment-import-external-not-this-one - # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml deleted file mode 100644 index b5b5eb744..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import/02-assert.yaml +++ /dev/null @@ -1,33 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-import-external - ref: roleassignment1 - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-import-external-not-this-one - ref: roleassignment2 -assertAll: - - celExpr: "roleassignment1.status.id != roleassignment2.status.id" ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import -status: - conditions: - - type: Available - message: OpenStack resource is available - status: "True" - reason: Success - - type: Progressing - message: OpenStack resource is up to date - status: "False" - reason: Success - resource: - name: roleassignment-import-external - description: RoleAssignment roleassignment-import-external from "roleassignment-import" test - # TODO(scaffolding): Add all fields the resource supports diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml deleted file mode 100644 index a4613b0a5..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import/02-create-resource.yaml +++ /dev/null @@ -1,28 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-import -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-import-external -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - resource: - description: RoleAssignment roleassignment-import-external from "roleassignment-import" test - roleRef: roleassignment-import - # TODO(scaffolding): Add fields necessary to match filter diff --git a/internal/controllers/roleassignment/tests/roleassignment-import/README.md b/internal/controllers/roleassignment/tests/roleassignment-import/README.md deleted file mode 100644 index 0d655cbb5..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-import/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Import RoleAssignment - -## Step 00 - -Import a roleassignment that matches all fields in the filter, and verify it is waiting for the external resource to be created. - -## Step 01 - -Create a roleassignment whose name is a superstring of the one specified in the import filter, otherwise matching the filter, and verify that it's not being imported. - -## Step 02 - -Create a roleassignment matching the filter and verify that the observed status on the imported roleassignment corresponds to the spec of the created roleassignment. -Also, confirm that it does not adopt any roleassignment whose name is a superstring of its own. - -## Reference - -https://k-orc.cloud/development/writing-tests/#import diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/00-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/00-assert.yaml deleted file mode 100644 index 09171f4ed..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-update/00-assert.yaml +++ /dev/null @@ -1,26 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-update - ref: roleassignment -assertAll: - - celExpr: "!has(roleassignment.status.resource.description)" ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-update -status: - resource: - name: roleassignment-update - # TODO(scaffolding): Add matches for more fields - conditions: - - type: Available - status: "True" - reason: Success - - type: Progressing - status: "False" - reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/00-minimal-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/00-minimal-resource.yaml deleted file mode 100644 index dded7b069..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-update/00-minimal-resource.yaml +++ /dev/null @@ -1,28 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: Role -metadata: - name: roleassignment-update -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Add the necessary fields to create the resource - resource: {} ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-update -spec: - cloudCredentialsRef: - # TODO(scaffolding): Use openstack-admin if the resource needs admin credentials to be created or updated - cloudName: openstack - secretName: openstack-clouds - managementPolicy: managed - # TODO(scaffolding): Only add the mandatory fields. It's possible the resource - # doesn't have mandatory fields, in that case, leave it empty. - resource: - roleRef: roleassignment-update diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/00-secret.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/00-secret.yaml deleted file mode 100644 index 045711ee7..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-update/00-secret.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT} - namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/01-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/01-assert.yaml deleted file mode 100644 index 6ac85e51b..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-update/01-assert.yaml +++ /dev/null @@ -1,17 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-update -status: - resource: - name: roleassignment-update-updated - description: roleassignment-update-updated - # TODO(scaffolding): match all fields that were modified - conditions: - - type: Available - status: "True" - reason: Success - - type: Progressing - status: "False" - reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/01-updated-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/01-updated-resource.yaml deleted file mode 100644 index d07ade78c..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-update/01-updated-resource.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-update -spec: - resource: - name: roleassignment-update-updated - description: roleassignment-update-updated - # TODO(scaffolding): update all mutable fields diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/02-assert.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/02-assert.yaml deleted file mode 100644 index 15536b2d3..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-update/02-assert.yaml +++ /dev/null @@ -1,26 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -resourceRefs: - - apiVersion: openstack.k-orc.cloud/v1alpha1 - kind: RoleAssignment - name: roleassignment-update - ref: roleassignment -assertAll: - - celExpr: "!has(roleassignment.status.resource.description)" ---- -apiVersion: openstack.k-orc.cloud/v1alpha1 -kind: RoleAssignment -metadata: - name: roleassignment-update -status: - resource: - name: roleassignment-update - # TODO(scaffolding): validate that updated fields were all reverted to their original value - conditions: - - type: Available - status: "True" - reason: Success - - type: Progressing - status: "False" - reason: Success diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/02-reverted-resource.yaml b/internal/controllers/roleassignment/tests/roleassignment-update/02-reverted-resource.yaml deleted file mode 100644 index 2c6c253ff..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-update/02-reverted-resource.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# NOTE: kuttl only does patch updates, which means we can't delete a field. -# We have to use a kubectl apply command instead. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: - - command: kubectl replace -f 00-minimal-resource.yaml - namespaced: true diff --git a/internal/controllers/roleassignment/tests/roleassignment-update/README.md b/internal/controllers/roleassignment/tests/roleassignment-update/README.md deleted file mode 100644 index 8c0d2dee7..000000000 --- a/internal/controllers/roleassignment/tests/roleassignment-update/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Update RoleAssignment - -## Step 00 - -Create a RoleAssignment using only mandatory fields. - -## Step 01 - -Update all mutable fields. - -## Step 02 - -Revert the resource to its original value and verify that the resulting object matches its state when first created. - -## Reference - -https://k-orc.cloud/development/writing-tests/#update diff --git a/internal/controllers/roleassignment/zz_generated.adapter.go b/internal/controllers/roleassignment/zz_generated.adapter.go new file mode 100644 index 000000000..53f478a76 --- /dev/null +++ b/internal/controllers/roleassignment/zz_generated.adapter.go @@ -0,0 +1,78 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package roleassignment + +import ( + orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces" +) + +// Fundamental types +type ( + orcObjectT = orcv1alpha1.RoleAssignment + orcObjectListT = orcv1alpha1.RoleAssignmentList + resourceSpecT = orcv1alpha1.RoleAssignmentResourceSpec + filterT = orcv1alpha1.RoleAssignmentFilter +) + +// Derived types +type ( + orcObjectPT = *orcObjectT + adapterI = interfaces.APIObjectAdapter[orcObjectPT, resourceSpecT, filterT] + adapterT = roleassignmentAdapter +) + +type roleassignmentAdapter struct { + *orcv1alpha1.RoleAssignment +} + +var _ adapterI = &adapterT{} + +func (f adapterT) GetObject() orcObjectPT { + return f.RoleAssignment +} + +func (f adapterT) GetManagementPolicy() orcv1alpha1.ManagementPolicy { + return f.Spec.ManagementPolicy +} + +func (f adapterT) GetManagedOptions() *orcv1alpha1.ManagedOptions { + return f.Spec.ManagedOptions +} + +func (f adapterT) GetStatusID() *string { + return f.Status.ID +} + +func (f adapterT) GetResourceSpec() *resourceSpecT { + return f.Spec.Resource +} + +func (f adapterT) GetImportID() *string { + if f.Spec.Import == nil { + return nil + } + return f.Spec.Import.ID +} + +func (f adapterT) GetImportFilter() *filterT { + if f.Spec.Import == nil { + return nil + } + return f.Spec.Import.Filter +} diff --git a/internal/controllers/roleassignment/zz_generated.controller.go b/internal/controllers/roleassignment/zz_generated.controller.go new file mode 100644 index 000000000..469e96460 --- /dev/null +++ b/internal/controllers/roleassignment/zz_generated.controller.go @@ -0,0 +1,45 @@ +// Code generated by resource-generator. DO NOT EDIT. +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package roleassignment + +import ( + corev1 "k8s.io/api/core/v1" + + "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency" + orcstrings "github.com/k-orc/openstack-resource-controller/v2/internal/util/strings" +) + +var ( + // NOTE: controllerName must be defined in any controller using this template + + // finalizer is the string this controller adds to an object's Finalizers + finalizer = orcstrings.GetFinalizerName(controllerName) + + // externalObjectFieldOwner is the field owner we use when using + // server-side-apply on objects we don't control + externalObjectFieldOwner = orcstrings.GetSSAFieldOwner(controllerName) + + credentialsDependency = dependency.NewDeletionGuardDependency[*orcObjectListT, *corev1.Secret]( + "spec.cloudCredentialsRef.secretName", + func(obj orcObjectPT) []string { + return []string{obj.Spec.CloudCredentialsRef.SecretName} + }, + finalizer, externalObjectFieldOwner, + dependency.OverrideDependencyName("credentials"), + ) +) diff --git a/internal/osclients/mock/doc.go b/internal/osclients/mock/doc.go index 766500c8f..57088736b 100644 --- a/internal/osclients/mock/doc.go +++ b/internal/osclients/mock/doc.go @@ -56,6 +56,9 @@ import ( //go:generate mockgen -package mock -destination=role.go -source=../role.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock RoleClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt role.go > _role.go && mv _role.go role.go" +//go:generate mockgen -package mock -destination=roleassignment.go -source=../roleassignment.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock RoleAssignmentClient +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt roleassignment.go > _roleassignment.go && mv _roleassignment.go roleassignment.go" + //go:generate mockgen -package mock -destination=service.go -source=../service.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock ServiceClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt service.go > _service.go && mv _service.go service.go" diff --git a/internal/osclients/mock/roleassignment.go b/internal/osclients/mock/roleassignment.go new file mode 100644 index 000000000..fa513aba8 --- /dev/null +++ b/internal/osclients/mock/roleassignment.go @@ -0,0 +1,100 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Code generated by MockGen. DO NOT EDIT. +// Source: ../roleassignment.go +// +// Generated by this command: +// +// mockgen -package mock -destination=roleassignment.go -source=../roleassignment.go github.com/k-orc/openstack-resource-controller/internal/osclients/mock RoleAssignmentClient +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + iter "iter" + reflect "reflect" + + roles "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/roles" + gomock "go.uber.org/mock/gomock" +) + +// MockRoleAssignmentClient is a mock of RoleAssignmentClient interface. +type MockRoleAssignmentClient struct { + ctrl *gomock.Controller + recorder *MockRoleAssignmentClientMockRecorder + isgomock struct{} +} + +// MockRoleAssignmentClientMockRecorder is the mock recorder for MockRoleAssignmentClient. +type MockRoleAssignmentClientMockRecorder struct { + mock *MockRoleAssignmentClient +} + +// NewMockRoleAssignmentClient creates a new mock instance. +func NewMockRoleAssignmentClient(ctrl *gomock.Controller) *MockRoleAssignmentClient { + mock := &MockRoleAssignmentClient{ctrl: ctrl} + mock.recorder = &MockRoleAssignmentClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRoleAssignmentClient) EXPECT() *MockRoleAssignmentClientMockRecorder { + return m.recorder +} + +// AssignRole mocks base method. +func (m *MockRoleAssignmentClient) AssignRole(ctx context.Context, roleID string, opts roles.AssignOpts) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AssignRole", ctx, roleID, opts) + ret0, _ := ret[0].(error) + return ret0 +} + +// AssignRole indicates an expected call of AssignRole. +func (mr *MockRoleAssignmentClientMockRecorder) AssignRole(ctx, roleID, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssignRole", reflect.TypeOf((*MockRoleAssignmentClient)(nil).AssignRole), ctx, roleID, opts) +} + +// ListRoleAssignments mocks base method. +func (m *MockRoleAssignmentClient) ListRoleAssignments(ctx context.Context, listOpts roles.ListAssignmentsOpts) iter.Seq2[*roles.RoleAssignment, error] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListRoleAssignments", ctx, listOpts) + ret0, _ := ret[0].(iter.Seq2[*roles.RoleAssignment, error]) + return ret0 +} + +// ListRoleAssignments indicates an expected call of ListRoleAssignments. +func (mr *MockRoleAssignmentClientMockRecorder) ListRoleAssignments(ctx, listOpts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListRoleAssignments", reflect.TypeOf((*MockRoleAssignmentClient)(nil).ListRoleAssignments), ctx, listOpts) +} + +// UnassignRole mocks base method. +func (m *MockRoleAssignmentClient) UnassignRole(ctx context.Context, roleID string, opts roles.UnassignOpts) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UnassignRole", ctx, roleID, opts) + ret0, _ := ret[0].(error) + return ret0 +} + +// UnassignRole indicates an expected call of UnassignRole. +func (mr *MockRoleAssignmentClientMockRecorder) UnassignRole(ctx, roleID, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnassignRole", reflect.TypeOf((*MockRoleAssignmentClient)(nil).UnassignRole), ctx, roleID, opts) +} diff --git a/internal/osclients/roleassignment.go b/internal/osclients/roleassignment.go index 62da755cd..bf83a75b2 100644 --- a/internal/osclients/roleassignment.go +++ b/internal/osclients/roleassignment.go @@ -28,16 +28,14 @@ import ( ) type RoleAssignmentClient interface { - ListRoleAssignments(ctx context.Context, listOpts roles.ListOptsBuilder) iter.Seq2[*roles.RoleAssignment, error] - CreateRoleAssignment(ctx context.Context, opts roles.CreateOptsBuilder) (*roles.RoleAssignment, error) - DeleteRoleAssignment(ctx context.Context, resourceID string) error - GetRoleAssignment(ctx context.Context, resourceID string) (*roles.RoleAssignment, error) - UpdateRoleAssignment(ctx context.Context, id string, opts roles.UpdateOptsBuilder) (*roles.RoleAssignment, error) + ListRoleAssignments(ctx context.Context, listOpts roles.ListAssignmentsOpts) iter.Seq2[*roles.RoleAssignment, error] + AssignRole(ctx context.Context, roleID string, opts roles.AssignOpts) error + UnassignRole(ctx context.Context, roleID string, opts roles.UnassignOpts) error } type roleassignmentClient struct{ client *gophercloud.ServiceClient } -// NewRoleAssignmentClient returns a new OpenStack client. +// NewRoleAssignmentClient returns a new OpenStack Identity client for role assignments. func NewRoleAssignmentClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (RoleAssignmentClient, error) { client, err := openstack.NewIdentityV3(providerClient, gophercloud.EndpointOpts{ Region: providerClientOpts.RegionName, @@ -45,33 +43,25 @@ func NewRoleAssignmentClient(providerClient *gophercloud.ProviderClient, provide }) if err != nil { - return nil, fmt.Errorf("failed to create roleassignment service client: %v", err) + return nil, fmt.Errorf("failed to create role assignment service client: %v", err) } return &roleassignmentClient{client}, nil } -func (c roleassignmentClient) ListRoleAssignments(ctx context.Context, listOpts roles.ListOptsBuilder) iter.Seq2[*roles.RoleAssignment, error] { - pager := roles.List(c.client, listOpts) +func (c roleassignmentClient) ListRoleAssignments(ctx context.Context, listOpts roles.ListAssignmentsOpts) iter.Seq2[*roles.RoleAssignment, error] { + pager := roles.ListAssignments(c.client, listOpts) return func(yield func(*roles.RoleAssignment, error) bool) { _ = pager.EachPage(ctx, yieldPage(roles.ExtractRoleAssignments, yield)) } } -func (c roleassignmentClient) CreateRoleAssignment(ctx context.Context, opts roles.CreateOptsBuilder) (*roles.RoleAssignment, error) { - return roles.Create(ctx, c.client, opts).Extract() +func (c roleassignmentClient) AssignRole(ctx context.Context, roleID string, opts roles.AssignOpts) error { + return roles.Assign(ctx, c.client, roleID, opts).ExtractErr() } -func (c roleassignmentClient) DeleteRoleAssignment(ctx context.Context, resourceID string) error { - return roles.Delete(ctx, c.client, resourceID).ExtractErr() -} - -func (c roleassignmentClient) GetRoleAssignment(ctx context.Context, resourceID string) (*roles.RoleAssignment, error) { - return roles.Get(ctx, c.client, resourceID).Extract() -} - -func (c roleassignmentClient) UpdateRoleAssignment(ctx context.Context, id string, opts roles.UpdateOptsBuilder) (*roles.RoleAssignment, error) { - return roles.Update(ctx, c.client, id, opts).Extract() +func (c roleassignmentClient) UnassignRole(ctx context.Context, roleID string, opts roles.UnassignOpts) error { + return roles.Unassign(ctx, c.client, roleID, opts).ExtractErr() } type roleassignmentErrorClient struct{ error } @@ -81,24 +71,16 @@ func NewRoleAssignmentErrorClient(e error) RoleAssignmentClient { return roleassignmentErrorClient{e} } -func (e roleassignmentErrorClient) ListRoleAssignments(_ context.Context, _ roles.ListOptsBuilder) iter.Seq2[*roles.RoleAssignment, error] { +func (e roleassignmentErrorClient) ListRoleAssignments(_ context.Context, _ roles.ListAssignmentsOpts) iter.Seq2[*roles.RoleAssignment, error] { return func(yield func(*roles.RoleAssignment, error) bool) { yield(nil, e.error) } } -func (e roleassignmentErrorClient) CreateRoleAssignment(_ context.Context, _ roles.CreateOptsBuilder) (*roles.RoleAssignment, error) { - return nil, e.error -} - -func (e roleassignmentErrorClient) DeleteRoleAssignment(_ context.Context, _ string) error { +func (e roleassignmentErrorClient) AssignRole(_ context.Context, _ string, _ roles.AssignOpts) error { return e.error } -func (e roleassignmentErrorClient) GetRoleAssignment(_ context.Context, _ string) (*roles.RoleAssignment, error) { - return nil, e.error -} - -func (e roleassignmentErrorClient) UpdateRoleAssignment(_ context.Context, _ string, _ roles.UpdateOptsBuilder) (*roles.RoleAssignment, error) { - return nil, e.error +func (e roleassignmentErrorClient) UnassignRole(_ context.Context, _ string, _ roles.UnassignOpts) error { + return e.error } diff --git a/internal/scope/mock.go b/internal/scope/mock.go index 256fa2e1d..fdf984782 100644 --- a/internal/scope/mock.go +++ b/internal/scope/mock.go @@ -45,6 +45,7 @@ type MockScopeFactory struct { KeyPairClient *mock.MockKeyPairClient NetworkClient *mock.MockNetworkClient RoleClient *mock.MockRoleClient + RoleAssignmentClient *mock.MockRoleAssignmentClient ServiceClient *mock.MockServiceClient UserClient *mock.MockUserClient VolumeClient *mock.MockVolumeClient @@ -65,6 +66,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { keypairClient := mock.NewMockKeyPairClient(mockCtrl) networkClient := mock.NewMockNetworkClient(mockCtrl) roleClient := mock.NewMockRoleClient(mockCtrl) + roleassignmentClient := mock.NewMockRoleAssignmentClient(mockCtrl) serviceClient := mock.NewMockServiceClient(mockCtrl) userClient := mock.NewMockUserClient(mockCtrl) volumeClient := mock.NewMockVolumeClient(mockCtrl) @@ -82,6 +84,7 @@ func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory { KeyPairClient: keypairClient, NetworkClient: networkClient, RoleClient: roleClient, + RoleAssignmentClient: roleassignmentClient, ServiceClient: serviceClient, UserClient: userClient, VolumeClient: volumeClient, @@ -152,6 +155,10 @@ func (f *MockScopeFactory) NewRoleClient() (osclients.RoleClient, error) { return f.RoleClient, nil } +func (f *MockScopeFactory) NewRoleAssignmentClient() (osclients.RoleAssignmentClient, error) { + return f.RoleAssignmentClient, nil +} + func (f *MockScopeFactory) NewEndpointClient() (osclients.EndpointClient, error) { return f.EndpointClient, nil } diff --git a/internal/scope/provider.go b/internal/scope/provider.go index aadd5c5ff..c6fd90099 100644 --- a/internal/scope/provider.go +++ b/internal/scope/provider.go @@ -197,6 +197,10 @@ func (s *providerScope) NewRoleClient() (clients.RoleClient, error) { return clients.NewRoleClient(s.providerClient, s.providerClientOpts) } +func (s *providerScope) NewRoleAssignmentClient() (clients.RoleAssignmentClient, error) { + return clients.NewRoleAssignmentClient(s.providerClient, s.providerClientOpts) +} + func (s *providerScope) ExtractToken() (*tokens.Token, error) { client, err := openstack.NewIdentityV3(s.providerClient, gophercloud.EndpointOpts{}) if err != nil { diff --git a/internal/scope/scope.go b/internal/scope/scope.go index d8426fd62..63d6f649d 100644 --- a/internal/scope/scope.go +++ b/internal/scope/scope.go @@ -59,6 +59,7 @@ type Scope interface { NewKeyPairClient() (osclients.KeyPairClient, error) NewNetworkClient() (osclients.NetworkClient, error) NewRoleClient() (osclients.RoleClient, error) + NewRoleAssignmentClient() (osclients.RoleAssignmentClient, error) NewServiceClient() (osclients.ServiceClient, error) NewUserClient() (osclients.UserClient, error) NewVolumeClient() (osclients.VolumeClient, error) diff --git a/kuttl-test.yaml b/kuttl-test.yaml index d58526493..99d71a82c 100644 --- a/kuttl-test.yaml +++ b/kuttl-test.yaml @@ -15,6 +15,7 @@ testDirs: - ./internal/controllers/port/tests/ - ./internal/controllers/project/tests/ - ./internal/controllers/role/tests/ +- ./internal/controllers/roleassignment/tests/ - ./internal/controllers/router/tests/ - ./internal/controllers/routerinterface/tests/ - ./internal/controllers/securitygroup/tests/ diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignment.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignment.go new file mode 100644 index 000000000..26462d14d --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignment.go @@ -0,0 +1,281 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + internal "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/internal" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// RoleAssignmentApplyConfiguration represents a declarative configuration of the RoleAssignment type for use +// with apply. +type RoleAssignmentApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *RoleAssignmentSpecApplyConfiguration `json:"spec,omitempty"` + Status *RoleAssignmentStatusApplyConfiguration `json:"status,omitempty"` +} + +// RoleAssignment constructs a declarative configuration of the RoleAssignment type for use with +// apply. +func RoleAssignment(name, namespace string) *RoleAssignmentApplyConfiguration { + b := &RoleAssignmentApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("RoleAssignment") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b +} + +// ExtractRoleAssignment extracts the applied configuration owned by fieldManager from +// roleAssignment. If no managedFields are found in roleAssignment for fieldManager, a +// RoleAssignmentApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// roleAssignment must be a unmodified RoleAssignment API object that was retrieved from the Kubernetes API. +// ExtractRoleAssignment provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +// Experimental! +func ExtractRoleAssignment(roleAssignment *apiv1alpha1.RoleAssignment, fieldManager string) (*RoleAssignmentApplyConfiguration, error) { + return extractRoleAssignment(roleAssignment, fieldManager, "") +} + +// ExtractRoleAssignmentStatus is the same as ExtractRoleAssignment except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractRoleAssignmentStatus(roleAssignment *apiv1alpha1.RoleAssignment, fieldManager string) (*RoleAssignmentApplyConfiguration, error) { + return extractRoleAssignment(roleAssignment, fieldManager, "status") +} + +func extractRoleAssignment(roleAssignment *apiv1alpha1.RoleAssignment, fieldManager string, subresource string) (*RoleAssignmentApplyConfiguration, error) { + b := &RoleAssignmentApplyConfiguration{} + err := managedfields.ExtractInto(roleAssignment, internal.Parser().Type("com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignment"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(roleAssignment.Name) + b.WithNamespace(roleAssignment.Namespace) + + b.WithKind("RoleAssignment") + b.WithAPIVersion("openstack.k-orc.cloud/v1alpha1") + return b, nil +} +func (b RoleAssignmentApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *RoleAssignmentApplyConfiguration) WithKind(value string) *RoleAssignmentApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *RoleAssignmentApplyConfiguration) WithAPIVersion(value string) *RoleAssignmentApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *RoleAssignmentApplyConfiguration) WithName(value string) *RoleAssignmentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *RoleAssignmentApplyConfiguration) WithGenerateName(value string) *RoleAssignmentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *RoleAssignmentApplyConfiguration) WithNamespace(value string) *RoleAssignmentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *RoleAssignmentApplyConfiguration) WithUID(value types.UID) *RoleAssignmentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *RoleAssignmentApplyConfiguration) WithResourceVersion(value string) *RoleAssignmentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *RoleAssignmentApplyConfiguration) WithGeneration(value int64) *RoleAssignmentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *RoleAssignmentApplyConfiguration) WithCreationTimestamp(value metav1.Time) *RoleAssignmentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *RoleAssignmentApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *RoleAssignmentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *RoleAssignmentApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *RoleAssignmentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *RoleAssignmentApplyConfiguration) WithLabels(entries map[string]string) *RoleAssignmentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *RoleAssignmentApplyConfiguration) WithAnnotations(entries map[string]string) *RoleAssignmentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *RoleAssignmentApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *RoleAssignmentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *RoleAssignmentApplyConfiguration) WithFinalizers(values ...string) *RoleAssignmentApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *RoleAssignmentApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *RoleAssignmentApplyConfiguration) WithSpec(value *RoleAssignmentSpecApplyConfiguration) *RoleAssignmentApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *RoleAssignmentApplyConfiguration) WithStatus(value *RoleAssignmentStatusApplyConfiguration) *RoleAssignmentApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *RoleAssignmentApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *RoleAssignmentApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *RoleAssignmentApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *RoleAssignmentApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentfilter.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentfilter.go new file mode 100644 index 000000000..5367d30da --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentfilter.go @@ -0,0 +1,79 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// RoleAssignmentFilterApplyConfiguration represents a declarative configuration of the RoleAssignmentFilter type for use +// with apply. +type RoleAssignmentFilterApplyConfiguration struct { + RoleRef *apiv1alpha1.KubernetesNameRef `json:"roleRef,omitempty"` + UserRef *apiv1alpha1.KubernetesNameRef `json:"userRef,omitempty"` + GroupRef *apiv1alpha1.KubernetesNameRef `json:"groupRef,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` +} + +// RoleAssignmentFilterApplyConfiguration constructs a declarative configuration of the RoleAssignmentFilter type for use with +// apply. +func RoleAssignmentFilter() *RoleAssignmentFilterApplyConfiguration { + return &RoleAssignmentFilterApplyConfiguration{} +} + +// WithRoleRef sets the RoleRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RoleRef field is set to the value of the last call. +func (b *RoleAssignmentFilterApplyConfiguration) WithRoleRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentFilterApplyConfiguration { + b.RoleRef = &value + return b +} + +// WithUserRef sets the UserRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UserRef field is set to the value of the last call. +func (b *RoleAssignmentFilterApplyConfiguration) WithUserRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentFilterApplyConfiguration { + b.UserRef = &value + return b +} + +// WithGroupRef sets the GroupRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GroupRef field is set to the value of the last call. +func (b *RoleAssignmentFilterApplyConfiguration) WithGroupRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentFilterApplyConfiguration { + b.GroupRef = &value + return b +} + +// WithProjectRef sets the ProjectRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ProjectRef field is set to the value of the last call. +func (b *RoleAssignmentFilterApplyConfiguration) WithProjectRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentFilterApplyConfiguration { + b.ProjectRef = &value + return b +} + +// WithDomainRef sets the DomainRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DomainRef field is set to the value of the last call. +func (b *RoleAssignmentFilterApplyConfiguration) WithDomainRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentFilterApplyConfiguration { + b.DomainRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentimport.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentimport.go new file mode 100644 index 000000000..364d27bcf --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentimport.go @@ -0,0 +1,48 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// RoleAssignmentImportApplyConfiguration represents a declarative configuration of the RoleAssignmentImport type for use +// with apply. +type RoleAssignmentImportApplyConfiguration struct { + ID *string `json:"id,omitempty"` + Filter *RoleAssignmentFilterApplyConfiguration `json:"filter,omitempty"` +} + +// RoleAssignmentImportApplyConfiguration constructs a declarative configuration of the RoleAssignmentImport type for use with +// apply. +func RoleAssignmentImport() *RoleAssignmentImportApplyConfiguration { + return &RoleAssignmentImportApplyConfiguration{} +} + +// WithID sets the ID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ID field is set to the value of the last call. +func (b *RoleAssignmentImportApplyConfiguration) WithID(value string) *RoleAssignmentImportApplyConfiguration { + b.ID = &value + return b +} + +// WithFilter sets the Filter field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Filter field is set to the value of the last call. +func (b *RoleAssignmentImportApplyConfiguration) WithFilter(value *RoleAssignmentFilterApplyConfiguration) *RoleAssignmentImportApplyConfiguration { + b.Filter = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentresourcespec.go new file mode 100644 index 000000000..680572620 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentresourcespec.go @@ -0,0 +1,79 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// RoleAssignmentResourceSpecApplyConfiguration represents a declarative configuration of the RoleAssignmentResourceSpec type for use +// with apply. +type RoleAssignmentResourceSpecApplyConfiguration struct { + RoleRef *apiv1alpha1.KubernetesNameRef `json:"roleRef,omitempty"` + UserRef *apiv1alpha1.KubernetesNameRef `json:"userRef,omitempty"` + GroupRef *apiv1alpha1.KubernetesNameRef `json:"groupRef,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` +} + +// RoleAssignmentResourceSpecApplyConfiguration constructs a declarative configuration of the RoleAssignmentResourceSpec type for use with +// apply. +func RoleAssignmentResourceSpec() *RoleAssignmentResourceSpecApplyConfiguration { + return &RoleAssignmentResourceSpecApplyConfiguration{} +} + +// WithRoleRef sets the RoleRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RoleRef field is set to the value of the last call. +func (b *RoleAssignmentResourceSpecApplyConfiguration) WithRoleRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentResourceSpecApplyConfiguration { + b.RoleRef = &value + return b +} + +// WithUserRef sets the UserRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UserRef field is set to the value of the last call. +func (b *RoleAssignmentResourceSpecApplyConfiguration) WithUserRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentResourceSpecApplyConfiguration { + b.UserRef = &value + return b +} + +// WithGroupRef sets the GroupRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GroupRef field is set to the value of the last call. +func (b *RoleAssignmentResourceSpecApplyConfiguration) WithGroupRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentResourceSpecApplyConfiguration { + b.GroupRef = &value + return b +} + +// WithProjectRef sets the ProjectRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ProjectRef field is set to the value of the last call. +func (b *RoleAssignmentResourceSpecApplyConfiguration) WithProjectRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentResourceSpecApplyConfiguration { + b.ProjectRef = &value + return b +} + +// WithDomainRef sets the DomainRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DomainRef field is set to the value of the last call. +func (b *RoleAssignmentResourceSpecApplyConfiguration) WithDomainRef(value apiv1alpha1.KubernetesNameRef) *RoleAssignmentResourceSpecApplyConfiguration { + b.DomainRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentresourcestatus.go new file mode 100644 index 000000000..e4e29148e --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentresourcestatus.go @@ -0,0 +1,75 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// RoleAssignmentResourceStatusApplyConfiguration represents a declarative configuration of the RoleAssignmentResourceStatus type for use +// with apply. +type RoleAssignmentResourceStatusApplyConfiguration struct { + RoleID *string `json:"roleID,omitempty"` + UserID *string `json:"userID,omitempty"` + GroupID *string `json:"groupID,omitempty"` + ProjectID *string `json:"projectID,omitempty"` + DomainID *string `json:"domainID,omitempty"` +} + +// RoleAssignmentResourceStatusApplyConfiguration constructs a declarative configuration of the RoleAssignmentResourceStatus type for use with +// apply. +func RoleAssignmentResourceStatus() *RoleAssignmentResourceStatusApplyConfiguration { + return &RoleAssignmentResourceStatusApplyConfiguration{} +} + +// WithRoleID sets the RoleID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RoleID field is set to the value of the last call. +func (b *RoleAssignmentResourceStatusApplyConfiguration) WithRoleID(value string) *RoleAssignmentResourceStatusApplyConfiguration { + b.RoleID = &value + return b +} + +// WithUserID sets the UserID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UserID field is set to the value of the last call. +func (b *RoleAssignmentResourceStatusApplyConfiguration) WithUserID(value string) *RoleAssignmentResourceStatusApplyConfiguration { + b.UserID = &value + return b +} + +// WithGroupID sets the GroupID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GroupID field is set to the value of the last call. +func (b *RoleAssignmentResourceStatusApplyConfiguration) WithGroupID(value string) *RoleAssignmentResourceStatusApplyConfiguration { + b.GroupID = &value + return b +} + +// WithProjectID sets the ProjectID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ProjectID field is set to the value of the last call. +func (b *RoleAssignmentResourceStatusApplyConfiguration) WithProjectID(value string) *RoleAssignmentResourceStatusApplyConfiguration { + b.ProjectID = &value + return b +} + +// WithDomainID sets the DomainID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DomainID field is set to the value of the last call. +func (b *RoleAssignmentResourceStatusApplyConfiguration) WithDomainID(value string) *RoleAssignmentResourceStatusApplyConfiguration { + b.DomainID = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentspec.go new file mode 100644 index 000000000..df29d44b1 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentspec.go @@ -0,0 +1,79 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" +) + +// RoleAssignmentSpecApplyConfiguration represents a declarative configuration of the RoleAssignmentSpec type for use +// with apply. +type RoleAssignmentSpecApplyConfiguration struct { + Import *RoleAssignmentImportApplyConfiguration `json:"import,omitempty"` + Resource *RoleAssignmentResourceSpecApplyConfiguration `json:"resource,omitempty"` + ManagementPolicy *apiv1alpha1.ManagementPolicy `json:"managementPolicy,omitempty"` + ManagedOptions *ManagedOptionsApplyConfiguration `json:"managedOptions,omitempty"` + CloudCredentialsRef *CloudCredentialsReferenceApplyConfiguration `json:"cloudCredentialsRef,omitempty"` +} + +// RoleAssignmentSpecApplyConfiguration constructs a declarative configuration of the RoleAssignmentSpec type for use with +// apply. +func RoleAssignmentSpec() *RoleAssignmentSpecApplyConfiguration { + return &RoleAssignmentSpecApplyConfiguration{} +} + +// WithImport sets the Import field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Import field is set to the value of the last call. +func (b *RoleAssignmentSpecApplyConfiguration) WithImport(value *RoleAssignmentImportApplyConfiguration) *RoleAssignmentSpecApplyConfiguration { + b.Import = value + return b +} + +// WithResource sets the Resource field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Resource field is set to the value of the last call. +func (b *RoleAssignmentSpecApplyConfiguration) WithResource(value *RoleAssignmentResourceSpecApplyConfiguration) *RoleAssignmentSpecApplyConfiguration { + b.Resource = value + return b +} + +// WithManagementPolicy sets the ManagementPolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ManagementPolicy field is set to the value of the last call. +func (b *RoleAssignmentSpecApplyConfiguration) WithManagementPolicy(value apiv1alpha1.ManagementPolicy) *RoleAssignmentSpecApplyConfiguration { + b.ManagementPolicy = &value + return b +} + +// WithManagedOptions sets the ManagedOptions field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ManagedOptions field is set to the value of the last call. +func (b *RoleAssignmentSpecApplyConfiguration) WithManagedOptions(value *ManagedOptionsApplyConfiguration) *RoleAssignmentSpecApplyConfiguration { + b.ManagedOptions = value + return b +} + +// WithCloudCredentialsRef sets the CloudCredentialsRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CloudCredentialsRef field is set to the value of the last call. +func (b *RoleAssignmentSpecApplyConfiguration) WithCloudCredentialsRef(value *CloudCredentialsReferenceApplyConfiguration) *RoleAssignmentSpecApplyConfiguration { + b.CloudCredentialsRef = value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentstatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentstatus.go new file mode 100644 index 000000000..1bb4a0d56 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/roleassignmentstatus.go @@ -0,0 +1,66 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// RoleAssignmentStatusApplyConfiguration represents a declarative configuration of the RoleAssignmentStatus type for use +// with apply. +type RoleAssignmentStatusApplyConfiguration struct { + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ID *string `json:"id,omitempty"` + Resource *RoleAssignmentResourceStatusApplyConfiguration `json:"resource,omitempty"` +} + +// RoleAssignmentStatusApplyConfiguration constructs a declarative configuration of the RoleAssignmentStatus type for use with +// apply. +func RoleAssignmentStatus() *RoleAssignmentStatusApplyConfiguration { + return &RoleAssignmentStatusApplyConfiguration{} +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *RoleAssignmentStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *RoleAssignmentStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} + +// WithID sets the ID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ID field is set to the value of the last call. +func (b *RoleAssignmentStatusApplyConfiguration) WithID(value string) *RoleAssignmentStatusApplyConfiguration { + b.ID = &value + return b +} + +// WithResource sets the Resource field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Resource field is set to the value of the last call. +func (b *RoleAssignmentStatusApplyConfiguration) WithResource(value *RoleAssignmentResourceStatusApplyConfiguration) *RoleAssignmentStatusApplyConfiguration { + b.Resource = value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 419e61894..34eda1487 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -2067,6 +2067,126 @@ var schemaYAML = typed.YAMLObject(`types: type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleStatus default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignment + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentSpec + default: {} + - name: status + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentStatus + default: {} +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentFilter + map: + fields: + - name: domainRef + type: + scalar: string + - name: groupRef + type: + scalar: string + - name: projectRef + type: + scalar: string + - name: roleRef + type: + scalar: string + - name: userRef + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentImport + map: + fields: + - name: filter + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentFilter + - name: id + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentResourceSpec + map: + fields: + - name: domainRef + type: + scalar: string + - name: groupRef + type: + scalar: string + - name: projectRef + type: + scalar: string + - name: roleRef + type: + scalar: string + - name: userRef + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentResourceStatus + map: + fields: + - name: domainID + type: + scalar: string + - name: groupID + type: + scalar: string + - name: projectID + type: + scalar: string + - name: roleID + type: + scalar: string + - name: userID + type: + scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentSpec + map: + fields: + - name: cloudCredentialsRef + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.CloudCredentialsReference + default: {} + - name: import + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentImport + - name: managedOptions + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ManagedOptions + - name: managementPolicy + type: + scalar: string + - name: resource + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentResourceSpec +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentStatus + map: + fields: + - name: conditions + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition + elementRelationship: associative + keys: + - type + - name: id + type: + scalar: string + - name: resource + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleAssignmentResourceStatus - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.RoleFilter map: fields: diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 618c6c9b7..6cd89eefd 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -264,6 +264,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.ProviderPropertiesStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Role"): return &apiv1alpha1.RoleApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RoleAssignment"): + return &apiv1alpha1.RoleAssignmentApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RoleAssignmentFilter"): + return &apiv1alpha1.RoleAssignmentFilterApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RoleAssignmentImport"): + return &apiv1alpha1.RoleAssignmentImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RoleAssignmentResourceSpec"): + return &apiv1alpha1.RoleAssignmentResourceSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RoleAssignmentResourceStatus"): + return &apiv1alpha1.RoleAssignmentResourceStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RoleAssignmentSpec"): + return &apiv1alpha1.RoleAssignmentSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("RoleAssignmentStatus"): + return &apiv1alpha1.RoleAssignmentStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("RoleFilter"): return &apiv1alpha1.RoleFilterApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("RoleImport"): diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go index a145cab6f..af2dbd9e0 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/api_client.go @@ -41,6 +41,7 @@ type OpenstackV1alpha1Interface interface { PortsGetter ProjectsGetter RolesGetter + RoleAssignmentsGetter RoutersGetter RouterInterfacesGetter SecurityGroupsGetter @@ -111,6 +112,10 @@ func (c *OpenstackV1alpha1Client) Roles(namespace string) RoleInterface { return newRoles(c, namespace) } +func (c *OpenstackV1alpha1Client) RoleAssignments(namespace string) RoleAssignmentInterface { + return newRoleAssignments(c, namespace) +} + func (c *OpenstackV1alpha1Client) Routers(namespace string) RouterInterface { return newRouters(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go index 87bc2b39d..2b3498411 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_api_client.go @@ -80,6 +80,10 @@ func (c *FakeOpenstackV1alpha1) Roles(namespace string) v1alpha1.RoleInterface { return newFakeRoles(c, namespace) } +func (c *FakeOpenstackV1alpha1) RoleAssignments(namespace string) v1alpha1.RoleAssignmentInterface { + return newFakeRoleAssignments(c, namespace) +} + func (c *FakeOpenstackV1alpha1) Routers(namespace string) v1alpha1.RouterInterface { return newFakeRouters(c, namespace) } diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_roleassignment.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_roleassignment.go new file mode 100644 index 000000000..05bbae41c --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/fake/fake_roleassignment.go @@ -0,0 +1,53 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" + typedapiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset/typed/api/v1alpha1" + gentype "k8s.io/client-go/gentype" +) + +// fakeRoleAssignments implements RoleAssignmentInterface +type fakeRoleAssignments struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.RoleAssignment, *v1alpha1.RoleAssignmentList, *apiv1alpha1.RoleAssignmentApplyConfiguration] + Fake *FakeOpenstackV1alpha1 +} + +func newFakeRoleAssignments(fake *FakeOpenstackV1alpha1, namespace string) typedapiv1alpha1.RoleAssignmentInterface { + return &fakeRoleAssignments{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.RoleAssignment, *v1alpha1.RoleAssignmentList, *apiv1alpha1.RoleAssignmentApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("roleassignments"), + v1alpha1.SchemeGroupVersion.WithKind("RoleAssignment"), + func() *v1alpha1.RoleAssignment { return &v1alpha1.RoleAssignment{} }, + func() *v1alpha1.RoleAssignmentList { return &v1alpha1.RoleAssignmentList{} }, + func(dst, src *v1alpha1.RoleAssignmentList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.RoleAssignmentList) []*v1alpha1.RoleAssignment { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.RoleAssignmentList, items []*v1alpha1.RoleAssignment) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go index de60388f2..7d409e9d8 100644 --- a/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/generated_expansion.go @@ -44,6 +44,8 @@ type ProjectExpansion interface{} type RoleExpansion interface{} +type RoleAssignmentExpansion interface{} + type RouterExpansion interface{} type RouterInterfaceExpansion interface{} diff --git a/pkg/clients/clientset/clientset/typed/api/v1alpha1/roleassignment.go b/pkg/clients/clientset/clientset/typed/api/v1alpha1/roleassignment.go new file mode 100644 index 000000000..37d5f5a96 --- /dev/null +++ b/pkg/clients/clientset/clientset/typed/api/v1alpha1/roleassignment.go @@ -0,0 +1,74 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + applyconfigurationapiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1" + scheme "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// RoleAssignmentsGetter has a method to return a RoleAssignmentInterface. +// A group's client should implement this interface. +type RoleAssignmentsGetter interface { + RoleAssignments(namespace string) RoleAssignmentInterface +} + +// RoleAssignmentInterface has methods to work with RoleAssignment resources. +type RoleAssignmentInterface interface { + Create(ctx context.Context, roleAssignment *apiv1alpha1.RoleAssignment, opts v1.CreateOptions) (*apiv1alpha1.RoleAssignment, error) + Update(ctx context.Context, roleAssignment *apiv1alpha1.RoleAssignment, opts v1.UpdateOptions) (*apiv1alpha1.RoleAssignment, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, roleAssignment *apiv1alpha1.RoleAssignment, opts v1.UpdateOptions) (*apiv1alpha1.RoleAssignment, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*apiv1alpha1.RoleAssignment, error) + List(ctx context.Context, opts v1.ListOptions) (*apiv1alpha1.RoleAssignmentList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *apiv1alpha1.RoleAssignment, err error) + Apply(ctx context.Context, roleAssignment *applyconfigurationapiv1alpha1.RoleAssignmentApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.RoleAssignment, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, roleAssignment *applyconfigurationapiv1alpha1.RoleAssignmentApplyConfiguration, opts v1.ApplyOptions) (result *apiv1alpha1.RoleAssignment, err error) + RoleAssignmentExpansion +} + +// roleAssignments implements RoleAssignmentInterface +type roleAssignments struct { + *gentype.ClientWithListAndApply[*apiv1alpha1.RoleAssignment, *apiv1alpha1.RoleAssignmentList, *applyconfigurationapiv1alpha1.RoleAssignmentApplyConfiguration] +} + +// newRoleAssignments returns a RoleAssignments +func newRoleAssignments(c *OpenstackV1alpha1Client, namespace string) *roleAssignments { + return &roleAssignments{ + gentype.NewClientWithListAndApply[*apiv1alpha1.RoleAssignment, *apiv1alpha1.RoleAssignmentList, *applyconfigurationapiv1alpha1.RoleAssignmentApplyConfiguration]( + "roleassignments", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apiv1alpha1.RoleAssignment { return &apiv1alpha1.RoleAssignment{} }, + func() *apiv1alpha1.RoleAssignmentList { return &apiv1alpha1.RoleAssignmentList{} }, + ), + } +} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go index b9b415243..0db92472f 100644 --- a/pkg/clients/informers/externalversions/api/v1alpha1/interface.go +++ b/pkg/clients/informers/externalversions/api/v1alpha1/interface.go @@ -50,6 +50,8 @@ type Interface interface { Projects() ProjectInformer // Roles returns a RoleInformer. Roles() RoleInformer + // RoleAssignments returns a RoleAssignmentInformer. + RoleAssignments() RoleAssignmentInformer // Routers returns a RouterInformer. Routers() RouterInformer // RouterInterfaces returns a RouterInterfaceInformer. @@ -150,6 +152,11 @@ func (v *version) Roles() RoleInformer { return &roleInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// RoleAssignments returns a RoleAssignmentInformer. +func (v *version) RoleAssignments() RoleAssignmentInformer { + return &roleAssignmentInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Routers returns a RouterInformer. func (v *version) Routers() RouterInformer { return &routerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/clients/informers/externalversions/api/v1alpha1/roleassignment.go b/pkg/clients/informers/externalversions/api/v1alpha1/roleassignment.go new file mode 100644 index 000000000..d34221cf4 --- /dev/null +++ b/pkg/clients/informers/externalversions/api/v1alpha1/roleassignment.go @@ -0,0 +1,102 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + time "time" + + v2apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + clientset "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/clientset/clientset" + internalinterfaces "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/informers/externalversions/internalinterfaces" + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/listers/api/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// RoleAssignmentInformer provides access to a shared informer and lister for +// RoleAssignments. +type RoleAssignmentInformer interface { + Informer() cache.SharedIndexInformer + Lister() apiv1alpha1.RoleAssignmentLister +} + +type roleAssignmentInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewRoleAssignmentInformer constructs a new informer for RoleAssignment type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewRoleAssignmentInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredRoleAssignmentInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredRoleAssignmentInformer constructs a new informer for RoleAssignment type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredRoleAssignmentInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().RoleAssignments(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().RoleAssignments(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().RoleAssignments(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OpenstackV1alpha1().RoleAssignments(namespace).Watch(ctx, options) + }, + }, + &v2apiv1alpha1.RoleAssignment{}, + resyncPeriod, + indexers, + ) +} + +func (f *roleAssignmentInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredRoleAssignmentInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *roleAssignmentInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&v2apiv1alpha1.RoleAssignment{}, f.defaultInformer) +} + +func (f *roleAssignmentInformer) Lister() apiv1alpha1.RoleAssignmentLister { + return apiv1alpha1.NewRoleAssignmentLister(f.Informer().GetIndexer()) +} diff --git a/pkg/clients/informers/externalversions/generic.go b/pkg/clients/informers/externalversions/generic.go index 99f589164..a6627b89c 100644 --- a/pkg/clients/informers/externalversions/generic.go +++ b/pkg/clients/informers/externalversions/generic.go @@ -79,6 +79,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Projects().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("roles"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Roles().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("roleassignments"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().RoleAssignments().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("routers"): return &genericInformer{resource: resource.GroupResource(), informer: f.Openstack().V1alpha1().Routers().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("routerinterfaces"): diff --git a/pkg/clients/listers/api/v1alpha1/expansion_generated.go b/pkg/clients/listers/api/v1alpha1/expansion_generated.go index 98590bb01..77fd9e47b 100644 --- a/pkg/clients/listers/api/v1alpha1/expansion_generated.go +++ b/pkg/clients/listers/api/v1alpha1/expansion_generated.go @@ -122,6 +122,14 @@ type RoleListerExpansion interface{} // RoleNamespaceLister. type RoleNamespaceListerExpansion interface{} +// RoleAssignmentListerExpansion allows custom methods to be added to +// RoleAssignmentLister. +type RoleAssignmentListerExpansion interface{} + +// RoleAssignmentNamespaceListerExpansion allows custom methods to be added to +// RoleAssignmentNamespaceLister. +type RoleAssignmentNamespaceListerExpansion interface{} + // RouterListerExpansion allows custom methods to be added to // RouterLister. type RouterListerExpansion interface{} diff --git a/pkg/clients/listers/api/v1alpha1/roleassignment.go b/pkg/clients/listers/api/v1alpha1/roleassignment.go new file mode 100644 index 000000000..37c6c7b8d --- /dev/null +++ b/pkg/clients/listers/api/v1alpha1/roleassignment.go @@ -0,0 +1,70 @@ +/* +Copyright The ORC Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// RoleAssignmentLister helps list RoleAssignments. +// All objects returned here must be treated as read-only. +type RoleAssignmentLister interface { + // List lists all RoleAssignments in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.RoleAssignment, err error) + // RoleAssignments returns an object that can list and get RoleAssignments. + RoleAssignments(namespace string) RoleAssignmentNamespaceLister + RoleAssignmentListerExpansion +} + +// roleAssignmentLister implements the RoleAssignmentLister interface. +type roleAssignmentLister struct { + listers.ResourceIndexer[*apiv1alpha1.RoleAssignment] +} + +// NewRoleAssignmentLister returns a new RoleAssignmentLister. +func NewRoleAssignmentLister(indexer cache.Indexer) RoleAssignmentLister { + return &roleAssignmentLister{listers.New[*apiv1alpha1.RoleAssignment](indexer, apiv1alpha1.Resource("roleassignment"))} +} + +// RoleAssignments returns an object that can list and get RoleAssignments. +func (s *roleAssignmentLister) RoleAssignments(namespace string) RoleAssignmentNamespaceLister { + return roleAssignmentNamespaceLister{listers.NewNamespaced[*apiv1alpha1.RoleAssignment](s.ResourceIndexer, namespace)} +} + +// RoleAssignmentNamespaceLister helps list and get RoleAssignments. +// All objects returned here must be treated as read-only. +type RoleAssignmentNamespaceLister interface { + // List lists all RoleAssignments in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apiv1alpha1.RoleAssignment, err error) + // Get retrieves the RoleAssignment from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apiv1alpha1.RoleAssignment, error) + RoleAssignmentNamespaceListerExpansion +} + +// roleAssignmentNamespaceLister implements the RoleAssignmentNamespaceLister +// interface. +type roleAssignmentNamespaceLister struct { + listers.ResourceIndexer[*apiv1alpha1.RoleAssignment] +} diff --git a/test/apivalidations/roleassignment_test.go b/test/apivalidations/roleassignment_test.go index 64efff717..7a40f65a2 100644 --- a/test/apivalidations/roleassignment_test.go +++ b/test/apivalidations/roleassignment_test.go @@ -79,7 +79,7 @@ var _ = Describe("ORC RoleAssignment API validations", func() { p.Spec.WithImport(applyconfigv1alpha1.RoleAssignmentImport().WithFilter(applyconfigv1alpha1.RoleAssignmentFilter())) }, applyValidFilter: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { - p.Spec.WithImport(applyconfigv1alpha1.RoleAssignmentImport().WithFilter(applyconfigv1alpha1.RoleAssignmentFilter().WithName("foo"))) + p.Spec.WithImport(applyconfigv1alpha1.RoleAssignmentImport().WithFilter(applyconfigv1alpha1.RoleAssignmentFilter().WithRoleRef("admin"))) }, applyManaged: func(p *applyconfigv1alpha1.RoleAssignmentApplyConfiguration) { p.Spec.WithManagementPolicy(orcv1alpha1.ManagementPolicyManaged) diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 12e26692e..9522d11db 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -23,6 +23,7 @@ Package v1alpha1 contains API Schema definitions for the openstack v1alpha1 API - [Port](#port) - [Project](#project) - [Role](#role) +- [RoleAssignment](#roleassignment) - [Router](#router) - [RouterInterface](#routerinterface) - [SecurityGroup](#securitygroup) @@ -512,6 +513,7 @@ _Appears in:_ - [NetworkSpec](#networkspec) - [PortSpec](#portspec) - [ProjectSpec](#projectspec) +- [RoleAssignmentSpec](#roleassignmentspec) - [RoleSpec](#rolespec) - [RouterSpec](#routerspec) - [SecurityGroupSpec](#securitygroupspec) @@ -2156,6 +2158,8 @@ _Appears in:_ - [PortResourceSpec](#portresourcespec) - [ProjectFilter](#projectfilter) - [ProjectResourceSpec](#projectresourcespec) +- [RoleAssignmentFilter](#roleassignmentfilter) +- [RoleAssignmentResourceSpec](#roleassignmentresourcespec) - [RoleFilter](#rolefilter) - [RoleResourceSpec](#roleresourcespec) - [RouterFilter](#routerfilter) @@ -2230,6 +2234,7 @@ _Appears in:_ - [NetworkSpec](#networkspec) - [PortSpec](#portspec) - [ProjectSpec](#projectspec) +- [RoleAssignmentSpec](#roleassignmentspec) - [RoleSpec](#rolespec) - [RouterSpec](#routerspec) - [SecurityGroupSpec](#securitygroupspec) @@ -2269,6 +2274,7 @@ _Appears in:_ - [NetworkSpec](#networkspec) - [PortSpec](#portspec) - [ProjectSpec](#projectspec) +- [RoleAssignmentSpec](#roleassignmentspec) - [RoleSpec](#rolespec) - [RouterSpec](#routerspec) - [SecurityGroupSpec](#securitygroupspec) @@ -3046,6 +3052,149 @@ Role is the Schema for an ORC resource. | `status` _[RoleStatus](#rolestatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| +#### RoleAssignment + + + +RoleAssignment is the Schema for an ORC resource. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `openstack.k-orc.cloud/v1alpha1` | | | +| `kind` _string_ | `RoleAssignment` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | Optional: \{\}
| +| `spec` _[RoleAssignmentSpec](#roleassignmentspec)_ | spec specifies the desired state of the resource. | | Required: \{\}
| +| `status` _[RoleAssignmentStatus](#roleassignmentstatus)_ | status defines the observed state of the resource. | | Optional: \{\}
| + + +#### RoleAssignmentFilter + + + +RoleAssignmentFilter defines import filter criteria for existing role assignments. + +_Validation:_ +- MinProperties: 1 + +_Appears in:_ +- [RoleAssignmentImport](#roleassignmentimport) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `roleRef` _[KubernetesNameRef](#kubernetesnameref)_ | roleRef filters by the referenced Role. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `userRef` _[KubernetesNameRef](#kubernetesnameref)_ | userRef filters by the referenced User. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `groupRef` _[KubernetesNameRef](#kubernetesnameref)_ | groupRef filters by the referenced Group. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef filters by the referenced Project scope. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef filters by the referenced Domain scope. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| + + +#### RoleAssignmentImport + + + +RoleAssignmentImport specifies an existing resource which will be imported instead of +creating a new one + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [RoleAssignmentSpec](#roleassignmentspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `id` _string_ | id contains the unique identifier of an existing OpenStack resource. Note
that when specifying an import by ID, the resource MUST already exist.
The ORC object will enter an error state if the resource does not exist. | | Format: uuid
MaxLength: 36
Optional: \{\}
| +| `filter` _[RoleAssignmentFilter](#roleassignmentfilter)_ | filter contains a resource query which is expected to return a single
result. The controller will continue to retry if filter returns no
results. If filter returns multiple results the controller will set an
error state and will not continue to retry. | | MinProperties: 1
Optional: \{\}
| + + +#### RoleAssignmentResourceSpec + + + +RoleAssignmentResourceSpec defines the desired role assignment. +A role assignment grants a role to a user or group on a project or domain. +Role assignments are immutable once created and identified by the combination +of (role, actor, scope) rather than a separate ID. + + + +_Appears in:_ +- [RoleAssignmentSpec](#roleassignmentspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `roleRef` _[KubernetesNameRef](#kubernetesnameref)_ | roleRef references the Role being assigned. | | MaxLength: 253
MinLength: 1
Required: \{\}
| +| `userRef` _[KubernetesNameRef](#kubernetesnameref)_ | userRef references the User receiving the role assignment.
Exactly one of userRef or groupRef must be specified. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `groupRef` _[KubernetesNameRef](#kubernetesnameref)_ | groupRef references the Group receiving the role assignment.
Exactly one of userRef or groupRef must be specified. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef references the Project scope for the assignment.
Exactly one of projectRef or domainRef must be specified. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| +| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef references the Domain scope for the assignment.
Exactly one of projectRef or domainRef must be specified. | | MaxLength: 253
MinLength: 1
Optional: \{\}
| + + +#### RoleAssignmentResourceStatus + + + +RoleAssignmentResourceStatus represents the observed state of the role assignment. +Note: Role assignments do not have a unique ID in OpenStack - they are identified +by the combination of role, actor (user/group), and scope (project/domain). + + + +_Appears in:_ +- [RoleAssignmentStatus](#roleassignmentstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `roleID` _string_ | roleID is the OpenStack ID of the assigned role. | | MaxLength: 1024
Optional: \{\}
| +| `userID` _string_ | userID is the OpenStack ID of the user (if actorType is User). | | MaxLength: 1024
Optional: \{\}
| +| `groupID` _string_ | groupID is the OpenStack ID of the group (if actorType is Group). | | MaxLength: 1024
Optional: \{\}
| +| `projectID` _string_ | projectID is the OpenStack ID of the project scope (if scopeType is Project). | | MaxLength: 1024
Optional: \{\}
| +| `domainID` _string_ | domainID is the OpenStack ID of the domain scope (if scopeType is Domain). | | MaxLength: 1024
Optional: \{\}
| + + +#### RoleAssignmentSpec + + + +RoleAssignmentSpec defines the desired state of an ORC object. + + + +_Appears in:_ +- [RoleAssignment](#roleassignment) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `import` _[RoleAssignmentImport](#roleassignmentimport)_ | import refers to an existing OpenStack resource which will be imported instead of
creating a new one. | | MaxProperties: 1
MinProperties: 1
Optional: \{\}
| +| `resource` _[RoleAssignmentResourceSpec](#roleassignmentresourcespec)_ | resource specifies the desired state of the resource.
resource may not be specified if the management policy is `unmanaged`.
resource must be specified if the management policy is `managed`. | | Optional: \{\}
| +| `managementPolicy` _[ManagementPolicy](#managementpolicy)_ | managementPolicy defines how ORC will treat the object. Valid values are
`managed`: ORC will create, update, and delete the resource; `unmanaged`:
ORC will import an existing resource, and will not apply updates to it or
delete it. | managed | Enum: [managed unmanaged]
Optional: \{\}
| +| `managedOptions` _[ManagedOptions](#managedoptions)_ | managedOptions specifies options which may be applied to managed objects. | | Optional: \{\}
| +| `cloudCredentialsRef` _[CloudCredentialsReference](#cloudcredentialsreference)_ | cloudCredentialsRef points to a secret containing OpenStack credentials | | Required: \{\}
| + + +#### RoleAssignmentStatus + + + +RoleAssignmentStatus defines the observed state of an ORC resource. + + + +_Appears in:_ +- [RoleAssignment](#roleassignment) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta) array_ | conditions represents the observed status of the object.
Known .status.conditions.type are: "Available", "Progressing"
Available represents the availability of the OpenStack resource. If it is
true then the resource is ready for use.
Progressing indicates whether the controller is still attempting to
reconcile the current state of the OpenStack resource to the desired
state. Progressing will be False either because the desired state has
been achieved, or because some terminal error prevents it from ever being
achieved and the controller is no longer attempting to reconcile. If
Progressing is True, an observer waiting on the resource should continue
to wait. | | MaxItems: 32
Optional: \{\}
| +| `id` _string_ | id is the unique identifier of the OpenStack resource. | | MaxLength: 1024
Optional: \{\}
| +| `resource` _[RoleAssignmentResourceStatus](#roleassignmentresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | Optional: \{\}
| + + #### RoleFilter