Skip to content

Commit 32efe48

Browse files
committed
Address review comments on CredentialRotation PR
- Remove SetOwnerReference in CLI (controller handles it; kubeClient has no Scheme() method) - Reuse existing ROTATION_ID from source Secret in handleStartRotation to avoid wedging on crash recovery between Secret update and status update - Fail closed in reconcileV1Secret guard: return error on transient CredentialRotation Get failures instead of proceeding with distribution - Fix test assertion: use Equal("") instead of BeElementOf with duplicate values Signed-off-by: shunki-fujita <shunki-fujita@cybozu.co.jp>
1 parent 5447a2d commit 32efe48

5 files changed

Lines changed: 22 additions & 10 deletions

File tree

cmd/kubectl-moco/cmd/credential.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
apierrors "k8s.io/apimachinery/pkg/api/errors"
1212
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1313
"k8s.io/apimachinery/pkg/types"
14-
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
1514
)
1615

1716
var credentialConfig struct {
@@ -119,9 +118,7 @@ func credentialRotate(ctx context.Context, clusterName string) error {
119118
DiscardOldPassword: false,
120119
},
121120
}
122-
if err := controllerutil.SetOwnerReference(cluster, cr, kubeClient.Scheme()); err != nil {
123-
return fmt.Errorf("failed to set ownerReference: %w", err)
124-
}
121+
// ownerReference is set by the controller on first reconcile.
125122
if err := kubeClient.Create(ctx, cr); err != nil {
126123
return fmt.Errorf("failed to create CredentialRotation: %w", err)
127124
}

controllers/credentialrotation_controller.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,6 @@ func (r *CredentialRotationReconciler) handleStartRotation(ctx context.Context,
114114
return ctrl.Result{RequeueAfter: credRotationRequeueInterval}, nil
115115
}
116116

117-
// Generate rotationID
118-
rotationID := uuid.New().String()
119-
120117
// Get the source Secret
121118
sourceSecret := &corev1.Secret{}
122119
secretName := cluster.ControllerSecretName()
@@ -127,7 +124,14 @@ func (r *CredentialRotationReconciler) handleStartRotation(ctx context.Context,
127124
return ctrl.Result{}, fmt.Errorf("failed to get source secret: %w", err)
128125
}
129126

130-
// Generate pending passwords
127+
// Reuse existing rotationID if the Secret already has complete pending
128+
// passwords (crash recovery: Secret was updated but status was not).
129+
rotationID := password.GetRotationID(sourceSecret)
130+
if rotationID == "" {
131+
rotationID = uuid.New().String()
132+
}
133+
134+
// Generate pending passwords (idempotent if rotationID matches)
131135
_, err := password.SetPendingPasswords(sourceSecret, rotationID)
132136
if err != nil {
133137
r.Recorder.Eventf(cr, corev1.EventTypeWarning, "RotationPendingError",

controllers/credentialrotation_controller_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,14 +515,14 @@ var _ = Describe("CredentialRotation reconciler", func() {
515515
err = k8sClient.Create(ctx, cr)
516516
Expect(err).NotTo(HaveOccurred())
517517

518-
// Phase should NOT advance past Rotating (reconciler emits warning and requeues).
518+
// Phase should remain empty (reconciler refuses and requeues without advancing).
519519
Consistently(func() mocov1beta2.RotationPhase {
520520
cr := &mocov1beta2.CredentialRotation{}
521521
if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: "test", Name: "test"}, cr); err != nil {
522522
return ""
523523
}
524524
return cr.Status.Phase
525-
}).Should(BeElementOf(mocov1beta2.RotationPhase(""), mocov1beta2.RotationPhase("")))
525+
}).Should(Equal(mocov1beta2.RotationPhase("")))
526526
})
527527

528528
It("should not advance past Rotated without discardOldPassword", func() {

controllers/mysqlcluster_controller.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ func (r *MySQLClusterReconciler) reconcileV1Secret(ctx context.Context, req ctrl
348348

349349
// During credential rotation, the CredentialRotationReconciler owns
350350
// secret distribution. Skip to prevent overwriting pending passwords.
351+
// Fail closed on transient errors to avoid distributing stale passwords.
351352
var cr mocov1beta2.CredentialRotation
352353
if err := r.Get(ctx, client.ObjectKey{
353354
Namespace: cluster.Namespace,
@@ -358,6 +359,8 @@ func (r *MySQLClusterReconciler) reconcileV1Secret(ctx context.Context, req ctrl
358359
log.Info("credential rotation in progress, skipping secret distribution", "phase", phase)
359360
return nil
360361
}
362+
} else if !apierrors.IsNotFound(err) {
363+
return fmt.Errorf("failed to check CredentialRotation status: %w", err)
361364
}
362365

363366
if err := r.reconcileUserSecret(ctx, req, cluster, secret); err != nil {

pkg/password/rotation.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ var userToPendingKey = map[string]string{
6363
constants.WritableUser: WritablePasswordPendingKey,
6464
}
6565

66+
// GetRotationID returns the stored ROTATION_ID from the secret, or "" if absent.
67+
func GetRotationID(secret *corev1.Secret) string {
68+
if secret.Data == nil {
69+
return ""
70+
}
71+
return string(secret.Data[RotationIDKey])
72+
}
73+
6674
// HasPendingPasswords validates the pending state of a source secret.
6775
//
6876
// Returns:

0 commit comments

Comments
 (0)