Skip to content

Commit c76b0e8

Browse files
committed
WIP: Trying to add UpdateOpt for password, using resource version of object to determine when the password has been changed, need to test first before opening a proper PR
Signed-off-by: Daniel Lawton <dlawton@redhat.com>
1 parent d2668a0 commit c76b0e8

9 files changed

Lines changed: 202 additions & 1 deletion

File tree

internal/controllers/user/actuator.go

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT
137137
}
138138

139139
var password string
140+
var passwordSecretVersion string
140141
if resource.Password != nil {
141142
secret, secretReconcileStatus := dependency.FetchDependency(
142143
ctx, actuator.k8sClient, obj.Namespace,
@@ -145,13 +146,13 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT
145146
)
146147
reconcileStatus = reconcileStatus.WithReconcileStatus(secretReconcileStatus)
147148
if secretReconcileStatus == nil {
148-
var ok bool
149149
passwordBytes, ok := secret.Data["password"]
150150
if !ok {
151151
reconcileStatus = reconcileStatus.WithReconcileStatus(
152152
progress.NewReconcileStatus().WithProgressMessage("Password secret does not contain \"password\" key"))
153153
} else {
154154
password = string(passwordBytes)
155+
passwordSecretVersion = secret.ResourceVersion
155156
}
156157
}
157158
}
@@ -177,6 +178,15 @@ func (actuator userActuator) CreateResource(ctx context.Context, obj orcObjectPT
177178
return nil, progress.WrapError(err)
178179
}
179180

181+
// Store the password Secret ResourceVersion after successful creation
182+
if passwordSecretVersion != "" {
183+
if err := actuator.updatePasswordSecretVersionAnnotation(ctx, obj, passwordSecretVersion); err != nil {
184+
log := ctrl.LoggerFrom(ctx)
185+
log.Error(err, "Failed to update password secret version annotation after creation")
186+
// Don't fail the create just because we couldn't update the annotation
187+
}
188+
}
189+
180190
return osResource, nil
181191
}
182192

