diff --git a/api/v1alpha1/user_types.go b/api/v1alpha1/user_types.go
index faf317660..ddccb96e2 100644
--- a/api/v1alpha1/user_types.go
+++ b/api/v1alpha1/user_types.go
@@ -42,6 +42,12 @@ type UserResourceSpec struct {
// enabled defines whether a user is enabled or disabled
// +optional
Enabled *bool `json:"enabled,omitempty"`
+
+ // passwordRef is a reference to a Secret containing the password
+ // for this user. The Secret must contain a key named "password".
+ // +required
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="passwordRef is immutable"
+ PasswordRef KubernetesNameRef `json:"passwordRef,omitempty"`
}
// UserFilter defines an existing resource by its properties
@@ -81,4 +87,9 @@ type UserResourceStatus struct {
// enabled defines whether a user is enabled or disabled
// +optional
Enabled bool `json:"enabled,omitempty"`
+
+ // passwordExpiresAt is the timestamp at which the user's password expires.
+ // +kubebuilder:validation:MaxLength:=1024
+ // +optional
+ PasswordExpiresAt string `json:"passwordExpiresAt,omitempty"`
}
diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go
index 9250f4e36..e13e9899b 100644
--- a/cmd/models-schema/zz_generated.openapi.go
+++ b/cmd/models-schema/zz_generated.openapi.go
@@ -11393,7 +11393,15 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceSpec(ref c
Format: "",
},
},
+ "passwordRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "passwordRef is a reference to a Secret containing the password for this user. The Secret must contain a key named \"password\".",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
},
+ Required: []string{"passwordRef"},
},
},
}
@@ -11441,6 +11449,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceStatus(ref
Format: "",
},
},
+ "passwordExpiresAt": {
+ SchemaProps: spec.SchemaProps{
+ Description: "passwordExpiresAt is the timestamp at which the user's password expires.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
},
},
},
diff --git a/config/crd/bases/openstack.k-orc.cloud_users.yaml b/config/crd/bases/openstack.k-orc.cloud_users.yaml
index 2b7480257..201bb5eba 100644
--- a/config/crd/bases/openstack.k-orc.cloud_users.yaml
+++ b/config/crd/bases/openstack.k-orc.cloud_users.yaml
@@ -186,6 +186,18 @@ spec:
minLength: 1
pattern: ^[^,]+$
type: string
+ passwordRef:
+ description: |-
+ passwordRef is a reference to a Secret containing the password
+ for this user. The Secret must contain a key named "password".
+ maxLength: 253
+ minLength: 1
+ type: string
+ x-kubernetes-validations:
+ - message: passwordRef is immutable
+ rule: self == oldSelf
+ required:
+ - passwordRef
type: object
required:
- cloudCredentialsRef
@@ -313,6 +325,11 @@ spec:
not be unique.
maxLength: 1024
type: string
+ passwordExpiresAt:
+ description: passwordExpiresAt is the timestamp at which the user's
+ password expires.
+ maxLength: 1024
+ type: string
type: object
type: object
required:
diff --git a/config/samples/openstack_v1alpha1_user.yaml b/config/samples/openstack_v1alpha1_user.yaml
index 09067e614..2e6371f2f 100644
--- a/config/samples/openstack_v1alpha1_user.yaml
+++ b/config/samples/openstack_v1alpha1_user.yaml
@@ -1,5 +1,35 @@
---
apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Domain
+metadata:
+ name: user-sample
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Project
+metadata:
+ name: user-sample
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource: {}
+---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: user-sample
+type: Opaque
+stringData:
+ password: "TestPassword"
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
kind: User
metadata:
name: user-sample
@@ -9,4 +39,9 @@ spec:
secretName: openstack-clouds
managementPolicy: managed
resource:
- description: Sample User
+ name: user-sample
+ description: User sample
+ domainRef: user-sample
+ defaultProjectRef: user-sample
+ enabled: true
+ passwordRef: user-sample
diff --git a/internal/controllers/user/actuator.go b/internal/controllers/user/actuator.go
index 391205112..4b4683fe5 100644
--- a/internal/controllers/user/actuator.go
+++ b/internal/controllers/user/actuator.go
@@ -135,6 +135,26 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT
defaultProjectID = ptr.Deref(project.Status.ID, "")
}
}
+
+ var password string
+ {
+ secret, secretReconcileStatus := dependency.FetchDependency(
+ ctx, actuator.k8sClient, obj.Namespace,
+ &resource.PasswordRef, "Secret",
+ func(*corev1.Secret) bool { return true },
+ )
+ reconcileStatus = reconcileStatus.WithReconcileStatus(secretReconcileStatus)
+ if secretReconcileStatus == nil {
+ 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
}
@@ -144,6 +164,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)
diff --git a/internal/controllers/user/controller.go b/internal/controllers/user/controller.go
index 4e432c0c7..f69818726 100644
--- a/internal/controllers/user/controller.go
+++ b/internal/controllers/user/controller.go
@@ -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"
@@ -86,6 +87,17 @@ var domainImportDependency = dependency.NewDependency[*orcv1alpha1.UserList, *or
},
)
+var passwordDependency = dependency.NewDependency[*orcv1alpha1.UserList, *corev1.Secret](
+ "spec.resource.passwordRef",
+ func(user *orcv1alpha1.User) []string {
+ resource := user.Spec.Resource
+ if resource == nil {
+ return nil
+ }
+ return []string{string(resource.PasswordRef)}
+ },
+)
+
// 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)
@@ -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{})),
).
@@ -118,12 +136,20 @@ func (c userReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctr
Watches(&orcv1alpha1.Domain{}, domainImportWatchEventHandler,
builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})),
).
- For(&orcv1alpha1.User{})
+ // XXX: This is a general watch on secrets. A general watch on secrets
+ // is undesirable because:
+ // - It requires problematic RBAC
+ // - Secrets are arbitrarily large, and we don't want to cache their contents
+ //
+ // These will require separate solutions. For the latter we should
+ // probably use a MetadataOnly 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 {
diff --git a/internal/controllers/user/status.go b/internal/controllers/user/status.go
index 0d0f8da51..e412d66fd 100644
--- a/internal/controllers/user/status.go
+++ b/internal/controllers/user/status.go
@@ -17,6 +17,8 @@ limitations under the License.
package user
import (
+ "time"
+
"github.com/go-logr/logr"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -62,5 +64,9 @@ func (userStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResou
resourceStatus.WithDefaultProjectID(osResource.DefaultProjectID)
}
+ if !osResource.PasswordExpiresAt.IsZero() {
+ resourceStatus.WithPasswordExpiresAt(osResource.PasswordExpiresAt.Format(time.RFC3339))
+ }
+
statusApply.WithResource(resourceStatus)
}
diff --git a/internal/controllers/user/tests/user-create-full/00-assert.yaml b/internal/controllers/user/tests/user-create-full/00-assert.yaml
index 0e9bd2a17..b91d5b2dd 100644
--- a/internal/controllers/user/tests/user-create-full/00-assert.yaml
+++ b/internal/controllers/user/tests/user-create-full/00-assert.yaml
@@ -35,3 +35,5 @@ assertAll:
- celExpr: "user.status.id != ''"
- celExpr: "user.status.resource.domainID == domain.status.id"
- celExpr: "user.status.resource.defaultProjectID == project.status.id"
+ # passwordExpiresAt depends on the Keystone security_compliance
+ # configuration and is not asserted here.
diff --git a/internal/controllers/user/tests/user-create-full/00-create-resource.yaml b/internal/controllers/user/tests/user-create-full/00-create-resource.yaml
index 4df449bda..53d6869bb 100644
--- a/internal/controllers/user/tests/user-create-full/00-create-resource.yaml
+++ b/internal/controllers/user/tests/user-create-full/00-create-resource.yaml
@@ -21,6 +21,14 @@ spec:
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
metadata:
@@ -35,4 +43,5 @@ spec:
description: User from "create full" test
domainRef: user-create-full
defaultProjectRef: user-create-full
- enabled: true
\ No newline at end of file
+ enabled: true
+ passwordRef: user-create-full
diff --git a/internal/controllers/user/tests/user-create-minimal/00-assert.yaml b/internal/controllers/user/tests/user-create-minimal/00-assert.yaml
index 950d429bd..f8ffcb148 100644
--- a/internal/controllers/user/tests/user-create-minimal/00-assert.yaml
+++ b/internal/controllers/user/tests/user-create-minimal/00-assert.yaml
@@ -27,3 +27,6 @@ assertAll:
- celExpr: "!has(user.status.resource.description)"
- celExpr: "user.status.resource.domainID == 'default'"
- celExpr: "!has(user.status.resource.defaultProjectID)"
+ # passwordExpiresAt depends on the Keystone security_compliance
+ # configuration and is not asserted here.
+
diff --git a/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml b/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml
index c3d2147bf..72545e48c 100644
--- a/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml
+++ b/internal/controllers/user/tests/user-create-minimal/00-create-resource.yaml
@@ -1,4 +1,12 @@
---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: user-create-minimal
+type: Opaque
+stringData:
+ password: "TestPassword"
+---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: User
metadata:
@@ -8,4 +16,5 @@ spec:
cloudName: openstack-admin
secretName: openstack-clouds
managementPolicy: managed
- resource: {}
\ No newline at end of file
+ resource:
+ passwordRef: user-create-minimal
\ No newline at end of file
diff --git a/internal/controllers/user/tests/user-dependency/00-assert.yaml b/internal/controllers/user/tests/user-dependency/00-assert.yaml
index 388f70d82..f45da298d 100644
--- a/internal/controllers/user/tests/user-dependency/00-assert.yaml
+++ b/internal/controllers/user/tests/user-dependency/00-assert.yaml
@@ -42,4 +42,19 @@ status:
- type: Progressing
message: Waiting for Project/user-dependency to be created
status: "True"
+ reason: Progressing
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: User
+metadata:
+ name: user-dependency-no-password
+status:
+ conditions:
+ - type: Available
+ message: Waiting for Secret/user-dependency-password to be created
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: Waiting for Secret/user-dependency-password to be created
+ status: "True"
reason: Progressing
\ No newline at end of file
diff --git a/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml
index c5b59dafa..c06e90511 100644
--- a/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml
+++ b/internal/controllers/user/tests/user-dependency/00-create-resources-missing-deps.yaml
@@ -10,6 +10,7 @@ spec:
managementPolicy: managed
resource:
domainRef: user-dependency
+ passwordRef: user-dependency-password-existing
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: User
@@ -22,6 +23,7 @@ spec:
managementPolicy: managed
resource:
defaultProjectRef: user-dependency
+ passwordRef: user-dependency-password-existing
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: User
@@ -32,4 +34,17 @@ spec:
cloudName: openstack-admin
secretName: user-dependency
managementPolicy: managed
- resource: {}
\ No newline at end of file
+ resource:
+ passwordRef: user-dependency-password-existing
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: User
+metadata:
+ name: user-dependency-no-password
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ passwordRef: user-dependency-password
\ No newline at end of file
diff --git a/internal/controllers/user/tests/user-dependency/00-secret.yaml b/internal/controllers/user/tests/user-dependency/00-secret.yaml
index 082860af5..1e9d5d5fb 100644
--- a/internal/controllers/user/tests/user-dependency/00-secret.yaml
+++ b/internal/controllers/user/tests/user-dependency/00-secret.yaml
@@ -3,4 +3,12 @@ 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
\ No newline at end of file
+ namespaced: true
+---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: user-dependency-password-existing
+type: Opaque
+stringData:
+ password: "TestPassword"
\ No newline at end of file
diff --git a/internal/controllers/user/tests/user-dependency/01-assert.yaml b/internal/controllers/user/tests/user-dependency/01-assert.yaml
index 30bfee417..83de36848 100644
--- a/internal/controllers/user/tests/user-dependency/01-assert.yaml
+++ b/internal/controllers/user/tests/user-dependency/01-assert.yaml
@@ -33,6 +33,21 @@ apiVersion: openstack.k-orc.cloud/v1alpha1
kind: User
metadata:
name: user-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: User
+metadata:
+ name: user-dependency-no-password
status:
conditions:
- type: Available
diff --git a/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml b/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml
index 4a292db93..2823b9d99 100644
--- a/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml
+++ b/internal/controllers/user/tests/user-dependency/01-create-dependencies.yaml
@@ -25,4 +25,12 @@ spec:
cloudName: openstack-admin
secretName: openstack-clouds
managementPolicy: managed
- resource: {}
\ No newline at end of file
+ resource: {}
+---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: user-dependency-password
+type: Opaque
+stringData:
+ password: "TestPassword"
diff --git a/internal/controllers/user/tests/user-dependency/04-delete-resources.yaml b/internal/controllers/user/tests/user-dependency/04-delete-resources.yaml
index 8054e0ebc..e0787a012 100644
--- a/internal/controllers/user/tests/user-dependency/04-delete-resources.yaml
+++ b/internal/controllers/user/tests/user-dependency/04-delete-resources.yaml
@@ -10,4 +10,7 @@ delete:
name: user-dependency-no-domain
- apiVersion: openstack.k-orc.cloud/v1alpha1
kind: User
- name: user-dependency-no-project
\ No newline at end of file
+ name: user-dependency-no-project
+- apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: User
+ name: user-dependency-no-password
\ No newline at end of file
diff --git a/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml b/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml
index 0681c805b..6001315f4 100644
--- a/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml
+++ b/internal/controllers/user/tests/user-import-dependency/00-import-resource.yaml
@@ -1,4 +1,12 @@
---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: user-import-dependency-password
+type: Opaque
+stringData:
+ password: "TestPassword"
+---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Domain
metadata:
diff --git a/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml b/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml
index 7154af7ba..48536462a 100644
--- a/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml
+++ b/internal/controllers/user/tests/user-import-dependency/01-create-trap-resource.yaml
@@ -21,4 +21,5 @@ spec:
secretName: openstack-clouds
managementPolicy: managed
resource:
- domainRef: user-import-dependency-not-this-one
\ No newline at end of file
+ domainRef: user-import-dependency-not-this-one
+ passwordRef: user-import-dependency-password
\ No newline at end of file
diff --git a/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml b/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml
index ea64cab75..51c32bb06 100644
--- a/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml
+++ b/internal/controllers/user/tests/user-import-dependency/02-create-resource.yaml
@@ -20,4 +20,5 @@ spec:
secretName: openstack-clouds
managementPolicy: managed
resource:
- domainRef: user-import-dependency-external
\ No newline at end of file
+ domainRef: user-import-dependency-external
+ passwordRef: user-import-dependency-password
\ No newline at end of file
diff --git a/internal/controllers/user/tests/user-import-error/00-create-resources.yaml b/internal/controllers/user/tests/user-import-error/00-create-resources.yaml
index 6f4e6c034..10e809d1a 100644
--- a/internal/controllers/user/tests/user-import-error/00-create-resources.yaml
+++ b/internal/controllers/user/tests/user-import-error/00-create-resources.yaml
@@ -10,6 +10,14 @@ spec:
managementPolicy: managed
resource: {}
---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: user-import-error-password
+type: Opaque
+stringData:
+ password: "TestPassword"
+---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: User
metadata:
@@ -22,7 +30,7 @@ spec:
resource:
description: User from "import error" test
domainRef: user-import-error-domain
-
+ passwordRef: user-import-error-password
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: User
@@ -35,4 +43,5 @@ spec:
managementPolicy: managed
resource:
description: User from "import error" test
- domainRef: user-import-error-domain
\ No newline at end of file
+ domainRef: user-import-error-domain
+ passwordRef: user-import-error-password
\ No newline at end of file
diff --git a/internal/controllers/user/tests/user-import/00-import-resource.yaml b/internal/controllers/user/tests/user-import/00-import-resource.yaml
index d8b7199fa..a80dc427a 100644
--- a/internal/controllers/user/tests/user-import/00-import-resource.yaml
+++ b/internal/controllers/user/tests/user-import/00-import-resource.yaml
@@ -1,4 +1,12 @@
---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: user-import-password
+type: Opaque
+stringData:
+ password: "TestPassword"
+---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Domain
metadata:
diff --git a/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml b/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml
index ea393341f..18ae8d89a 100644
--- a/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml
+++ b/internal/controllers/user/tests/user-import/01-create-trap-resource.yaml
@@ -13,4 +13,5 @@ spec:
managementPolicy: managed
resource:
description: User user-import-external from "user-import" test
- domainRef: user-import-external
\ No newline at end of file
+ domainRef: user-import-external
+ passwordRef: user-import-password
\ No newline at end of file
diff --git a/internal/controllers/user/tests/user-import/02-create-resource.yaml b/internal/controllers/user/tests/user-import/02-create-resource.yaml
index 43a43ef04..ca3cb03fc 100644
--- a/internal/controllers/user/tests/user-import/02-create-resource.yaml
+++ b/internal/controllers/user/tests/user-import/02-create-resource.yaml
@@ -10,4 +10,5 @@ spec:
managementPolicy: managed
resource:
description: User user-import-external from "user-import" test
- domainRef: user-import-external
\ No newline at end of file
+ domainRef: user-import-external
+ passwordRef: user-import-password
\ No newline at end of file
diff --git a/internal/controllers/user/tests/user-update/00-assert.yaml b/internal/controllers/user/tests/user-update/00-assert.yaml
index 1cd41ff5a..c7a2749fc 100644
--- a/internal/controllers/user/tests/user-update/00-assert.yaml
+++ b/internal/controllers/user/tests/user-update/00-assert.yaml
@@ -10,7 +10,8 @@ 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)"
+ # passwordExpiresAt depends on the Keystone security_compliance
+ # configuration and is not asserted here.
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: User
diff --git a/internal/controllers/user/tests/user-update/00-minimal-resource.yaml b/internal/controllers/user/tests/user-update/00-minimal-resource.yaml
index 02960585c..d980e382a 100644
--- a/internal/controllers/user/tests/user-update/00-minimal-resource.yaml
+++ b/internal/controllers/user/tests/user-update/00-minimal-resource.yaml
@@ -1,4 +1,12 @@
---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: user-update
+type: Opaque
+stringData:
+ password: "TestPassword"
+---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: User
metadata:
@@ -8,4 +16,5 @@ spec:
cloudName: openstack-admin
secretName: openstack-clouds
managementPolicy: managed
- resource: {}
\ No newline at end of file
+ resource:
+ passwordRef: user-update
\ No newline at end of file
diff --git a/internal/controllers/user/tests/user-update/01-updated-resource.yaml b/internal/controllers/user/tests/user-update/01-updated-resource.yaml
index 4cbafe8c3..dea4f5476 100644
--- a/internal/controllers/user/tests/user-update/01-updated-resource.yaml
+++ b/internal/controllers/user/tests/user-update/01-updated-resource.yaml
@@ -7,4 +7,4 @@ spec:
resource:
name: user-update-updated
description: user-update-updated
- enabled: false
\ No newline at end of file
+ enabled: false
diff --git a/internal/controllers/user/tests/user-update/02-assert.yaml b/internal/controllers/user/tests/user-update/02-assert.yaml
index 1c70b64e1..c2c14d837 100644
--- a/internal/controllers/user/tests/user-update/02-assert.yaml
+++ b/internal/controllers/user/tests/user-update/02-assert.yaml
@@ -8,7 +8,8 @@ resourceRefs:
ref: user
assertAll:
- celExpr: "!has(user.status.resource.description)"
- - celExpr: "!has(user.status.resource.passwordExpiresAt)"
+ # passwordExpiresAt depends on the Keystone security_compliance
+ # configuration and is not asserted here.
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: User
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go
index ed4b86a2e..bd0bab7c6 100644
--- a/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go
@@ -30,6 +30,7 @@ type UserResourceSpecApplyConfiguration struct {
DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"`
DefaultProjectRef *apiv1alpha1.KubernetesNameRef `json:"defaultProjectRef,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
+ PasswordRef *apiv1alpha1.KubernetesNameRef `json:"passwordRef,omitempty"`
}
// UserResourceSpecApplyConfiguration constructs a declarative configuration of the UserResourceSpec type for use with
@@ -77,3 +78,11 @@ func (b *UserResourceSpecApplyConfiguration) WithEnabled(value bool) *UserResour
b.Enabled = &value
return b
}
+
+// WithPasswordRef sets the PasswordRef 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 PasswordRef field is set to the value of the last call.
+func (b *UserResourceSpecApplyConfiguration) WithPasswordRef(value apiv1alpha1.KubernetesNameRef) *UserResourceSpecApplyConfiguration {
+ b.PasswordRef = &value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go
index 05093ff79..c23b0b6cf 100644
--- a/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcestatus.go
@@ -21,11 +21,12 @@ package v1alpha1
// UserResourceStatusApplyConfiguration represents a declarative configuration of the UserResourceStatus type for use
// with apply.
type UserResourceStatusApplyConfiguration struct {
- Name *string `json:"name,omitempty"`
- Description *string `json:"description,omitempty"`
- DomainID *string `json:"domainID,omitempty"`
- DefaultProjectID *string `json:"defaultProjectID,omitempty"`
- Enabled *bool `json:"enabled,omitempty"`
+ Name *string `json:"name,omitempty"`
+ Description *string `json:"description,omitempty"`
+ DomainID *string `json:"domainID,omitempty"`
+ DefaultProjectID *string `json:"defaultProjectID,omitempty"`
+ Enabled *bool `json:"enabled,omitempty"`
+ PasswordExpiresAt *string `json:"passwordExpiresAt,omitempty"`
}
// UserResourceStatusApplyConfiguration constructs a declarative configuration of the UserResourceStatus type for use with
@@ -73,3 +74,11 @@ func (b *UserResourceStatusApplyConfiguration) WithEnabled(value bool) *UserReso
b.Enabled = &value
return b
}
+
+// WithPasswordExpiresAt sets the PasswordExpiresAt 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 PasswordExpiresAt field is set to the value of the last call.
+func (b *UserResourceStatusApplyConfiguration) WithPasswordExpiresAt(value string) *UserResourceStatusApplyConfiguration {
+ b.PasswordExpiresAt = &value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go
index 216436517..c641e66f4 100644
--- a/pkg/clients/applyconfiguration/internal/internal.go
+++ b/pkg/clients/applyconfiguration/internal/internal.go
@@ -3409,6 +3409,9 @@ var schemaYAML = typed.YAMLObject(`types:
- name: name
type:
scalar: string
+ - name: passwordRef
+ type:
+ scalar: string
- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserResourceStatus
map:
fields:
@@ -3427,6 +3430,9 @@ var schemaYAML = typed.YAMLObject(`types:
- name: name
type:
scalar: string
+ - name: passwordExpiresAt
+ type:
+ scalar: string
- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserSpec
map:
fields:
diff --git a/test/apivalidations/user_test.go b/test/apivalidations/user_test.go
index cc7024b9e..15057eb85 100644
--- a/test/apivalidations/user_test.go
+++ b/test/apivalidations/user_test.go
@@ -41,7 +41,8 @@ func userStub(namespace *corev1.Namespace) *orcv1alpha1.User {
}
func testUserResource() *applyconfigv1alpha1.UserResourceSpecApplyConfiguration {
- return applyconfigv1alpha1.UserResourceSpec()
+ return applyconfigv1alpha1.UserResourceSpec().
+ WithPasswordRef("user-password")
}
func baseUserPatch(user client.Object) *applyconfigv1alpha1.UserApplyConfiguration {
@@ -95,10 +96,12 @@ var _ = Describe("ORC User API validations", func() {
user := userStub(namespace)
patch := baseUserPatch(user)
patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec().
+ WithPasswordRef("user-password").
WithDomainRef("domain-a"))
Expect(applyObj(ctx, user, patch)).To(Succeed())
patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec().
+ WithPasswordRef("user-password").
WithDomainRef("domain-b"))
Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("domainRef is immutable")))
})
@@ -107,11 +110,25 @@ var _ = Describe("ORC User API validations", func() {
user := userStub(namespace)
patch := baseUserPatch(user)
patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec().
+ WithPasswordRef("user-password").
WithDefaultProjectRef("project-a"))
Expect(applyObj(ctx, user, patch)).To(Succeed())
patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec().
+ WithPasswordRef("user-password").
WithDefaultProjectRef("project-b"))
Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("defaultProjectRef is immutable")))
})
+
+ It("should have immutable passwordRef", func(ctx context.Context) {
+ user := userStub(namespace)
+ patch := baseUserPatch(user)
+ patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec().
+ WithPasswordRef("password-a"))
+ Expect(applyObj(ctx, user, patch)).To(Succeed())
+
+ patch.Spec.WithResource(applyconfigv1alpha1.UserResourceSpec().
+ WithPasswordRef("password-b"))
+ Expect(applyObj(ctx, user, patch)).To(MatchError(ContainSubstring("passwordRef is immutable")))
+ })
})
diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md
index 8d22de9fc..07dbfbe88 100644
--- a/website/docs/crd-reference.md
+++ b/website/docs/crd-reference.md
@@ -4436,6 +4436,7 @@ _Appears in:_
| `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
|
| `defaultProjectRef` _[KubernetesNameRef](#kubernetesnameref)_ | defaultProjectRef is a reference to the Default Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
Optional: \{\}
|
| `enabled` _boolean_ | enabled defines whether a user is enabled or disabled | | Optional: \{\}
|
+| `passwordRef` _[KubernetesNameRef](#kubernetesnameref)_ | passwordRef is a reference to a Secret containing the password
for this user. The Secret must contain a key named "password". | | MaxLength: 253
MinLength: 1
Required: \{\}
|
#### UserResourceStatus
@@ -4456,6 +4457,7 @@ _Appears in:_
| `domainID` _string_ | domainID is the ID of the Domain to which the resource is associated. | | MaxLength: 1024
Optional: \{\}
|
| `defaultProjectID` _string_ | defaultProjectID is the ID of the Default Project to which the user is associated with. | | MaxLength: 1024
Optional: \{\}
|
| `enabled` _boolean_ | enabled defines whether a user is enabled or disabled | | Optional: \{\}
|
+| `passwordExpiresAt` _string_ | passwordExpiresAt is the timestamp at which the user's password expires. | | MaxLength: 1024
Optional: \{\}
|
#### UserSpec