@@ -22,6 +22,7 @@ import (
2222
2323 "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users"
2424 corev1 "k8s.io/api/core/v1"
25+ "k8s.io/apimachinery/pkg/types"
2526 "k8s.io/utils/ptr"
2627 ctrl "sigs.k8s.io/controller-runtime"
2728 "sigs.k8s.io/controller-runtime/pkg/client"
@@ -31,8 +32,10 @@ import (
3132 "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress"
3233 "github.com/k-orc/openstack-resource-controller/v2/internal/logging"
3334 "github.com/k-orc/openstack-resource-controller/v2/internal/osclients"
35+ "github.com/k-orc/openstack-resource-controller/v2/internal/util/applyconfigs"
3436 "github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency"
3537 orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors"
38+ orcapplyconfigv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/pkg/clients/applyconfiguration/api/v1alpha1"
3639)
3740
3841// OpenStack resource types
@@ -183,6 +186,75 @@ func (actuator userActuator) DeleteResource(ctx context.Context, _ orcObjectPT,
183186 return progress .WrapError (actuator .osClient .DeleteUser (ctx , resource .ID ))
184187}
185188
189+ func (actuator userActuator ) reconcilePassword (ctx context.Context , obj orcObjectPT , osResource * osResourceT ) progress.ReconcileStatus {
190+ log := ctrl .LoggerFrom (ctx )
191+ resource := obj .Spec .Resource
192+ if resource == nil {
193+ return nil
194+ }
195+
196+ currentRef := string (resource .PasswordRef )
197+ var lastAppliedRef string
198+ if obj .Status .Resource != nil {
199+ lastAppliedRef = obj .Status .Resource .AppliedPasswordRef
200+ }
201+
202+ if lastAppliedRef == currentRef {
203+ return nil
204+ }
205+
206+ // Read the password from the referenced Secret
207+ secret , secretRS := dependency .FetchDependency (
208+ ctx , actuator .k8sClient , obj .Namespace ,
209+ & resource .PasswordRef , "Secret" ,
210+ func (* corev1.Secret ) bool { return true },
211+ )
212+ if secretRS != nil {
213+ return secretRS
214+ }
215+
216+ passwordBytes , ok := secret .Data ["password" ]
217+ if ! ok {
218+ return progress .NewReconcileStatus ().WithProgressMessage ("Password secret does not contain \" password\" key" )
219+ }
220+ password := string (passwordBytes )
221+
222+ // Only call UpdateUser if this is not the first reconcile after creation.
223+ // CreateResource already set the initial password.
224+ if lastAppliedRef != "" {
225+ log .V (logging .Info ).Info ("Updating password" )
226+ _ , err := actuator .osClient .UpdateUser (ctx , osResource .ID , users.UpdateOpts {
227+ Password : password ,
228+ })
229+
230+ if orcerrors .IsConflict (err ) {
231+ err = orcerrors .Terminal (orcv1alpha1 .ConditionReasonInvalidConfiguration , "invalid configuration updating resource: " + err .Error (), err )
232+ }
233+ if err != nil {
234+ return progress .WrapError (err )
235+ }
236+ }
237+
238+ // Update the lastAppliedPasswordRef status field via a MergePatch.
239+ // MergePatch sets only the specified fields without claiming SSA
240+ // ownership, so the main SSA status update won't remove this field.
241+ statusApply := orcapplyconfigv1alpha1 .UserResourceStatus ().
242+ WithAppliedPasswordRef (currentRef )
243+ applyConfig := orcapplyconfigv1alpha1 .User (obj .Name , obj .Namespace ).
244+ WithUID (obj .UID ).
245+ WithStatus (orcapplyconfigv1alpha1 .UserStatus ().
246+ WithResource (statusApply ))
247+ if err := actuator .k8sClient .Status ().Patch (ctx , obj ,
248+ applyconfigs .Patch (types .MergePatchType , applyConfig )); err != nil {
249+ return progress .WrapError (err )
250+ }
251+
252+ if lastAppliedRef != "" {
253+ return progress .NeedsRefresh ()
254+ }
255+ return nil
256+ }
257+
186258func (actuator userActuator ) updateResource (ctx context.Context , obj orcObjectPT , osResource * osResourceT ) progress.ReconcileStatus {
187259 log := ctrl .LoggerFrom (ctx )
188260 resource := obj .Spec .Resource
@@ -259,6 +331,7 @@ func handleEnabledUpdate(updateOpts *users.UpdateOpts, resource *resourceSpecT,
259331
260332func (actuator userActuator ) GetResourceReconcilers (ctx context.Context , orcObject orcObjectPT , osResource * osResourceT , controller interfaces.ResourceController ) ([]resourceReconciler , progress.ReconcileStatus ) {
261333 return []resourceReconciler {
334+ actuator .reconcilePassword ,
262335 actuator .updateResource ,
263336 }, nil
264337}
0 commit comments