From ff275f648a6ffe8b9a6b98e5383bd5d528bd268b Mon Sep 17 00:00:00 2001 From: Artem Bortnikov Date: Thu, 16 May 2024 22:29:15 +0300 Subject: [PATCH 1/3] update logging Signed-off-by: Artem Bortnikov --- Dockerfile | 2 +- cmd/app/commandline.go | 114 ++++++++++++++++++ cmd/main.go | 86 ++++++------- internal/controller/etcdcluster_controller.go | 18 ++- internal/controller/factory/builders.go | 34 +++--- internal/controller/factory/configMap.go | 12 +- internal/controller/factory/pdb.go | 36 +++--- internal/controller/factory/statefulset.go | 9 +- internal/controller/factory/svc.go | 22 ++-- internal/log/logger.go | 95 +++++++++++++++ internal/signal/signal.go | 36 ++++++ 11 files changed, 352 insertions(+), 112 deletions(-) create mode 100644 cmd/app/commandline.go create mode 100644 internal/log/logger.go create mode 100644 internal/signal/signal.go diff --git a/Dockerfile b/Dockerfile index 86893cc2..3ebd1746 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ COPY go.mod go.sum ./ RUN go mod download # Copy the go source -COPY cmd/main.go ./cmd/ +COPY cmd/ ./cmd/ COPY api/ ./api/ COPY internal/ ./internal/ diff --git a/cmd/app/commandline.go b/cmd/app/commandline.go new file mode 100644 index 00000000..9ba7fcb4 --- /dev/null +++ b/cmd/app/commandline.go @@ -0,0 +1,114 @@ +/* +Copyright 2024 The etcd-operator 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. +*/ + +package app + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/aenix-io/etcd-operator/internal/log" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +type Flags struct { + Kubeconfig string + MetricsAddress string + ProbeAddress string + LeaderElection bool + SecureMetrics bool + EnableHTTP2 bool + DisableWebhooks bool + LogLevel string + StacktraceLevel string + Dev bool +} + +func ParseCmdLine() Flags { + pflag.Usage = usage + pflag.ErrHelp = nil + pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ContinueOnError) + + pflag.String("kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.") + pflag.String("metrics-bind-address", ":8080", "The address the metric endpoint binds to.") + pflag.String("health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + pflag.Bool("leader-elect", false, "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + pflag.Bool("metrics-secure", false, "If set the metrics endpoint is served securely.") + pflag.Bool("enable-http2", false, "If set, HTTP/2 will be enabled for the metrics and webhook servers.") + pflag.Bool("disable-webhooks", false, "If set, the webhooks will be disabled.") + pflag.String("log-level", "info", "Logger verbosity level.Applicable values are debug, info, warn, error.") + pflag.String("stacktrace-level", "error", "Logger level to add stacktrace. "+ + "Applicable values are debug, info, warn, error.") + pflag.Bool("dev", false, "development mode.") + + var help bool + pflag.BoolVarP(&help, "help", "h", false, "Show this help message.") + err := viper.BindPFlags(pflag.CommandLine) + if err != nil { + exitUsage(err) + } + viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) + viper.AutomaticEnv() + err = pflag.CommandLine.Parse(os.Args[1:]) + if err != nil { + exitUsage(err) + } + if help { + exitUsage(nil) + } + + return Flags{ + Kubeconfig: viper.GetString("kubeconfig"), + MetricsAddress: viper.GetString("metrics-bind-address"), + ProbeAddress: viper.GetString("health-probe-bind-address"), + LeaderElection: viper.GetBool("leader-elect"), + SecureMetrics: viper.GetBool("metrics-secure"), + EnableHTTP2: viper.GetBool("enable-http2"), + DisableWebhooks: viper.GetBool("disable-webhooks"), + LogLevel: viper.GetString("log-level"), + StacktraceLevel: viper.GetString("stacktrace-level"), + Dev: viper.GetBool("dev"), + } +} + +func usage() { + name := filepath.Base(os.Args[0]) + _, _ = fmt.Fprintf(os.Stderr, "Usage: %s [--option]...\n", name) + _, _ = fmt.Fprintf(os.Stderr, "Options:\n") + pflag.PrintDefaults() +} + +func exitUsage(err error) { + code := 0 + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%s: %s\n", filepath.Base(os.Args[0]), err) + code = 2 + } + pflag.Usage() + os.Exit(code) +} + +func LogParameters(flags Flags) log.Parameters { + return log.Parameters{ + LogLevel: flags.LogLevel, + StacktraceLevel: flags.StacktraceLevel, + Development: flags.Dev, + } +} diff --git a/cmd/main.go b/cmd/main.go index 6ff10b1b..ddbb5bbc 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -17,62 +17,48 @@ limitations under the License. package main import ( + "context" "crypto/tls" - "flag" "os" + "syscall" - // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) - // to ensure that exec-entrypoint and run can make use of them. - _ "k8s.io/client-go/plugin/pkg/client/auth" - + "github.com/aenix-io/etcd-operator/cmd/app" + "github.com/aenix-io/etcd-operator/internal/log" + "github.com/aenix-io/etcd-operator/internal/signal" + "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + _ "k8s.io/client-go/plugin/pkg/client/auth" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/log/zap" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" etcdaenixiov1alpha1 "github.com/aenix-io/etcd-operator/api/v1alpha1" "github.com/aenix-io/etcd-operator/internal/controller" - //+kubebuilder:scaffold:imports + // +kubebuilder:scaffold:imports ) var ( - scheme = runtime.NewScheme() - setupLog = ctrl.Log.WithName("setup") + scheme = runtime.NewScheme() + signals = []os.Signal{os.Interrupt, syscall.SIGTERM} ) func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(etcdaenixiov1alpha1.AddToScheme(scheme)) - //+kubebuilder:scaffold:scheme + // +kubebuilder:scaffold:scheme } func main() { - var metricsAddr string - var enableLeaderElection bool - var probeAddr string - var secureMetrics bool - var enableHTTP2 bool - flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") - flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") - flag.BoolVar(&enableLeaderElection, "leader-elect", false, - "Enable leader election for controller manager. "+ - "Enabling this will ensure there is only one active controller manager.") - flag.BoolVar(&secureMetrics, "metrics-secure", false, - "If set the metrics endpoint is served securely") - flag.BoolVar(&enableHTTP2, "enable-http2", false, - "If set, HTTP/2 will be enabled for the metrics and webhook servers") - opts := zap.Options{ - Development: true, - } - opts.BindFlags(flag.CommandLine) - flag.Parse() - - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + flags := app.ParseCmdLine() + ctx := signal.NotifyContext(log.Setup(context.TODO(), app.LogParameters(flags)), signals...) + ctrl.SetLogger(logr.FromContextOrDiscard(ctx)) // if the enable-http2 flag is false (the default), http/2 should be disabled // due to its vulnerabilities. More specifically, disabling http/2 will @@ -81,12 +67,12 @@ func main() { // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 // - https://github.com/advisories/GHSA-4374-p667-p6c8 disableHTTP2 := func(c *tls.Config) { - setupLog.Info("disabling http/2") + log.Info(ctx, "disabling http/2") c.NextProtos = []string{"http/1.1"} } - tlsOpts := []func(*tls.Config){} - if !enableHTTP2 { + tlsOpts := make([]func(*tls.Config), 0) + if !flags.EnableHTTP2 { tlsOpts = append(tlsOpts, disableHTTP2) } @@ -97,13 +83,13 @@ func main() { mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, Metrics: metricsserver.Options{ - BindAddress: metricsAddr, - SecureServing: secureMetrics, + BindAddress: flags.MetricsAddress, + SecureServing: flags.SecureMetrics, TLSOpts: tlsOpts, }, WebhookServer: webhookServer, - HealthProbeBindAddress: probeAddr, - LeaderElection: enableLeaderElection, + HealthProbeBindAddress: flags.ProbeAddress, + LeaderElection: flags.LeaderElection, LeaderElectionID: "1b04a718.etcd.aenix.io", // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily // when the Manager ends. This requires the binary to immediately end when the @@ -118,7 +104,7 @@ func main() { // LeaderElectionReleaseOnCancel: true, }) if err != nil { - setupLog.Error(err, "unable to start manager") + log.Error(ctx, err, "unable to setup manager") os.Exit(1) } @@ -126,29 +112,29 @@ func main() { Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "EtcdCluster") + log.Error(ctx, err, "unable to create controller", "controller", "EtcdCluster") os.Exit(1) } - if os.Getenv("ENABLE_WEBHOOKS") != "false" { + if !flags.DisableWebhooks { if err = (&etcdaenixiov1alpha1.EtcdCluster{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "EtcdCluster") + log.Error(ctx, err, "unable to create webhook", "webhook", "EtcdCluster") os.Exit(1) } } - //+kubebuilder:scaffold:builder + // +kubebuilder:scaffold:builder - if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { - setupLog.Error(err, "unable to set up health check") + if err = mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + log.Error(ctx, err, "unable to set up health check") os.Exit(1) } - if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { - setupLog.Error(err, "unable to set up ready check") + if err = mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + log.Error(ctx, err, "unable to set up ready check") os.Exit(1) } - setupLog.Info("starting manager") - if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { - setupLog.Error(err, "problem running manager") + log.Info(ctx, "starting manager") + if err = mgr.Start(ctx); err != nil { + log.Error(ctx, err, "problem running manager") os.Exit(1) } } diff --git a/internal/controller/etcdcluster_controller.go b/internal/controller/etcdcluster_controller.go index d3834494..d916f8dd 100644 --- a/internal/controller/etcdcluster_controller.go +++ b/internal/controller/etcdcluster_controller.go @@ -21,8 +21,8 @@ import ( goerrors "errors" "fmt" + "github.com/aenix-io/etcd-operator/internal/log" policyv1 "k8s.io/api/policy/v1" - "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -55,13 +55,12 @@ type EtcdClusterReconciler struct { // Reconcile checks CR and current cluster state and performs actions to transform current state to desired. func (r *EtcdClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := log.FromContext(ctx) - logger.V(2).Info("reconciling object", "namespaced_name", req.NamespacedName) + log.Debug(ctx, "reconciling object") instance := &etcdaenixiov1alpha1.EtcdCluster{} err := r.Get(ctx, req.NamespacedName, instance) if err != nil { if errors.IsNotFound(err) { - logger.V(2).Info("object not found", "namespaced_name", req.NamespacedName) + log.Debug(ctx, "object not found") return ctrl.Result{}, nil } // Error retrieving object, requeue @@ -78,8 +77,8 @@ func (r *EtcdClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) } // ensure managed resources - if err := r.ensureClusterObjects(ctx, instance); err != nil { - logger.Error(err, "cannot create Cluster auxiliary objects") + if err = r.ensureClusterObjects(ctx, instance); err != nil { + log.Error(ctx, err, "cannot create Cluster auxiliary objects") return r.updateStatusOnErr(ctx, instance, fmt.Errorf("cannot create Cluster auxiliary objects: %w", err)) } @@ -93,7 +92,7 @@ func (r *EtcdClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) // check sts condition clusterReady, err := r.isStatefulSetReady(ctx, instance) if err != nil { - logger.Error(err, "failed to check etcd cluster state") + log.Error(ctx, err, "failed to check etcd cluster state") return r.updateStatusOnErr(ctx, instance, fmt.Errorf("cannot check Cluster readiness: %w", err)) } @@ -159,16 +158,15 @@ func (r *EtcdClusterReconciler) updateStatusOnErr(ctx context.Context, cluster * // updateStatus updates EtcdCluster status and returns error and requeue in case status could not be updated due to conflict func (r *EtcdClusterReconciler) updateStatus(ctx context.Context, cluster *etcdaenixiov1alpha1.EtcdCluster) (ctrl.Result, error) { - logger := log.FromContext(ctx) err := r.Status().Update(ctx, cluster) if err == nil { return ctrl.Result{}, nil } if errors.IsConflict(err) { - logger.V(2).Info("conflict during cluster status update") + log.Debug(ctx, "conflict during cluster status update") return ctrl.Result{Requeue: true}, nil } - logger.Error(err, "cannot update cluster status") + log.Error(ctx, err, "cannot update cluster status") return ctrl.Result{}, err } diff --git a/internal/controller/factory/builders.go b/internal/controller/factory/builders.go index da1cc719..4784c842 100644 --- a/internal/controller/factory/builders.go +++ b/internal/controller/factory/builders.go @@ -20,46 +20,46 @@ import ( "context" "fmt" + "github.com/aenix-io/etcd-operator/internal/log" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" - "sigs.k8s.io/controller-runtime/pkg/log" ) +func contextWithGVK(ctx context.Context, resource client.Object, scheme *runtime.Scheme) (context.Context, error) { + gvk, err := apiutil.GVKForObject(resource, scheme) + if err != nil { + return nil, fmt.Errorf("failed to get GVK: %w", err) + } + ctx = log.WithValues(ctx, "group", gvk.GroupVersion().String(), "kind", gvk.Kind, "name", resource.GetName()) + return ctx, nil +} + func reconcileOwnedResource(ctx context.Context, c client.Client, resource client.Object) error { if resource == nil { return fmt.Errorf("resource cannot be nil") } - gvk, err := apiutil.GVKForObject(resource, c.Scheme()) - if err != nil { - return fmt.Errorf("failed to get GVK: %w", err) - } - logger := log.FromContext(ctx).WithValues("group", gvk.GroupVersion().String(), "kind", gvk.Kind, "name", resource.GetName()) - logger.V(2).Info("reconciling owned resource") + log.Debug(ctx, "reconciling owned resource") base := resource.DeepCopyObject().(client.Object) - err = c.Get(ctx, client.ObjectKeyFromObject(resource), base) + err := c.Get(ctx, client.ObjectKeyFromObject(resource), base) if err == nil { - logger.V(2).Info("updating owned resource") + log.Debug(ctx, "updating owned resource") resource.SetAnnotations(labels.Merge(base.GetAnnotations(), resource.GetAnnotations())) resource.SetResourceVersion(base.GetResourceVersion()) - logger.V(2).Info("owned resource annotations merged", "annotations", resource.GetAnnotations()) + log.Debug(ctx, "owned resource annotations merged", "annotations", resource.GetAnnotations()) return c.Update(ctx, resource) } if errors.IsNotFound(err) { - logger.V(2).Info("creating new owned resource") + log.Debug(ctx, "creating new owned resource") return c.Create(ctx, resource) } return fmt.Errorf("error getting owned resource: %w", err) } func deleteOwnedResource(ctx context.Context, c client.Client, resource client.Object) error { - gvk, err := apiutil.GVKForObject(resource, c.Scheme()) - if err != nil { - return err - } - logger := log.FromContext(ctx).WithValues("group", gvk.GroupVersion().String(), "kind", gvk.Kind, "name", resource.GetName()) - logger.V(2).Info("deleting owned resource") + log.Debug(ctx, "deleting owned resource") return client.IgnoreNotFound(c.Delete(ctx, resource)) } diff --git a/internal/controller/factory/configMap.go b/internal/controller/factory/configMap.go index 5c7bbc38..66862cfd 100644 --- a/internal/controller/factory/configMap.go +++ b/internal/controller/factory/configMap.go @@ -21,11 +21,11 @@ import ( "fmt" etcdaenixiov1alpha1 "github.com/aenix-io/etcd-operator/api/v1alpha1" + "github.com/aenix-io/etcd-operator/internal/log" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" ) func GetClusterStateConfigMapName(cluster *etcdaenixiov1alpha1.EtcdCluster) string { @@ -37,6 +37,7 @@ func CreateOrUpdateClusterStateConfigMap( cluster *etcdaenixiov1alpha1.EtcdCluster, rclient client.Client, ) error { + var err error initialCluster := "" clusterService := fmt.Sprintf("%s.%s.svc:2380", GetHeadlessServiceName(cluster), cluster.Namespace) for i := int32(0); i < *cluster.Spec.Replicas; i++ { @@ -49,7 +50,6 @@ func CreateOrUpdateClusterStateConfigMap( ) } - logger := log.FromContext(ctx) configMap := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Namespace: cluster.Namespace, @@ -61,13 +61,17 @@ func CreateOrUpdateClusterStateConfigMap( "ETCD_INITIAL_CLUSTER_TOKEN": cluster.Name + "-" + cluster.Namespace, }, } + ctx, err = contextWithGVK(ctx, configMap, rclient.Scheme()) + if err != nil { + return err + } if isEtcdClusterReady(cluster) { // update cluster state to existing - logger.V(2).Info("updating cluster state", "cluster_name", cluster.Name) + log.Debug(ctx, "updating cluster state") configMap.Data["ETCD_INITIAL_CLUSTER_STATE"] = "existing" } - logger.V(2).Info("configmap spec generated", "cm_name", configMap.Name, "cm_spec", configMap.Data) + log.Debug(ctx, "configmap data generated", "data", configMap.Data) if err := ctrl.SetControllerReference(cluster, configMap, rclient.Scheme()); err != nil { return fmt.Errorf("cannot set controller reference: %w", err) diff --git a/internal/controller/factory/pdb.go b/internal/controller/factory/pdb.go index efadbb93..5646c56b 100644 --- a/internal/controller/factory/pdb.go +++ b/internal/controller/factory/pdb.go @@ -21,13 +21,13 @@ import ( "fmt" etcdaenixiov1alpha1 "github.com/aenix-io/etcd-operator/api/v1alpha1" + "github.com/aenix-io/etcd-operator/internal/log" v1 "k8s.io/api/policy/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" ) func CreateOrUpdatePdb( @@ -35,35 +35,35 @@ func CreateOrUpdatePdb( cluster *etcdaenixiov1alpha1.EtcdCluster, rclient client.Client, ) error { - if cluster.Spec.PodDisruptionBudgetTemplate == nil { - return deleteOwnedResource(ctx, rclient, &v1.PodDisruptionBudget{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: cluster.Namespace, - Name: cluster.Name, - }}) - } - - logger := log.FromContext(ctx) pdb := &v1.PodDisruptionBudget{ ObjectMeta: metav1.ObjectMeta{ Namespace: cluster.Namespace, Name: cluster.Name, }, - Spec: v1.PodDisruptionBudgetSpec{ - MinAvailable: cluster.Spec.PodDisruptionBudgetTemplate.Spec.MinAvailable, - MaxUnavailable: cluster.Spec.PodDisruptionBudgetTemplate.Spec.MaxUnavailable, - Selector: &metav1.LabelSelector{ - MatchLabels: NewLabelsBuilder().WithName().WithInstance(cluster.Name).WithManagedBy(), - }, - UnhealthyPodEvictionPolicy: ptr.To(v1.IfHealthyBudget), + } + ctx, err := contextWithGVK(ctx, pdb, rclient.Scheme()) + if err != nil { + return err + } + + if cluster.Spec.PodDisruptionBudgetTemplate == nil { + return deleteOwnedResource(ctx, rclient, pdb) + } + + pdb.Spec = v1.PodDisruptionBudgetSpec{ + MinAvailable: cluster.Spec.PodDisruptionBudgetTemplate.Spec.MinAvailable, + MaxUnavailable: cluster.Spec.PodDisruptionBudgetTemplate.Spec.MaxUnavailable, + Selector: &metav1.LabelSelector{ + MatchLabels: NewLabelsBuilder().WithName().WithInstance(cluster.Name).WithManagedBy(), }, + UnhealthyPodEvictionPolicy: ptr.To(v1.IfHealthyBudget), } // if both are nil, calculate minAvailable based on number of replicas in the cluster if pdb.Spec.MinAvailable == nil && pdb.Spec.MaxUnavailable == nil { pdb.Spec.MinAvailable = ptr.To(intstr.FromInt32(int32(cluster.CalculateQuorumSize()))) } - logger.V(2).Info("pdb spec generated", "pdb_name", pdb.Name, "pdb_spec", pdb.Spec) + log.Debug(ctx, "pdb spec generated", "spec", pdb.Spec) if err := ctrl.SetControllerReference(cluster, pdb, rclient.Scheme()); err != nil { return fmt.Errorf("cannot set controller reference: %w", err) diff --git a/internal/controller/factory/statefulset.go b/internal/controller/factory/statefulset.go index 9266ebbb..577b2dfc 100644 --- a/internal/controller/factory/statefulset.go +++ b/internal/controller/factory/statefulset.go @@ -22,6 +22,7 @@ import ( "math" "strconv" + "github.com/aenix-io/etcd-operator/internal/log" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -29,7 +30,6 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" etcdaenixiov1alpha1 "github.com/aenix-io/etcd-operator/api/v1alpha1" "github.com/aenix-io/etcd-operator/internal/k8sutils" @@ -105,8 +105,11 @@ func CreateOrUpdateStatefulSet( VolumeClaimTemplates: volumeClaimTemplates, }, } - logger := log.FromContext(ctx) - logger.V(2).Info("statefulset spec generated", "sts_name", statefulSet.Name, "sts_spec", statefulSet.Spec) + ctx, err = contextWithGVK(ctx, statefulSet, rclient.Scheme()) + if err != nil { + return err + } + log.Debug(ctx, "statefulset spec generated", "spec", statefulSet.Spec) if err = ctrl.SetControllerReference(cluster, statefulSet, rclient.Scheme()); err != nil { return fmt.Errorf("cannot set controller reference: %w", err) diff --git a/internal/controller/factory/svc.go b/internal/controller/factory/svc.go index 9d857dd6..8a2ffb63 100644 --- a/internal/controller/factory/svc.go +++ b/internal/controller/factory/svc.go @@ -20,12 +20,12 @@ import ( "context" "fmt" + "github.com/aenix-io/etcd-operator/internal/log" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" etcdaenixiov1alpha1 "github.com/aenix-io/etcd-operator/api/v1alpha1" "github.com/aenix-io/etcd-operator/internal/k8sutils" @@ -52,7 +52,6 @@ func CreateOrUpdateHeadlessService( cluster *etcdaenixiov1alpha1.EtcdCluster, rclient client.Client, ) error { - logger := log.FromContext(ctx) var err error metadata := metav1.ObjectMeta{ @@ -81,10 +80,13 @@ func CreateOrUpdateHeadlessService( PublishNotReadyAddresses: true, }, } + ctx, err = contextWithGVK(ctx, svc, rclient.Scheme()) + if err != nil { + return err + } + log.Debug(ctx, "cluster service spec generated", "spec", svc.Spec) - logger.V(2).Info("cluster service spec generated", "svc_name", svc.Name, "svc_spec", svc.Spec) - - if err := ctrl.SetControllerReference(cluster, svc, rclient.Scheme()); err != nil { + if err = ctrl.SetControllerReference(cluster, svc, rclient.Scheme()); err != nil { return fmt.Errorf("cannot set controller reference: %w", err) } @@ -96,7 +98,6 @@ func CreateOrUpdateClientService( cluster *etcdaenixiov1alpha1.EtcdCluster, rclient client.Client, ) error { - logger := log.FromContext(ctx) var err error svc := corev1.Service{ @@ -123,10 +124,13 @@ func CreateOrUpdateClientService( return fmt.Errorf("cannot strategic-merge base svc with serviceTemplate: %w", err) } } + ctx, err = contextWithGVK(ctx, &svc, rclient.Scheme()) + if err != nil { + return err + } + log.Debug(ctx, "client service spec generated", "spec", svc.Spec) - logger.V(2).Info("client service spec generated", "svc_name", svc.Name, "svc_spec", svc.Spec) - - if err := ctrl.SetControllerReference(cluster, &svc, rclient.Scheme()); err != nil { + if err = ctrl.SetControllerReference(cluster, &svc, rclient.Scheme()); err != nil { return fmt.Errorf("cannot set controller reference: %w", err) } diff --git a/internal/log/logger.go b/internal/log/logger.go new file mode 100644 index 00000000..9faae96a --- /dev/null +++ b/internal/log/logger.go @@ -0,0 +1,95 @@ +/* +Copyright 2024 The etcd-operator 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. +*/ + +package log + +import ( + "context" + "log/slog" + "os" + + "github.com/go-logr/logr" + "go.uber.org/zap" + "go.uber.org/zap/exp/zapslog" + "go.uber.org/zap/zapcore" +) + +func mapLogLevel(level string) zapcore.Level { + switch level { + case "debug": + return zapcore.DebugLevel + case "info": + return zapcore.InfoLevel + case "warn": + return zapcore.WarnLevel + case "error": + return zapcore.ErrorLevel + default: + return zapcore.InfoLevel + } +} + +type Parameters struct { + LogLevel string + StacktraceLevel string + Development bool +} + +func Setup(ctx context.Context, p Parameters) context.Context { + encoderConfig := zapcore.EncoderConfig{ + MessageKey: "message", + LevelKey: "level", + TimeKey: "time", + CallerKey: "caller", + StacktraceKey: "stacktrace", + EncodeLevel: zapcore.CapitalLevelEncoder, + EncodeTime: zapcore.RFC3339TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + EncodeName: zapcore.FullNameEncoder, + } + encoder := zapcore.NewJSONEncoder(encoderConfig) + writer := os.Stderr + if p.Development { + encoder = zapcore.NewConsoleEncoder(encoderConfig) + writer = os.Stdout + } + + core := zapcore.NewCore(encoder, writer, mapLogLevel(p.LogLevel)) + logger := zap.New(core, zap.AddStacktrace(mapLogLevel(p.StacktraceLevel))) + l := slog.New(zapslog.NewHandler(logger.Core(), &zapslog.HandlerOptions{AddSource: p.Development})) + return logr.NewContextWithSlogLogger(ctx, l) +} + +func Info(ctx context.Context, msg string, keysAndValues ...interface{}) { + logr.FromContextAsSlogLogger(ctx).With(keysAndValues...).Info(msg) +} + +func Debug(ctx context.Context, msg string, keysAndValues ...interface{}) { + logr.FromContextAsSlogLogger(ctx).With(keysAndValues...).Debug(msg) +} + +func Warn(ctx context.Context, msg string, keysAndValues ...interface{}) { + logr.FromContextAsSlogLogger(ctx).With(keysAndValues...).Warn(msg) +} + +func Error(ctx context.Context, err error, msg string, keysAndValues ...interface{}) { + logr.FromContextAsSlogLogger(ctx).With(keysAndValues...).Error(msg, slog.Any("error", err)) +} + +func WithValues(ctx context.Context, keysAndValues ...interface{}) context.Context { + return logr.NewContextWithSlogLogger(ctx, logr.FromContextAsSlogLogger(ctx).With(keysAndValues...)) +} diff --git a/internal/signal/signal.go b/internal/signal/signal.go new file mode 100644 index 00000000..4e89a6f8 --- /dev/null +++ b/internal/signal/signal.go @@ -0,0 +1,36 @@ +/* +Copyright 2024 The etcd-operator 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. +*/ + +package signal + +import ( + "context" + "os" + "os/signal" +) + +func NotifyContext(parent context.Context, signals ...os.Signal) context.Context { + ctx, cancel := context.WithCancel(parent) + c := make(chan os.Signal, 2) + signal.Notify(c, signals...) + go func() { + <-c + cancel() + <-c + os.Exit(1) + }() + return ctx +} From a8c04d291e2dd7f1bc4f09b593023d9622e3d534 Mon Sep 17 00:00:00 2001 From: Artem Bortnikov Date: Tue, 28 May 2024 18:04:18 +0300 Subject: [PATCH 2/3] fix panic caused by nil logger in tests Signed-off-by: Artem Bortnikov --- go.mod | 36 +++++--- go.sum | 91 ++++++++++++++----- .../controller/etcdcluster_controller_test.go | 8 +- internal/controller/factory/builders.go | 3 + internal/controller/factory/configmap_test.go | 10 +- internal/controller/factory/pdb_test.go | 14 +-- .../controller/factory/statefulset_test.go | 16 ++-- internal/controller/factory/suite_test.go | 10 ++ internal/controller/factory/svc_test.go | 18 ++-- internal/controller/suite_test.go | 10 ++ 10 files changed, 149 insertions(+), 67 deletions(-) diff --git a/go.mod b/go.mod index ad61e422..a63b0ddf 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,15 @@ module github.com/aenix-io/etcd-operator go 1.22.3 require ( + github.com/go-logr/logr v1.4.1 github.com/google/uuid v1.6.0 github.com/onsi/ginkgo/v2 v2.19.0 github.com/onsi/gomega v1.33.1 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.19.0 go.etcd.io/etcd/client/v3 v3.5.14 + go.uber.org/zap v1.26.0 + go.uber.org/zap/exp v0.2.0 k8s.io/api v0.30.1 k8s.io/apimachinery v0.30.1 k8s.io/client-go v0.30.1 @@ -19,11 +24,10 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect @@ -36,40 +40,48 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect go.etcd.io/etcd/api/v3 v3.5.14 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.26.0 // indirect - golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.25.0 // indirect - golang.org/x/oauth2 v0.12.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/term v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect - golang.org/x/time v0.3.0 // indirect + golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.21.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/grpc v1.59.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect + google.golang.org/grpc v1.62.1 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.30.1 // indirect diff --git a/go.sum b/go.sum index 8e3b8a30..0bb10b44 100644 --- a/go.sum +++ b/go.sum @@ -8,14 +8,17 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= @@ -35,11 +38,13 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -50,6 +55,8 @@ github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQN github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -65,10 +72,14 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -80,10 +91,13 @@ github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= @@ -94,19 +108,36 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/etcd/api/v3 v3.5.14 h1:vHObSCxyB9zlF60w7qzAdTcGaglbJOpSj1Xj9+WGxq0= go.etcd.io/etcd/api/v3 v3.5.14/go.mod h1:BmtWcRlQvwa1h3G2jvKYwIQy4PkHlDej5t7uLMUdJUU= go.etcd.io/etcd/client/pkg/v3 v3.5.14 h1:SaNH6Y+rVEdxfpA2Jr5wkEvN6Zykme5+YnbCkxvuWxQ= @@ -119,43 +150,57 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs= +go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= -golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -164,16 +209,16 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= -google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= -google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q= -google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -181,6 +226,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/internal/controller/etcdcluster_controller_test.go b/internal/controller/etcdcluster_controller_test.go index 8f67311b..83f6c79c 100644 --- a/internal/controller/etcdcluster_controller_test.go +++ b/internal/controller/etcdcluster_controller_test.go @@ -37,7 +37,7 @@ var _ = Describe("EtcdCluster Controller", func() { ns *corev1.Namespace ) - BeforeEach(func(ctx SpecContext) { + BeforeEach(func() { reconciler = &EtcdClusterReconciler{ Client: k8sClient, Scheme: k8sClient.Scheme(), @@ -63,7 +63,7 @@ var _ = Describe("EtcdCluster Controller", func() { err error ) - BeforeEach(func(ctx SpecContext) { + BeforeEach(func() { etcdcluster = etcdaenixiov1alpha1.EtcdCluster{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-etcdcluster-", @@ -110,7 +110,7 @@ var _ = Describe("EtcdCluster Controller", func() { DeferCleanup(k8sClient.Delete, &statefulSet) }) - It("should reconcile a new EtcdCluster", func(ctx SpecContext) { + It("should reconcile a new EtcdCluster", func() { By("reconciling the EtcdCluster", func() { _, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&etcdcluster)}) Expect(err).ToNot(HaveOccurred()) @@ -142,7 +142,7 @@ var _ = Describe("EtcdCluster Controller", func() { }) }) - It("should successfully reconcile the resource twice and mark as ready", func(ctx SpecContext) { + It("should successfully reconcile the resource twice and mark as ready", func() { By("reconciling the EtcdCluster", func() { _, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&etcdcluster)}) Expect(err).ToNot(HaveOccurred()) diff --git a/internal/controller/factory/builders.go b/internal/controller/factory/builders.go index 4784c842..9fa9c4a0 100644 --- a/internal/controller/factory/builders.go +++ b/internal/controller/factory/builders.go @@ -29,6 +29,9 @@ import ( ) func contextWithGVK(ctx context.Context, resource client.Object, scheme *runtime.Scheme) (context.Context, error) { + if resource == nil { + return nil, fmt.Errorf("resource cannot be nil") + } gvk, err := apiutil.GVKForObject(resource, scheme) if err != nil { return nil, fmt.Errorf("failed to get GVK: %w", err) diff --git a/internal/controller/factory/configmap_test.go b/internal/controller/factory/configmap_test.go index 2b83f994..2d744600 100644 --- a/internal/controller/factory/configmap_test.go +++ b/internal/controller/factory/configmap_test.go @@ -34,7 +34,7 @@ import ( var _ = Describe("CreateOrUpdateClusterStateConfigMap handlers", func() { var ns *corev1.Namespace - BeforeEach(func(ctx SpecContext) { + BeforeEach(func() { ns = &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-", @@ -52,7 +52,7 @@ var _ = Describe("CreateOrUpdateClusterStateConfigMap handlers", func() { err error ) - BeforeEach(func(ctx SpecContext) { + BeforeEach(func() { etcdcluster = etcdaenixiov1alpha1.EtcdCluster{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-etcdcluster-", @@ -75,7 +75,7 @@ var _ = Describe("CreateOrUpdateClusterStateConfigMap handlers", func() { } }) - AfterEach(func(ctx SpecContext) { + AfterEach(func() { err = Get(&configMap)() if err == nil { Expect(k8sClient.Delete(ctx, &configMap)).Should(Succeed()) @@ -84,7 +84,7 @@ var _ = Describe("CreateOrUpdateClusterStateConfigMap handlers", func() { } }) - It("should successfully ensure the configmap", func(ctx SpecContext) { + It("should successfully ensure the configmap", func() { var configMapUID types.UID By("processing new etcd cluster", func() { Expect(CreateOrUpdateClusterStateConfigMap(ctx, &etcdcluster, k8sClient)).To(Succeed()) @@ -114,7 +114,7 @@ var _ = Describe("CreateOrUpdateClusterStateConfigMap handlers", func() { }) }) - It("should fail to create the configmap with invalid owner reference", func(ctx SpecContext) { + It("should fail to create the configmap with invalid owner reference", func() { Expect(CreateOrUpdateClusterStateConfigMap(ctx, &etcdcluster, clientWithEmptyScheme)).NotTo(Succeed()) }) }) diff --git a/internal/controller/factory/pdb_test.go b/internal/controller/factory/pdb_test.go index 436a11cf..19d3a09c 100644 --- a/internal/controller/factory/pdb_test.go +++ b/internal/controller/factory/pdb_test.go @@ -34,7 +34,7 @@ import ( var _ = Describe("CreateOrUpdatePdb handlers", func() { var ns *corev1.Namespace - BeforeEach(func(ctx SpecContext) { + BeforeEach(func() { ns = &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-", @@ -52,7 +52,7 @@ var _ = Describe("CreateOrUpdatePdb handlers", func() { err error ) - BeforeEach(func(ctx SpecContext) { + BeforeEach(func() { etcdcluster = etcdaenixiov1alpha1.EtcdCluster{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-etcdcluster-", @@ -76,7 +76,7 @@ var _ = Describe("CreateOrUpdatePdb handlers", func() { } }) - AfterEach(func(ctx SpecContext) { + AfterEach(func() { err = Get(&podDisruptionBudget)() if err == nil { Expect(k8sClient.Delete(ctx, &podDisruptionBudget)).Should(Succeed()) @@ -85,7 +85,7 @@ var _ = Describe("CreateOrUpdatePdb handlers", func() { } }) - It("should create PDB with pre-filled data", func(ctx SpecContext) { + It("should create PDB with pre-filled data", func() { etcdcluster.Spec.PodDisruptionBudgetTemplate.Spec.MinAvailable = ptr.To(intstr.FromInt32(int32(3))) Expect(CreateOrUpdatePdb(ctx, &etcdcluster, k8sClient)).To(Succeed()) Eventually(Get(&podDisruptionBudget)).Should(Succeed()) @@ -94,7 +94,7 @@ var _ = Describe("CreateOrUpdatePdb handlers", func() { Expect(podDisruptionBudget.Spec.MaxUnavailable).To(BeNil()) }) - It("should create PDB with empty data", func(ctx SpecContext) { + It("should create PDB with empty data", func() { Expect(CreateOrUpdatePdb(ctx, &etcdcluster, k8sClient)).To(Succeed()) Eventually(Get(&podDisruptionBudget)).Should(Succeed()) Expect(etcdcluster.Spec.PodDisruptionBudgetTemplate.Spec.MinAvailable).To(BeNil()) @@ -102,12 +102,12 @@ var _ = Describe("CreateOrUpdatePdb handlers", func() { Expect(podDisruptionBudget.Spec.MaxUnavailable).To(BeNil()) }) - It("should skip deletion of PDB if not filled and not exist", func(ctx SpecContext) { + It("should skip deletion of PDB if not filled and not exist", func() { etcdcluster.Spec.PodDisruptionBudgetTemplate = nil Expect(CreateOrUpdatePdb(ctx, &etcdcluster, k8sClient)).NotTo(HaveOccurred()) }) - It("should delete created PDB after updating CR", func(ctx SpecContext) { + It("should delete created PDB after updating CR", func() { Expect(CreateOrUpdatePdb(ctx, &etcdcluster, k8sClient)).To(Succeed()) Eventually(Get(&podDisruptionBudget)).Should(Succeed()) etcdcluster.Spec.PodDisruptionBudgetTemplate = nil diff --git a/internal/controller/factory/statefulset_test.go b/internal/controller/factory/statefulset_test.go index ab522bde..be36bd97 100644 --- a/internal/controller/factory/statefulset_test.go +++ b/internal/controller/factory/statefulset_test.go @@ -37,7 +37,7 @@ import ( var _ = Describe("CreateOrUpdateStatefulSet handler", func() { var ns *corev1.Namespace - BeforeEach(func(ctx SpecContext) { + BeforeEach(func() { ns = &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-", @@ -55,7 +55,7 @@ var _ = Describe("CreateOrUpdateStatefulSet handler", func() { err error ) - BeforeEach(func(ctx SpecContext) { + BeforeEach(func() { etcdcluster = etcdaenixiov1alpha1.EtcdCluster{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-etcdcluster-", @@ -78,7 +78,7 @@ var _ = Describe("CreateOrUpdateStatefulSet handler", func() { } }) - AfterEach(func(ctx SpecContext) { + AfterEach(func() { err = Get(&statefulSet)() if err == nil { Expect(k8sClient.Delete(ctx, &statefulSet)).Should(Succeed()) @@ -87,14 +87,14 @@ var _ = Describe("CreateOrUpdateStatefulSet handler", func() { } }) - It("should successfully ensure the statefulSet with empty spec", func(ctx SpecContext) { + It("should successfully ensure the statefulSet with empty spec", func() { Expect(CreateOrUpdateStatefulSet(ctx, &etcdcluster, k8sClient)).To(Succeed()) Eventually(Object(&statefulSet)).Should( HaveField("Spec.Replicas", Equal(etcdcluster.Spec.Replicas)), ) }) - It("should successfully ensure the statefulSet with filled spec", func(ctx SpecContext) { + It("should successfully ensure the statefulSet with filled spec", func() { etcdcluster.Spec.Storage = etcdaenixiov1alpha1.StorageSpec{ VolumeClaimTemplate: etcdaenixiov1alpha1.EmbeddedPersistentVolumeClaim{ EmbeddedObjectMetadata: etcdaenixiov1alpha1.EmbeddedObjectMetadata{ @@ -276,7 +276,7 @@ var _ = Describe("CreateOrUpdateStatefulSet handler", func() { }) }) - It("should successfully override probes", func(ctx SpecContext) { + It("should successfully override probes", func() { etcdcluster.Spec.PodTemplate.Spec = corev1.PodSpec{ Containers: []corev1.Container{ { @@ -353,7 +353,7 @@ var _ = Describe("CreateOrUpdateStatefulSet handler", func() { }) }) - It("should successfully create statefulSet with emptyDir", func(ctx SpecContext) { + It("should successfully create statefulSet with emptyDir", func() { size := resource.MustParse("1Gi") etcdcluster.Spec.Storage = etcdaenixiov1alpha1.StorageSpec{ EmptyDir: &corev1.EmptyDirVolumeSource{ @@ -368,7 +368,7 @@ var _ = Describe("CreateOrUpdateStatefulSet handler", func() { }) }) - It("should fail on creating the statefulset with invalid owner reference", func(ctx SpecContext) { + It("should fail on creating the statefulset with invalid owner reference", func() { Expect(CreateOrUpdateStatefulSet(ctx, &etcdcluster, clientWithEmptyScheme)).NotTo(Succeed()) }) }) diff --git a/internal/controller/factory/suite_test.go b/internal/controller/factory/suite_test.go index 7260b791..b0b47bea 100644 --- a/internal/controller/factory/suite_test.go +++ b/internal/controller/factory/suite_test.go @@ -17,11 +17,13 @@ limitations under the License. package factory import ( + "context" "fmt" "path/filepath" "runtime" "testing" + "github.com/aenix-io/etcd-operator/internal/log" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" kruntime "k8s.io/apimachinery/pkg/runtime" @@ -45,6 +47,13 @@ var cfg *rest.Config var k8sClient, clientWithEmptyScheme client.Client var testEnv *envtest.Environment +// global context for test suites +var ctx = log.Setup(context.TODO(), log.Parameters{ + LogLevel: "debug", + StacktraceLevel: "error", + Development: true, +}) + func TestFactories(t *testing.T) { RegisterFailHandler(Fail) @@ -87,6 +96,7 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) SetClient(k8sClient) + SetContext(ctx) }) var _ = AfterSuite(func() { diff --git a/internal/controller/factory/svc_test.go b/internal/controller/factory/svc_test.go index 0e27752c..a2aabaa8 100644 --- a/internal/controller/factory/svc_test.go +++ b/internal/controller/factory/svc_test.go @@ -34,7 +34,7 @@ import ( var _ = Describe("CreateOrUpdateService handlers", func() { var ns *corev1.Namespace - BeforeEach(func(ctx SpecContext) { + BeforeEach(func() { ns = &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-", @@ -53,7 +53,7 @@ var _ = Describe("CreateOrUpdateService handlers", func() { err error ) - BeforeEach(func(ctx SpecContext) { + BeforeEach(func() { etcdcluster = etcdaenixiov1alpha1.EtcdCluster{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-etcdcluster-", @@ -82,7 +82,7 @@ var _ = Describe("CreateOrUpdateService handlers", func() { } }) - AfterEach(func(ctx SpecContext) { + AfterEach(func() { err = Get(&headlessService)() if err == nil { Expect(k8sClient.Delete(ctx, &headlessService)).Should(Succeed()) @@ -97,7 +97,7 @@ var _ = Describe("CreateOrUpdateService handlers", func() { } }) - It("should successfully ensure headless service", func(ctx SpecContext) { + It("should successfully ensure headless service", func() { Expect(CreateOrUpdateHeadlessService(ctx, &etcdcluster, k8sClient)).To(Succeed()) Eventually(Object(&headlessService)).Should(SatisfyAll( HaveField("Spec.Type", Equal(corev1.ServiceTypeClusterIP)), @@ -105,7 +105,7 @@ var _ = Describe("CreateOrUpdateService handlers", func() { )) }) - It("should successfully ensure headless service with custom metadata", func(ctx SpecContext) { + It("should successfully ensure headless service with custom metadata", func() { etcdcluster.Spec.HeadlessServiceTemplate = &etcdaenixiov1alpha1.EmbeddedMetadataResource{ EmbeddedObjectMetadata: etcdaenixiov1alpha1.EmbeddedObjectMetadata{ Name: "headless-name", @@ -129,7 +129,7 @@ var _ = Describe("CreateOrUpdateService handlers", func() { Expect(k8sClient.Delete(ctx, svc)).Should(Succeed()) }) - It("should successfully ensure client service", func(ctx SpecContext) { + It("should successfully ensure client service", func() { Expect(CreateOrUpdateClientService(ctx, &etcdcluster, k8sClient)).To(Succeed()) Eventually(Object(&clientService)).Should(SatisfyAll( HaveField("Spec.Type", Equal(corev1.ServiceTypeClusterIP)), @@ -137,7 +137,7 @@ var _ = Describe("CreateOrUpdateService handlers", func() { )) }) - It("should successfully ensure client service with custom metadata", func(ctx SpecContext) { + It("should successfully ensure client service with custom metadata", func() { etcdcluster.Spec.ServiceTemplate = &etcdaenixiov1alpha1.EmbeddedService{ EmbeddedObjectMetadata: etcdaenixiov1alpha1.EmbeddedObjectMetadata{ Name: "client-name", @@ -161,7 +161,7 @@ var _ = Describe("CreateOrUpdateService handlers", func() { Expect(k8sClient.Delete(ctx, svc)).Should(Succeed()) }) - It("should successfully ensure client service with custom spec", func(ctx SpecContext) { + It("should successfully ensure client service with custom spec", func() { etcdcluster.Spec.ServiceTemplate = &etcdaenixiov1alpha1.EmbeddedService{ Spec: corev1.ServiceSpec{ Type: corev1.ServiceTypeLoadBalancer, @@ -191,7 +191,7 @@ var _ = Describe("CreateOrUpdateService handlers", func() { )) }) - It("should fail on creating the client service with invalid owner reference", func(ctx SpecContext) { + It("should fail on creating the client service with invalid owner reference", func() { Expect(CreateOrUpdateHeadlessService(ctx, &etcdcluster, clientWithEmptyScheme)).NotTo(Succeed()) Expect(CreateOrUpdateClientService(ctx, &etcdcluster, clientWithEmptyScheme)).NotTo(Succeed()) }) diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index fe147008..dccc5bf0 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -17,12 +17,14 @@ limitations under the License. package controller import ( + "context" "fmt" "path/filepath" "runtime" "testing" "time" + "github.com/aenix-io/etcd-operator/internal/log" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "sigs.k8s.io/controller-runtime/pkg/envtest/komega" @@ -45,6 +47,13 @@ var cfg *rest.Config var k8sClient client.Client var testEnv *envtest.Environment +// global context for test suites +var ctx = log.Setup(context.TODO(), log.Parameters{ + LogLevel: "debug", + StacktraceLevel: "error", + Development: true, +}) + func TestControllers(t *testing.T) { SetDefaultEventuallyTimeout(time.Second * 5) RegisterFailHandler(Fail) @@ -85,6 +94,7 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) SetClient(k8sClient) + SetContext(ctx) }) var _ = AfterSuite(func() { From 0782ef6532f2f2f3985708c834bb861d5f89ebba Mon Sep 17 00:00:00 2001 From: Artem Bortnikov Date: Thu, 6 Jun 2024 12:40:16 +0300 Subject: [PATCH 3/3] update according to code review notes Signed-off-by: Artem Bortnikov --- internal/controller/factory/builders.go | 13 ++++++++ internal/controller/factory/pdb.go | 42 ++++++++++++++----------- internal/log/logger.go | 19 +++++++++++ internal/signal/signal.go | 12 +++++++ 4 files changed, 68 insertions(+), 18 deletions(-) diff --git a/internal/controller/factory/builders.go b/internal/controller/factory/builders.go index 9fa9c4a0..8f152377 100644 --- a/internal/controller/factory/builders.go +++ b/internal/controller/factory/builders.go @@ -28,6 +28,16 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/apiutil" ) +// contextWithGVK returns a new context with the GroupVersionKind (GVK) information +// of the given resource added to the context values. It uses the provided scheme +// to determine the GVK. +// If the resource is nil, it returns an error with the message "resource cannot be nil". +// If there is an error while obtaining the GVK, it returns an error with the message +// "failed to get GVK" followed by the detailed error message. +// The context value is updated with the following key-value pairs: +// - "group": GVK's GroupVersion string +// - "kind": GVK's Kind +// - "name": Resource's name func contextWithGVK(ctx context.Context, resource client.Object, scheme *runtime.Scheme) (context.Context, error) { if resource == nil { return nil, fmt.Errorf("resource cannot be nil") @@ -63,6 +73,9 @@ func reconcileOwnedResource(ctx context.Context, c client.Client, resource clien } func deleteOwnedResource(ctx context.Context, c client.Client, resource client.Object) error { + if resource == nil { + return fmt.Errorf("resource cannot be nil") + } log.Debug(ctx, "deleting owned resource") return client.IgnoreNotFound(c.Delete(ctx, resource)) } diff --git a/internal/controller/factory/pdb.go b/internal/controller/factory/pdb.go index 5646c56b..e3ef2e3c 100644 --- a/internal/controller/factory/pdb.go +++ b/internal/controller/factory/pdb.go @@ -35,37 +35,43 @@ func CreateOrUpdatePdb( cluster *etcdaenixiov1alpha1.EtcdCluster, rclient client.Client, ) error { + var err error + + if cluster.Spec.PodDisruptionBudgetTemplate == nil { + ctx = log.WithValues(ctx, "group", "policy/v1", "kind", "PodDisruptionBudget", "name", cluster.Name) + return deleteOwnedResource(ctx, rclient, &v1.PodDisruptionBudget{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: cluster.Namespace, + Name: cluster.Name, + }, + }) + } + pdb := &v1.PodDisruptionBudget{ ObjectMeta: metav1.ObjectMeta{ Namespace: cluster.Namespace, Name: cluster.Name, }, - } - ctx, err := contextWithGVK(ctx, pdb, rclient.Scheme()) - if err != nil { - return err - } - - if cluster.Spec.PodDisruptionBudgetTemplate == nil { - return deleteOwnedResource(ctx, rclient, pdb) - } - - pdb.Spec = v1.PodDisruptionBudgetSpec{ - MinAvailable: cluster.Spec.PodDisruptionBudgetTemplate.Spec.MinAvailable, - MaxUnavailable: cluster.Spec.PodDisruptionBudgetTemplate.Spec.MaxUnavailable, - Selector: &metav1.LabelSelector{ - MatchLabels: NewLabelsBuilder().WithName().WithInstance(cluster.Name).WithManagedBy(), + Spec: v1.PodDisruptionBudgetSpec{ + MinAvailable: cluster.Spec.PodDisruptionBudgetTemplate.Spec.MinAvailable, + MaxUnavailable: cluster.Spec.PodDisruptionBudgetTemplate.Spec.MaxUnavailable, + Selector: &metav1.LabelSelector{ + MatchLabels: NewLabelsBuilder().WithName().WithInstance(cluster.Name).WithManagedBy(), + }, + UnhealthyPodEvictionPolicy: ptr.To(v1.IfHealthyBudget), }, - UnhealthyPodEvictionPolicy: ptr.To(v1.IfHealthyBudget), } // if both are nil, calculate minAvailable based on number of replicas in the cluster if pdb.Spec.MinAvailable == nil && pdb.Spec.MaxUnavailable == nil { pdb.Spec.MinAvailable = ptr.To(intstr.FromInt32(int32(cluster.CalculateQuorumSize()))) } - + ctx, err = contextWithGVK(ctx, pdb, rclient.Scheme()) + if err != nil { + return err + } log.Debug(ctx, "pdb spec generated", "spec", pdb.Spec) - if err := ctrl.SetControllerReference(cluster, pdb, rclient.Scheme()); err != nil { + if err = ctrl.SetControllerReference(cluster, pdb, rclient.Scheme()); err != nil { return fmt.Errorf("cannot set controller reference: %w", err) } diff --git a/internal/log/logger.go b/internal/log/logger.go index 9faae96a..3bd4415d 100644 --- a/internal/log/logger.go +++ b/internal/log/logger.go @@ -48,6 +48,20 @@ type Parameters struct { Development bool } +// Setup initializes the logger and returns a new context with the logger attached. +// The logger is configured based on the provided Parameters. The encoder and writer +// are selected based on the Development flag. The LogLevel parameter determines the +// log level of the logger. The StacktraceLevel parameter determines the log level at +// which a stack trace is added to log entries. +// The function does not modify the original context. +// +// Example usage: +// +// ctx := Setup(context.Background(), Parameters{ +// LogLevel: "debug", +// StacktraceLevel: "error", +// Development: true, +// }) func Setup(ctx context.Context, p Parameters) context.Context { encoderConfig := zapcore.EncoderConfig{ MessageKey: "message", @@ -74,22 +88,27 @@ func Setup(ctx context.Context, p Parameters) context.Context { return logr.NewContextWithSlogLogger(ctx, l) } +// Info logs an informational message with optional key-value pairs. func Info(ctx context.Context, msg string, keysAndValues ...interface{}) { logr.FromContextAsSlogLogger(ctx).With(keysAndValues...).Info(msg) } +// Debug logs a debug message with optional key-value pairs. func Debug(ctx context.Context, msg string, keysAndValues ...interface{}) { logr.FromContextAsSlogLogger(ctx).With(keysAndValues...).Debug(msg) } +// Warn logs a warning message with optional key-value pairs. func Warn(ctx context.Context, msg string, keysAndValues ...interface{}) { logr.FromContextAsSlogLogger(ctx).With(keysAndValues...).Warn(msg) } +// Error logs an error message with optional key-value pairs. func Error(ctx context.Context, err error, msg string, keysAndValues ...interface{}) { logr.FromContextAsSlogLogger(ctx).With(keysAndValues...).Error(msg, slog.Any("error", err)) } +// WithValues adds additional key-value pairs to the context's logger. func WithValues(ctx context.Context, keysAndValues ...interface{}) context.Context { return logr.NewContextWithSlogLogger(ctx, logr.FromContextAsSlogLogger(ctx).With(keysAndValues...)) } diff --git a/internal/signal/signal.go b/internal/signal/signal.go index 4e89a6f8..223c080a 100644 --- a/internal/signal/signal.go +++ b/internal/signal/signal.go @@ -22,6 +22,18 @@ import ( "os/signal" ) +// NotifyContext returns a derived context from the parent context with a cancel +// function. It sets up a signal channel to listen for the specified signals and +// cancels the context when any of those signals are received. If a second signal +// is received, it exits the program with a status code of 1. +// +// Example usage: +// +// ctx := signal.NotifyContext(parentContext, os.Interrupt, syscall.SIGTERM) +// go func() { +// <-ctx.Done() +// // Perform cleanup or other tasks before exiting +// }() func NotifyContext(parent context.Context, signals ...os.Signal) context.Context { ctx, cancel := context.WithCancel(parent) c := make(chan os.Signal, 2)