diff --git a/go.mod b/go.mod index 9856a72a..4a2d9841 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21 require ( github.com/gardener/gardener v1.85.0 github.com/go-logr/logr v1.3.0 - github.com/onsi/ginkgo/v2 v2.13.0 + github.com/onsi/ginkgo/v2 v2.13.2 github.com/onsi/gomega v1.30.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.16.0 @@ -55,11 +55,11 @@ require ( golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/sys v0.14.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/tools v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.31.0 // indirect diff --git a/go.sum b/go.sum index d4bb2fff..bd30893e 100644 --- a/go.sum +++ b/go.sum @@ -86,6 +86,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= +github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -167,6 +169,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= @@ -185,6 +189,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/controller/find_last_sync_time.go b/internal/controller/find_last_sync_time.go new file mode 100644 index 00000000..3f03f500 --- /dev/null +++ b/internal/controller/find_last_sync_time.go @@ -0,0 +1,19 @@ +package controller + +import ( + "time" +) + +func findLastSyncTime(annotations map[string]string) (time.Time, bool) { + _, found := annotations[lastKubeconfigSyncAnnotation] + if !found { + return time.Time{}, false + } + + lastSyncTimeString := annotations[lastKubeconfigSyncAnnotation] + lastSyncTime, err := time.Parse(time.RFC3339, lastSyncTimeString) + if err != nil { + return time.Time{}, false + } + return lastSyncTime, true +} diff --git a/internal/controller/find_last_sync_time_test.go b/internal/controller/find_last_sync_time_test.go new file mode 100644 index 00000000..60c7af33 --- /dev/null +++ b/internal/controller/find_last_sync_time_test.go @@ -0,0 +1,27 @@ +package controller + +import ( + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("findLastSyncTime", func() { + + DescribeTable("should return expected values when", + func(annotations map[string]string, expectedFound bool, expectedTime time.Time) { + lastSyncTime, found := findLastSyncTime(annotations) + Expect(found).To(Equal(expectedFound)) + Expect(lastSyncTime).To(Equal(expectedTime)) + }, + Entry("receives empty annotation map", make(map[string]string), false, time.Time{}), + Entry("receives annotation map containing valid date value", + map[string]string{lastKubeconfigSyncAnnotation: "2023-01-01T12:00:00Z"}, true, + func() time.Time { + t, _ := time.Parse(time.RFC3339, "2023-01-01T12:00:00Z") + return t + }()), + Entry("receives annotation map containing invalid date value", map[string]string{lastKubeconfigSyncAnnotation: "invalid"}, false, time.Time{}), + ) +}) diff --git a/internal/controller/gardener_cluster_controller.go b/internal/controller/gardener_cluster_controller.go index 7bebd27e..6bd8c859 100644 --- a/internal/controller/gardener_cluster_controller.go +++ b/internal/controller/gardener_cluster_controller.go @@ -95,7 +95,7 @@ func (controller *GardenerClusterController) Reconcile(ctx context.Context, req } if err == nil { - controller.log.Info("Secret has been deleted.", loggingContext(req)...) + controller.log.Info("The gardener cluster does not exist the coresponding secret has been deleted.", loggingContext(req)...) } return controller.resultWithoutRequeue(&cluster), err @@ -113,7 +113,7 @@ func (controller *GardenerClusterController) Reconcile(ctx context.Context, req annotations = secret.Annotations } - _, lastSyncTime := findLastSyncTime(annotations) + lastSyncTime, _ := findLastSyncTime(annotations) now := time.Now().UTC() requeueAfter := nextRequeue(now, lastSyncTime, controller.rotationPeriod, rotationPeriodRatio) @@ -259,7 +259,7 @@ func (controller *GardenerClusterController) handleKubeconfig(ctx context.Contex return ksRotated, nil } - if !secretNeedsToBeRotated(cluster, secret, controller.rotationPeriod) { + if !secretNeedsToBeRotated(cluster, secret, controller.rotationPeriod, now) { message := fmt.Sprintf("Secret %s in namespace %s does not need to be rotated yet.", cluster.Spec.Kubeconfig.Secret.Name, cluster.Spec.Kubeconfig.Secret.Namespace) controller.log.Info(message, loggingContextFromCluster(cluster)...) cluster.UpdateConditionForReadyState(imv1.ConditionTypeKubeconfigManagement, imv1.ConditionReasonKubeconfigSecretCreated, metav1.ConditionTrue) @@ -273,57 +273,25 @@ func (controller *GardenerClusterController) handleKubeconfig(ctx context.Contex return ksCreated, controller.createNewSecret(ctx, kubeconfig, cluster, now) } -func secretNeedsToBeRotated(cluster *imv1.GardenerCluster, secret *corev1.Secret, rotationPeriod time.Duration) bool { - return secretRotationTimePassed(secret, rotationPeriod) || secretRotationForced(cluster) +func secretNeedsToBeRotated(cluster *imv1.GardenerCluster, secret *corev1.Secret, rotationPeriod time.Duration, now time.Time) bool { + return secretRotationTimePassed(secret, rotationPeriod, now) || secretRotationForced(cluster) } -func findLastSyncTime(annotations map[string]string) (bool, time.Time) { - _, found := annotations[lastKubeconfigSyncAnnotation] - if !found { - return false, time.Time{} - } - - lastSyncTimeString := annotations[lastKubeconfigSyncAnnotation] - lastSyncTime, err := time.Parse(time.RFC3339, lastSyncTimeString) - if err != nil { - return false, time.Time{} - } - return true, lastSyncTime -} - -// nextRequeue - predicts duration for next requeue of GardenerCluster CR -func nextRequeue(now, lastSyncTime time.Time, rotationPeriod time.Duration, modifier float64) time.Duration { - rotationPeriodWithModifier := modifier * rotationPeriod.Minutes() - minutesToRequeue := rotationPeriodWithModifier - now.Sub(lastSyncTime).Minutes() - if minutesToRequeue <= 0 { - return time.Duration(rotationPeriodWithModifier * float64(time.Minute)) - } - - return time.Duration(minutesToRequeue * float64(time.Minute)) -} - -func secretRotationTimePassed(secret *corev1.Secret, rotationPeriod time.Duration) bool { +func secretRotationTimePassed(secret *corev1.Secret, rotationPeriod time.Duration, now time.Time) bool { if secret == nil { return true } annotations := secret.GetAnnotations() - - _, found := annotations[lastKubeconfigSyncAnnotation] - + lastSyncTime, found := findLastSyncTime(annotations) if !found { return true } - lastSyncTimeString := annotations[lastKubeconfigSyncAnnotation] - lastSyncTime, err := time.Parse(time.RFC3339, lastSyncTimeString) - if err != nil { - return true - } - now := time.Now() - alreadyValidFor := now.Sub(lastSyncTime) + minutesToRotate := now.Sub(lastSyncTime).Minutes() + minutesInRotationPeriod := rotationPeriodRatio * rotationPeriod.Minutes() - return alreadyValidFor.Minutes() >= rotationPeriodRatio*rotationPeriod.Minutes() + return minutesToRotate >= minutesInRotationPeriod } func secretRotationForced(cluster *imv1.GardenerCluster) bool { @@ -413,7 +381,7 @@ func (controller *GardenerClusterController) removeForceRotationAnnotation(ctx c delete(annotations, forceKubeconfigRotationAnnotation) clusterToUpdate.SetAnnotations(annotations) - return controller.Client.Update(ctx, &clusterToUpdate) + return controller.Update(ctx, &clusterToUpdate) } return nil diff --git a/internal/controller/next_requeue.go b/internal/controller/next_requeue.go new file mode 100644 index 00000000..26eac578 --- /dev/null +++ b/internal/controller/next_requeue.go @@ -0,0 +1,16 @@ +package controller + +import ( + "time" +) + +// nextRequeue - predicts duration for next requeue of GardenerCluster CR +func nextRequeue(now, lastSyncTime time.Time, rotationPeriod time.Duration, modifier float64) time.Duration { + rotationPeriodWithModifier := modifier * rotationPeriod.Minutes() + minutesToRequeue := rotationPeriodWithModifier - now.Sub(lastSyncTime).Minutes() + if minutesToRequeue <= 0 { + return time.Duration(rotationPeriodWithModifier * float64(time.Minute)) + } + + return time.Duration(minutesToRequeue * float64(time.Minute)) +} diff --git a/internal/controller/next_requeue_test.go b/internal/controller/next_requeue_test.go new file mode 100644 index 00000000..fc496db6 --- /dev/null +++ b/internal/controller/next_requeue_test.go @@ -0,0 +1,28 @@ +package controller + +import ( + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("nextRequeueAfter", func() { + var ( + now = time.Now() + newYear, _ = time.Parse(time.RFC3339, "2024-01-01T00:00:01Z00:00") + ) + + DescribeTable("should return expected values when", + func(now, lastSyncTime time.Time, rotationPeriod time.Duration, modifier float64, expectedDuration time.Duration) { + result := nextRequeue(now, lastSyncTime, rotationPeriod, modifier) + Expect(result).To(BeNumerically("~", expectedDuration, 1)) + }, + Entry("receives all zero arguments", now, time.Time{}, time.Duration(0), 0.0, time.Duration(0)), + Entry("receives arguments (now, zero, 1[m], 0.95)", now, time.Time{}, time.Minute, 0.95, time.Second*57), + Entry("receives arguments (now, now-30[s], 1[m], 0.95)", now, now.Add(-30*time.Second), time.Minute, 0.95, time.Nanosecond*26999999999), + Entry("receives arguments (now, now-900[s], 1[m], 0.95)", now, now.Add(-900*time.Second), time.Minute, 0.95, time.Second*57), + Entry("receives arguments (now, now, 1[m], 0.95)", now, now, time.Minute, 0.95, time.Second*57), + Entry("receives arguments (newYear, newYear-45[m], 1[h], 0.95)", newYear, newYear.Add(-45*time.Minute), time.Hour, 0.95, time.Nanosecond*719999999999), + ) +})