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
17 changes: 17 additions & 0 deletions api/v1alpha1/user_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ type UserResourceSpec struct {
// enabled defines whether a user is enabled or disabled
// +optional
Enabled *bool `json:"enabled,omitempty"`

// password is the password set for the user
// +optional
Password *PasswordSpec `json:"password,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we keep it simple and pass the SecretRef directly here? Any advantage of using a struct?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on this, not sure there is a point in warping it in an additional struct

}

// +kubebuilder:validation:MinProperties:=1
// +kubebuilder:validation:MaxProperties:=1
type PasswordSpec struct {
// secretRef is a reference to a Secret containing the password for this user.
// +optional
SecretRef *KubernetesNameRef `json:"secretRef,omitempty"`
}

// UserFilter defines an existing resource by its properties
Expand Down Expand Up @@ -81,4 +93,9 @@ type UserResourceStatus struct {
// enabled defines whether a user is enabled or disabled
// +optional
Enabled bool `json:"enabled,omitempty"`

// passwordExpiresAt filters the response based on expriing passwords.
// +kubebuilder:validation:MaxLength:=255
// +optional
PasswordExpiresAt string `json:"passwordExpiresAt,omitempty"`
}
25 changes: 25 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

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

35 changes: 35 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_users.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,18 @@ spec:
minLength: 1
pattern: ^[^,]+$
type: string
password:
description: password is the password set for the user
maxProperties: 1
minProperties: 1
properties:
secretRef:
description: secretRef is a reference to a Secret containing
the password for this user.
maxLength: 253
minLength: 1
type: string
type: object
type: object
required:
- cloudCredentialsRef
Expand Down Expand Up @@ -313,6 +325,11 @@ spec:
not be unique.
maxLength: 1024
type: string
passwordExpiresAt:
description: passwordExpiresAt filters the response based on expriing
passwords.
maxLength: 255
type: string
type: object
type: object
required:
Expand Down
40 changes: 38 additions & 2 deletions config/samples/openstack_v1alpha1_user.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,48 @@
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Domain
metadata:
name: user-sample
spec:
cloudCredentialsRef:
cloudName: devstack-admin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a change during development. Let's stick with openstack-admin for consistency. Wdyt?

secretName: openstack-clouds
managementPolicy: managed
resource: {}
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Project
metadata:
name: user-sample
spec:
cloudCredentialsRef:
cloudName: devstack-admin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here.

secretName: openstack-clouds
managementPolicy: managed
resource: {}
---
apiVersion: v1
kind: Secret
metadata:
name: user-sample
type: Opaque
stringData:
password: "TestPassword"
Comment on lines +24 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
apiVersion: v1
kind: Secret
metadata:
name: user-sample
type: Opaque
stringData:
password: "TestPassword"
apiVersion: v1
kind: Secret
metadata:
name: user-sample
type: Opaque
stringData:
password: "TestPassword"

A few blank spaces at the beginning. Let's remove them for better indentation.

---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: User
metadata:
name: user-sample
spec:
cloudCredentialsRef:
cloudName: openstack-admin
cloudName: devstack-admin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here too :).

secretName: openstack-clouds
managementPolicy: managed
resource:
description: Sample User
name: user-sample
description: User sample
domainRef: user-sample
defaultProjectRef: user-sample
enabled: true
password:
secretRef: user-sample
22 changes: 22 additions & 0 deletions internal/controllers/user/actuator.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,27 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT
defaultProjectID = ptr.Deref(project.Status.ID, "")
}
}

var password string
if resource.Password != nil {
secret, secretReconcileStatus := dependency.FetchDependency(
ctx, actuator.k8sClient, obj.Namespace,
resource.Password.SecretRef, "Secret",
func(*corev1.Secret) bool { return true },
)
reconcileStatus = reconcileStatus.WithReconcileStatus(secretReconcileStatus)
if secretReconcileStatus == nil {
var ok bool
passwordBytes, ok := secret.Data["password"]
if !ok {
reconcileStatus = reconcileStatus.WithReconcileStatus(
progress.NewReconcileStatus().WithProgressMessage("Password secret does not contain \"password\" key"))
} else {
password = string(passwordBytes)
}
}
}

if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
return nil, reconcileStatus
}
Expand All @@ -144,6 +165,7 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT
DomainID: domainID,
Enabled: resource.Enabled,
DefaultProjectID: defaultProjectID,
Password: password,
}

osResource, err := actuator.osClient.CreateUser(ctx, createOpts)
Expand Down
22 changes: 21 additions & 1 deletion internal/controllers/user/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"errors"

corev1 "k8s.io/api/core/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/controller"
Expand Down Expand Up @@ -86,6 +87,17 @@ var domainImportDependency = dependency.NewDependency[*orcv1alpha1.UserList, *or
},
)

var passwordDependency = dependency.NewDependency[*orcv1alpha1.UserList, *corev1.Secret](
"spec.resource.password.secretRef",
func(user *orcv1alpha1.User) []string {
resource := user.Spec.Resource
if resource == nil || resource.Password == nil || resource.Password.SecretRef == nil {
return nil
}
return []string{string(*resource.Password.SecretRef)}
},
)

// SetupWithManager sets up the controller with the Manager.
func (c userReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
log := ctrl.LoggerFrom(ctx)
Expand All @@ -106,8 +118,14 @@ func (c userReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctr
return err
}

passwordWatchEventHandler, err := passwordDependency.WatchEventHandler(log, k8sClient)
if err != nil {
return err
}

builder := ctrl.NewControllerManagedBy(mgr).
WithOptions(options).
For(&orcv1alpha1.User{}).
Watches(&orcv1alpha1.Domain{}, domainWatchEventHandler,
builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})),
).
Expand All @@ -118,12 +136,14 @@ func (c userReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctr
Watches(&orcv1alpha1.Domain{}, domainImportWatchEventHandler,
builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})),
).
For(&orcv1alpha1.User{})
// General watch on secrets.
Watches(&corev1.Secret{}, passwordWatchEventHandler)

if err := errors.Join(
domainDependency.AddToManager(ctx, mgr),
projectDependency.AddToManager(ctx, mgr),
domainImportDependency.AddToManager(ctx, mgr),
passwordDependency.AddToManager(ctx, mgr),
credentialsDependency.AddToManager(ctx, mgr),
credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency),
); err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ spec:
secretName: openstack-clouds
managementPolicy: managed
resource: {}
---
apiVersion: v1
kind: Secret
metadata:
name: user-create-full
type: Opaque
stringData:
password: "TestPassword"
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: User
Expand All @@ -35,4 +43,6 @@ spec:
description: User from "create full" test
domainRef: user-create-full
defaultProjectRef: user-create-full
enabled: true
enabled: true
password:
secretRef: user-create-full
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ assertAll:
- celExpr: "!has(user.status.resource.description)"
- celExpr: "user.status.resource.domainID == 'default'"
- celExpr: "!has(user.status.resource.defaultProjectID)"
- celExpr: "!has(user.status.resource.passwordExpiresAt)"

Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,12 @@ spec:
cloudName: openstack-admin
secretName: openstack-clouds
managementPolicy: managed
resource: {}
resource: {}
---
apiVersion: v1
kind: Secret
metadata:
name: user-create-full
type: Opaque
stringData:
password: "TestPassword"
Comment on lines +30 to +36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably you also need to add a new user-dependency-no-password-secret or something like that at 00-create-resources-missing-deps.yaml to properly test the dependency with this secret, and complete the deletion later as well.

Loading
Loading