diff --git a/api/v1alpha1/user_types.go b/api/v1alpha1/user_types.go index faf317660..742f055d9 100644 --- a/api/v1alpha1/user_types.go +++ b/api/v1alpha1/user_types.go @@ -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"` +} + +// +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 @@ -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"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 2b3b6c381..e207925b3 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -2559,6 +2559,26 @@ func (in *NeutronStatusMetadata) DeepCopy() *NeutronStatusMetadata { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PasswordSpec) DeepCopyInto(out *PasswordSpec) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(KubernetesNameRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PasswordSpec. +func (in *PasswordSpec) DeepCopy() *PasswordSpec { + if in == nil { + return nil + } + out := new(PasswordSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Port) DeepCopyInto(out *Port) { *out = *in @@ -5697,6 +5717,11 @@ func (in *UserResourceSpec) DeepCopyInto(out *UserResourceSpec) { *out = new(bool) **out = **in } + if in.Password != nil { + in, out := &in.Password, &out.Password + *out = new(PasswordSpec) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserResourceSpec. diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index ee55bc2bb..e95eeb172 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -119,6 +119,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkSpec": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NetworkStatus": schema_openstack_resource_controller_v2_api_v1alpha1_NetworkStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.NeutronStatusMetadata": schema_openstack_resource_controller_v2_api_v1alpha1_NeutronStatusMetadata(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PasswordSpec": schema_openstack_resource_controller_v2_api_v1alpha1_PasswordSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Port": schema_openstack_resource_controller_v2_api_v1alpha1_Port(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortFilter": schema_openstack_resource_controller_v2_api_v1alpha1_PortFilter(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PortImport": schema_openstack_resource_controller_v2_api_v1alpha1_PortImport(ref), @@ -4872,6 +4873,25 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_NeutronStatusMetadata( } } +func schema_openstack_resource_controller_v2_api_v1alpha1_PasswordSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "secretRef is a reference to a Secret containing the password for this user.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_Port(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -11030,9 +11050,17 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceSpec(ref c Format: "", }, }, + "password": { + SchemaProps: spec.SchemaProps{ + Description: "password is the password set for the user", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PasswordSpec"), + }, + }, }, }, }, + Dependencies: []string{ + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.PasswordSpec"}, } } @@ -11078,6 +11106,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_UserResourceStatus(ref Format: "", }, }, + "passwordExpiresAt": { + SchemaProps: spec.SchemaProps{ + Description: "passwordExpiresAt filters the response based on expriing passwords.", + 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 bc8835301..ec3101c1a 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 + 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 @@ -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: diff --git a/config/samples/openstack_v1alpha1_user.yaml b/config/samples/openstack_v1alpha1_user.yaml index 09067e614..d27169d30 100644 --- a/config/samples/openstack_v1alpha1_user.yaml +++ b/config/samples/openstack_v1alpha1_user.yaml @@ -1,12 +1,48 @@ --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Domain +metadata: + name: user-sample +spec: + cloudCredentialsRef: + cloudName: devstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: {} +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Project +metadata: + name: user-sample +spec: + cloudCredentialsRef: + cloudName: devstack-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 spec: cloudCredentialsRef: - cloudName: openstack-admin + cloudName: devstack-admin 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 \ No newline at end of file diff --git a/internal/controllers/user/actuator.go b/internal/controllers/user/actuator.go index 391205112..cbf09c89e 100644 --- a/internal/controllers/user/actuator.go +++ b/internal/controllers/user/actuator.go @@ -135,6 +135,28 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT defaultProjectID = ptr.Deref(project.Status.ID, "") } } + + var password string + var passwordSecretVersion 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 { + passwordBytes, ok := secret.Data["password"] + if !ok { + reconcileStatus = reconcileStatus.WithReconcileStatus( + progress.NewReconcileStatus().WithProgressMessage("Password secret does not contain \"password\" key")) + } else { + password = string(passwordBytes) + passwordSecretVersion = secret.ResourceVersion + } + } + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus } @@ -144,6 +166,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) @@ -155,6 +178,15 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT return nil, progress.WrapError(err) } + // Store the password Secret ResourceVersion after successful creation + if passwordSecretVersion != "" { + if err := actuator.updatePasswordSecretVersionAnnotation(ctx, obj, passwordSecretVersion); err != nil { + log := ctrl.LoggerFrom(ctx) + log.Error(err, "Failed to update password secret version annotation after creation") + // Don't fail the create just because we couldn't update the annotation + } + } + return osResource, nil } @@ -177,6 +209,11 @@ func (actuator userActuator) updateResource(ctx context.Context, obj orcObjectPT handleDescriptionUpdate(&updateOpts, resource, osResource) handleEnabledUpdate(&updateOpts, resource, osResource) + newSecretVersion, passwordRS := actuator.handlePasswordUpdate(ctx, &updateOpts, obj) + if passwordRS != nil { + return passwordRS + } + needsUpdate, err := needsUpdate(updateOpts) if err != nil { return progress.WrapError( @@ -198,6 +235,15 @@ func (actuator userActuator) updateResource(ctx context.Context, obj orcObjectPT return progress.WrapError(err) } + // If password was updated, store the new Secret ResourceVersion in annotation + if newSecretVersion != "" { + if err := actuator.updatePasswordSecretVersionAnnotation(ctx, obj, newSecretVersion); err != nil { + log.Error(err, "Failed to update password secret version annotation") + // Don't fail the reconcile just because we couldn't update the annotation + // The password was already updated in OpenStack + } + } + return progress.NeedsRefresh() } @@ -236,6 +282,51 @@ func handleEnabledUpdate(updateOpts *users.UpdateOpts, resource *resourceSpecT, } } +func (actuator userActuator) updatePasswordSecretVersionAnnotation(ctx context.Context, obj orcObjectPT, secretVersion string) error { + // Create a patch to update just the annotation + patch := client.MergeFrom(obj.DeepCopy()) + + if obj.Annotations == nil { + obj.Annotations = make(map[string]string) + } + obj.Annotations["openstack.k-orc.cloud/password-secret-version"] = secretVersion + + return actuator.k8sClient.Patch(ctx, obj, patch) +} + +func (actuator userActuator) handlePasswordUpdate(ctx context.Context, updateOpts *users.UpdateOpts, obj orcObjectPT) (secretResourceVersion string, reconcileStatus progress.ReconcileStatus) { + resource := obj.Spec.Resource + if resource == nil { + return "", nil + } + + if resource.Password != nil && resource.Password.SecretRef != nil { + secret, secretReconcileStatus := dependency.FetchDependency( + ctx, actuator.k8sClient, obj.Namespace, + resource.Password.SecretRef, "Secret", + func(*corev1.Secret) bool { return true }, + ) + if secretReconcileStatus != nil { + return "", secretReconcileStatus + } + + // Check if password Secret has changed by comparing ResourceVersion + currentSecretVersion := secret.ResourceVersion + storedSecretVersion := obj.Annotations["openstack.k-orc.cloud/password-secret-version"] + + // Only update password if Secret ResourceVersion changed + if storedSecretVersion != currentSecretVersion { + if passwordBytes, ok := secret.Data["password"]; ok { + password := string(passwordBytes) + updateOpts.Password = password + return currentSecretVersion, nil + } + } + } + + return "", nil +} + func (actuator userActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) { return []resourceReconciler{ actuator.updateResource, diff --git a/internal/controllers/user/controller.go b/internal/controllers/user/controller.go index 4e432c0c7..2f966f873 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.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) @@ -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,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 { 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..e4f5f132f 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 @@ -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 @@ -35,4 +43,6 @@ 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 + password: + secretRef: user-create-full \ No newline at end of file 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..02a409a8f 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,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)" + 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..d47398d55 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-create-full +type: Opaque +stringData: + password: "TestPassword" \ No newline at end of file diff --git a/internal/controllers/user/tests/user-update-password/00-assert.yaml b/internal/controllers/user/tests/user-update-password/00-assert.yaml new file mode 100644 index 000000000..1565e3db4 --- /dev/null +++ b/internal/controllers/user/tests/user-update-password/00-assert.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +resourceRefs: + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: User + name: user-update-password + ref: user +assertAll: + - celExpr: "user.status.id != ''" + - celExpr: "user.status.resource.domainID == 'default'" +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-update-password +status: + resource: + name: user-update-password + enabled: true + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success + diff --git a/internal/controllers/user/tests/user-update-password/00-create-resource.yaml b/internal/controllers/user/tests/user-update-password/00-create-resource.yaml new file mode 100644 index 000000000..cea90f7bf --- /dev/null +++ b/internal/controllers/user/tests/user-update-password/00-create-resource.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-update-password +spec: + cloudCredentialsRef: + cloudName: openstack-admin + secretName: openstack-clouds + managementPolicy: managed + resource: + password: + secretRef: user-update-password diff --git a/internal/controllers/user/tests/user-update-password/00-secret.yaml b/internal/controllers/user/tests/user-update-password/00-secret.yaml new file mode 100644 index 000000000..c299398fb --- /dev/null +++ b/internal/controllers/user/tests/user-update-password/00-secret.yaml @@ -0,0 +1,14 @@ +--- +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: user-update-password +type: Opaque +stringData: + password: "user-update" diff --git a/internal/controllers/user/tests/user-update-password/01-assert.yaml b/internal/controllers/user/tests/user-update-password/01-assert.yaml new file mode 100644 index 000000000..7ff211503 --- /dev/null +++ b/internal/controllers/user/tests/user-update-password/01-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-update-password +status: + resource: + name: user-update-password + enabled: true + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success + diff --git a/internal/controllers/user/tests/user-update-password/01-update-password.yaml b/internal/controllers/user/tests/user-update-password/01-update-password.yaml new file mode 100644 index 000000000..c2165ccf6 --- /dev/null +++ b/internal/controllers/user/tests/user-update-password/01-update-password.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: user-update-password +type: Opaque +stringData: + password: "user-update-updated" diff --git a/internal/controllers/user/tests/user-update-password/02-assert.yaml b/internal/controllers/user/tests/user-update-password/02-assert.yaml new file mode 100644 index 000000000..7ff211503 --- /dev/null +++ b/internal/controllers/user/tests/user-update-password/02-assert.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: User +metadata: + name: user-update-password +status: + resource: + name: user-update-password + enabled: true + conditions: + - type: Available + status: "True" + reason: Success + - type: Progressing + status: "False" + reason: Success + diff --git a/internal/controllers/user/tests/user-update-password/02-revert-password.yaml b/internal/controllers/user/tests/user-update-password/02-revert-password.yaml new file mode 100644 index 000000000..8f9bd268f --- /dev/null +++ b/internal/controllers/user/tests/user-update-password/02-revert-password.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: user-update-password +type: Opaque +stringData: + password: "user-update" diff --git a/internal/controllers/user/tests/user-update-password/README.md b/internal/controllers/user/tests/user-update-password/README.md new file mode 100644 index 000000000..44a4a6485 --- /dev/null +++ b/internal/controllers/user/tests/user-update-password/README.md @@ -0,0 +1,27 @@ +# Update User Password + +This test verifies that a User's password can be updated by changing the referenced Secret. + +## Step 00 + +Create a User with a password Secret containing "InitialPassword123". + +## Step 01 + +Update the password Secret to contain "UpdatedPassword456". Verify that the User reconciles and remains Available. + +## Step 02 + +Revert the password Secret back to "InitialPassword123". Verify that the User reconciles and remains Available. + +## What This Tests + +- Password is set during creation +- Password can be updated by changing the Secret +- Password updates trigger reconciliation +- User remains Available throughout password updates +- Password can be changed multiple times + +## Note + +The password value itself is write-only in OpenStack, so we cannot verify the actual password value in the status. This test only verifies that password updates don't cause errors and the User remains Available. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/passwordspec.go b/pkg/clients/applyconfiguration/api/v1alpha1/passwordspec.go new file mode 100644 index 000000000..4dd652577 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/passwordspec.go @@ -0,0 +1,43 @@ +/* +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" +) + +// PasswordSpecApplyConfiguration represents a declarative configuration of the PasswordSpec type for use +// with apply. +type PasswordSpecApplyConfiguration struct { + SecretRef *apiv1alpha1.KubernetesNameRef `json:"secretRef,omitempty"` +} + +// PasswordSpecApplyConfiguration constructs a declarative configuration of the PasswordSpec type for use with +// apply. +func PasswordSpec() *PasswordSpecApplyConfiguration { + return &PasswordSpecApplyConfiguration{} +} + +// WithSecretRef sets the SecretRef 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 SecretRef field is set to the value of the last call. +func (b *PasswordSpecApplyConfiguration) WithSecretRef(value apiv1alpha1.KubernetesNameRef) *PasswordSpecApplyConfiguration { + b.SecretRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go index ed4b86a2e..e7898082e 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/userresourcespec.go @@ -25,11 +25,12 @@ import ( // UserResourceSpecApplyConfiguration represents a declarative configuration of the UserResourceSpec type for use // with apply. type UserResourceSpecApplyConfiguration struct { - Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` - DefaultProjectRef *apiv1alpha1.KubernetesNameRef `json:"defaultProjectRef,omitempty"` - Enabled *bool `json:"enabled,omitempty"` + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + DomainRef *apiv1alpha1.KubernetesNameRef `json:"domainRef,omitempty"` + DefaultProjectRef *apiv1alpha1.KubernetesNameRef `json:"defaultProjectRef,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + Password *PasswordSpecApplyConfiguration `json:"password,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 } + +// WithPassword sets the Password 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 Password field is set to the value of the last call. +func (b *UserResourceSpecApplyConfiguration) WithPassword(value *PasswordSpecApplyConfiguration) *UserResourceSpecApplyConfiguration { + b.Password = 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 3a7e6ae0c..2d11586f0 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -1338,6 +1338,12 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.NetworkResourceStatus +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.PasswordSpec + map: + fields: + - name: secretRef + type: + scalar: string - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.Port map: fields: @@ -3297,6 +3303,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: name type: scalar: string + - name: password + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.PasswordSpec - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.UserResourceStatus map: fields: @@ -3315,6 +3324,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/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 0e7f2efb3..428ca2b9c 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -194,6 +194,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.NetworkStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("NeutronStatusMetadata"): return &apiv1alpha1.NeutronStatusMetadataApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("PasswordSpec"): + return &apiv1alpha1.PasswordSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Port"): return &apiv1alpha1.PortApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("PortFilter"): diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 5512b3f6b..a8ea51c43 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -1784,6 +1784,7 @@ _Appears in:_ - [HostID](#hostid) - [NetworkFilter](#networkfilter) - [NetworkResourceSpec](#networkresourcespec) +- [PasswordSpec](#passwordspec) - [PortFilter](#portfilter) - [PortResourceSpec](#portresourcespec) - [RoleFilter](#rolefilter) @@ -2218,6 +2219,24 @@ _Appears in:_ +#### PasswordSpec + + + + + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [UserResourceSpec](#userresourcespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `secretRef` _[KubernetesNameRef](#kubernetesnameref)_ | secretRef is a reference to a Secret containing the password for this user. | | MaxLength: 253
MinLength: 1
| + + #### Port @@ -4291,6 +4310,7 @@ _Appears in:_ | `domainRef` _[KubernetesNameRef](#kubernetesnameref)_ | domainRef is a reference to the ORC Domain which this resource is associated with. | | MaxLength: 253
MinLength: 1
| | `defaultProjectRef` _[KubernetesNameRef](#kubernetesnameref)_ | defaultProjectRef is a reference to the Default Project which this resource is associated with. | | MaxLength: 253
MinLength: 1
| | `enabled` _boolean_ | enabled defines whether a user is enabled or disabled | | | +| `password` _[PasswordSpec](#passwordspec)_ | password is the password set for the user | | MaxProperties: 1
MinProperties: 1
| #### UserResourceStatus @@ -4311,6 +4331,7 @@ _Appears in:_ | `domainID` _string_ | domainID is the ID of the Domain to which the resource is associated. | | MaxLength: 1024
| | `defaultProjectID` _string_ | defaultProjectID is the ID of the Default Project to which the user is associated with. | | MaxLength: 1024
| | `enabled` _boolean_ | enabled defines whether a user is enabled or disabled | | | +| `passwordExpiresAt` _string_ | passwordExpiresAt filters the response based on expriing passwords. | | MaxLength: 255
| #### UserSpec