Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions api/v1alpha1/group_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ type GroupResourceSpec struct {
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="domainRef is immutable"
// +orc:kustomize:ref=Domain
DomainRef *KubernetesNameRef `json:"domainRef,omitempty"`

// userRefs contains ORC Users that belong to this group.
// +kubebuilder:validation:MaxItems:=1024
// +listType=set
// +optional
// +orc:kustomize:ref=User
UserRefs []KubernetesNameRef `json:"userRefs,omitempty"`
}

// GroupFilter defines an existing resource by its properties
Expand Down Expand Up @@ -65,4 +72,11 @@ type GroupResourceStatus struct {
// +kubebuilder:validation:MaxLength=1024
// +optional
DomainID string `json:"domainID,omitempty"`

// userIDs lists the IDs of the Users in this group.
// +kubebuilder:validation:MaxItems=1024
// +kubebuilder:validation:items:MaxLength=1024
// +optional
// +listType=set
UserIDs []string `json:"userIDs,omitempty"`
}
12 changes: 11 additions & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 40 additions & 0 deletions cmd/models-schema/zz_generated.openapi.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions config/crd/bases/openstack.k-orc.cloud_groups.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions config/samples/openstack_v1alpha1_group.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ spec:
resource:
description: Sample Group
domainRef: group-sample
userRefs:
- user-sample
2 changes: 2 additions & 0 deletions examples/components/kustomizeconfig/kustomizeconfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ nameReference:
path: spec/import/filter/userRef
- kind: ApplicationCredential
path: spec/resource/userRef
- kind: Group
path: spec/resource/userRefs[]
- kind: RoleAssignment
path: spec/import/filter/userRef
- kind: RoleAssignment
Expand Down
75 changes: 74 additions & 1 deletion internal/controllers/group/actuator.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ package group
import (
"context"
"iter"
"slices"

"github.com/gophercloud/gophercloud/v2/openstack/identity/v3/groups"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand All @@ -37,7 +39,7 @@ import (

// OpenStack resource types
type (
osResourceT = groups.Group
osResourceT = osclients.GroupExt

createResourceActuator = interfaces.CreateResourceActuator[orcObjectPT, orcObjectT, filterT, osResourceT]
deleteResourceActuator = interfaces.DeleteResourceActuator[orcObjectPT, orcObjectT, osResourceT]
Expand Down Expand Up @@ -228,9 +230,80 @@ func handleDescriptionUpdate(updateOpts *groups.UpdateOpts, resource *resourceSp
}
}

func (actuator groupActuator) reconcileUserRefs(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus {
log := ctrl.LoggerFrom(ctx)

orcUsers, usersDepRS := userDependency.RequireDependencies(
ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable,
)

reconcileStatus := progress.NewReconcileStatus().WithReconcileStatus(usersDepRS)
if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
return reconcileStatus
}

// This may remove users that were added to the group out-of-band.
toAdd, toDelete := handleUserRefsUpdate(orcUsers, osResource.UserIDs)

log.V(logging.Debug).Info("user ref changes", "toAdd", toAdd, "toDelete", toDelete)
for existingUserID := range toDelete {
if err := actuator.osClient.RemoveUser(ctx, osResource.ID, existingUserID); err != nil {
Comment thread
chenwng marked this conversation as resolved.
if !orcerrors.IsRetryable(err) {
err = orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, "error removing user from group: "+err.Error(), err)
}
return progress.WrapError(err)
}
log.V(logging.Debug).Info("user removed from group", "userID", existingUserID)
}

for expectedUserID := range toAdd {
if err := actuator.osClient.AddUser(ctx, osResource.ID, expectedUserID); err != nil {
if !orcerrors.IsRetryable(err) {
err = orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, "error adding user to group: "+err.Error(), err)
}
return progress.WrapError(err)
}
log.V(logging.Debug).Info("user added to group", "userID", expectedUserID)
}

if len(toAdd) > 0 || len(toDelete) > 0 {
osUsers, err := actuator.osClient.ListUsers(ctx, osResource.ID)
if err != nil {
if !orcerrors.IsRetryable(err) {
err = orcerrors.Terminal(orcv1alpha1.ConditionReasonUnrecoverableError, "error listing users in group: "+err.Error(), err)
}
return progress.WrapError(err)
}

userIDs := make([]string, 0, len(osUsers))
for _, u := range osUsers {
userIDs = append(userIDs, u.ID)
}
slices.Sort(userIDs)
osResource.UserIDs = userIDs
}

return nil
}

func handleUserRefsUpdate(orcUsers map[string]*orcv1alpha1.User, osUsers []string) (sets.Set[string], sets.Set[string]) {
expectedUsersMap := sets.New[string]()
for _, u := range orcUsers {
expectedUsersMap.Insert(ptr.Deref(u.Status.ID, ""))
}

existingUsersMap := sets.New[string]()
for _, u := range osUsers {
existingUsersMap.Insert(u)
}

return expectedUsersMap.Difference(existingUsersMap), existingUsersMap.Difference(expectedUsersMap)
}

func (actuator groupActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) {
return []resourceReconciler{
actuator.updateResource,
actuator.reconcileUserRefs,
}, nil
}

Expand Down
Loading
Loading