@@ -199,6 +209,11 @@ func (actuator userActuator) updateResource(ctx context.Context, obj orcObjectPT
199209
handleDescriptionUpdate(&updateOpts, resource, osResource)
200210
handleEnabledUpdate(&updateOpts, resource, osResource)
201211

212+
newSecretVersion, passwordRS := actuator.handlePasswordUpdate(ctx, &updateOpts, obj)
213+
if passwordRS != nil {
214+
return passwordRS
215+
}
216+
202217
needsUpdate, err := needsUpdate(updateOpts)
203218
if err != nil {
204219
return progress.WrapError(
@@ -220,6 +235,15 @@ func (actuator userActuator) updateResource(ctx context.Context, obj orcObjectPT
220235
return progress.WrapError(err)
221236
}
222237

238+
// If password was updated, store the new Secret ResourceVersion in annotation
239+
if newSecretVersion != "" {
240+
if err := actuator.updatePasswordSecretVersionAnnotation(ctx, obj, newSecretVersion); err != nil {
241+
log.Error(err, "Failed to update password secret version annotation")
242+
// Don't fail the reconcile just because we couldn't update the annotation
243+
// The password was already updated in OpenStack
244+
}
245+
}
246+
223247
return progress.NeedsRefresh()
224248
}
225249

@@ -258,6 +282,51 @@ func handleEnabledUpdate(updateOpts *users.UpdateOpts, resource *resourceSpecT,
258282
}
259283
}
260284

285+
func (actuator userActuator) updatePasswordSecretVersionAnnotation(ctx context.Context, obj orcObjectPT, secretVersion string) error {
286+
// Create a patch to update just the annotation
287+
patch := client.MergeFrom(obj.DeepCopy())
288+
289+
if obj.Annotations == nil {
290+
obj.Annotations = make(map[string]string)
291+
}
292+
obj.Annotations["openstack.k-orc.cloud/password-secret-version"] = secretVersion
293+
294+
return actuator.k8sClient.Patch(ctx, obj, patch)
295+
}
296+
297+
func (actuator userActuator) handlePasswordUpdate(ctx context.Context, updateOpts *users.UpdateOpts, obj orcObjectPT) (secretResourceVersion string, reconcileStatus progress.ReconcileStatus) {
298+
resource := obj.Spec.Resource
299+
if resource == nil {
300+
return "", nil
301+
}
302+
303+
if resource.Password != nil && resource.Password.SecretRef != nil {
304+
secret, secretReconcileStatus := dependency.FetchDependency(
305+
ctx, actuator.k8sClient, obj.Namespace,
306+
resource.Password.SecretRef, "Secret",
307+
func(*corev1.Secret) bool { return true },
308+
)
309+
if secretReconcileStatus != nil {
310+
return "", secretReconcileStatus
311+
}
312+
313+
// Check if password Secret has changed by comparing ResourceVersion
314+
currentSecretVersion := secret.ResourceVersion
315+
storedSecretVersion := obj.Annotations["openstack.k-orc.cloud/password-secret-version"]
316+
317+
// Only update password if Secret ResourceVersion changed
318+
if storedSecretVersion != currentSecretVersion {
319+
if passwordBytes, ok := secret.Data["password"]; ok {
320+
password := string(passwordBytes)
321+
updateOpts.Password = password
322+
return currentSecretVersion, nil
323+
}
324+
}
325+
}
326+
327+
return "", nil
328+
}
329+
261330
func (actuator userActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) {
262331
return []resourceReconciler{
263332
actuator.updateResource,
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
apiVersion: kuttl.dev/v1beta1
3+
kind: TestAssert
4+
resourceRefs:
5+
- apiVersion: openstack.k-orc.cloud/v1alpha1
6+
kind: User
7+
name: user-update-password
8+
ref: user
9+
assertAll:
10+
- celExpr: "user.status.id != ''"
11+
- celExpr: "user.status.resource.domainID == 'default'"
12+
---
13+
apiVersion: openstack.k-orc.cloud/v1alpha1
14+
kind: User
15+
metadata:
16+
name: user-update-password
17+
status:
18+
resource:
19+
name: user-update-password
20+
enabled: true
21+
conditions:
22+
- type: Available
23+
status: "True"
24+
reason: Success
25+
- type: Progressing
26+
status: "False"
27+
reason: Success
28+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
apiVersion: openstack.k-orc.cloud/v1alpha1
3+
kind: User
4+
metadata:
5+
name: user-update-password
6+
spec:
7+
cloudCredentialsRef:
8+
cloudName: openstack-admin
9+
secretName: openstack-clouds
10+
managementPolicy: managed
11+
resource:
12+
password:
13+
secretRef: user-update-password
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
apiVersion: kuttl.dev/v1beta1
3+
kind: TestStep
4+
commands:
5+
- command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
6+
namespaced: true
7+
---
8+
apiVersion: v1
9+
kind: Secret
10+
metadata:
11+
name: user-update-password
12+
type: Opaque
13+
stringData:
14+
password: "user-update"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
apiVersion: openstack.k-orc.cloud/v1alpha1
3+
kind: User
4+
metadata:
5+
name: user-update-password
6+
status:
7+
resource:
8+
name: user-update-password
9+
enabled: true
10+
conditions:
11+
- type: Available
12+
status: "True"
13+
reason: Success
14+
- type: Progressing
15+
status: "False"
16+
reason: Success
17+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
apiVersion: v1
3+
kind: Secret
4+
metadata:
5+
name: user-update-password
6+
type: Opaque
7+
stringData:
8+
password: "user-update-updated"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
apiVersion: openstack.k-orc.cloud/v1alpha1
3+
kind: User
4+
metadata:
5+
name: user-update-password
6+
status:
7+
resource:
8+
name: user-update-password
9+
enabled: true
10+
conditions:
11+
- type: Available
12+
status: "True"
13+
reason: Success
14+
- type: Progressing
15+
status: "False"
16+
reason: Success
17+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
apiVersion: v1
3+
kind: Secret
4+
metadata:
5+
name: user-update-password
6+
type: Opaque
7+
stringData:
8+
password: "user-update"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Update User Password
2+
3+
This test verifies that a User's password can be updated by changing the referenced Secret.
4+
5+
## Step 00
6+
7+
Create a User with a password Secret containing "InitialPassword123".
8+
9+
## Step 01
10+
11+
Update the password Secret to contain "UpdatedPassword456". Verify that the User reconciles and remains Available.
12+
13+
## Step 02
14+
15+
Revert the password Secret back to "InitialPassword123". Verify that the User reconciles and remains Available.
16+
17+
## What This Tests
18+
19+
- Password is set during creation
20+
- Password can be updated by changing the Secret
21+
- Password updates trigger reconciliation
22+
- User remains Available throughout password updates
23+
- Password can be changed multiple times
24+
25+
## Note
26+
27+
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.

0 commit comments

Comments
 (0)