diff --git a/cmd/postgres-operator/main.go b/cmd/postgres-operator/main.go index 2decf235d..79e4d55db 100644 --- a/cmd/postgres-operator/main.go +++ b/cmd/postgres-operator/main.go @@ -14,6 +14,7 @@ import ( "time" "unicode" + "github.com/kelseyhightower/envconfig" "github.com/pkg/errors" "go.opentelemetry.io/otel" uzap "go.uber.org/zap" @@ -280,69 +281,68 @@ func initManager(ctx context.Context) (runtime.Options, error) { options.HealthProbeBindAddress = ":8081" + options.Controller.GroupKindConcurrency = map[string]int{ + "PostgresCluster." + v1beta1.GroupVersion.Group: 1, + "PGUpgrade." + v1beta1.GroupVersion.Group: 1, + "PGAdmin." + v1beta1.GroupVersion.Group: 1, + "PerconaPGCluster." + v2.GroupVersion.Group: 1, + "PerconaPGUpgrade." + v2.GroupVersion.Group: 1, + "PerconaPGBackup." + v2.GroupVersion.Group: 1, + "PerconaPGRestore." + v2.GroupVersion.Group: 1, + } + + // K8SPG-915 + envs := new(envConfig) + if err := envconfig.Process("", envs); err != nil { + return options, errors.Wrap(err, "parse env vars") + } + + options.LeaseDuration = &envs.LeaseDuration + options.RenewDeadline = &envs.RenewDeadline + options.RetryPeriod = &envs.RetryPeriod + options.PprofBindAddress = envs.PprofBindAddress + + options.LeaderElection = envs.LeaderElection + if options.LeaderElection { + options.LeaderElectionID = perconaRuntime.ElectionID + } + // Enable leader elections when configured with a valid Lease.coordination.k8s.io name. // - https://docs.k8s.io/concepts/architecture/leases // - https://releases.k8s.io/v1.30.0/pkg/apis/coordination/validation/validation.go#L26 - if lease := os.Getenv("PGO_CONTROLLER_LEASE_NAME"); len(lease) > 0 { + if lease := envs.LeaderElectionID; options.LeaderElection && len(lease) > 0 { if errs := validation.IsDNS1123Subdomain(lease); len(errs) > 0 { return options, fmt.Errorf("value for PGO_CONTROLLER_LEASE_NAME is invalid: %v", errs) } - options.LeaderElection = true options.LeaderElectionID = lease - options.LeaderElectionNamespace = os.Getenv("PGO_NAMESPACE") - } else { - // K8SPG-761 - options.LeaderElection = true - options.LeaderElectionID = perconaRuntime.ElectionID + options.LeaderElectionNamespace = envs.LeaderElectionNamespace } - // Check PGO_TARGET_NAMESPACE for backwards compatibility with - // "singlenamespace" installations - singlenamespace := strings.TrimSpace(os.Getenv("PGO_TARGET_NAMESPACE")) - - // Check PGO_TARGET_NAMESPACES for non-cluster-wide, multi-namespace - // installations - multinamespace := strings.TrimSpace(os.Getenv("PGO_TARGET_NAMESPACES")) - - // Initialize DefaultNamespaces if any target namespaces are set - if len(singlenamespace) > 0 || len(multinamespace) > 0 { + if len(envs.SingleNamespace) > 0 || len(envs.MultiNamespaces) > 0 { + // Initialize DefaultNamespaces if any target namespaces are set options.Cache.DefaultNamespaces = map[string]runtime.CacheConfig{} - } - if len(singlenamespace) > 0 { - options.Cache.DefaultNamespaces[singlenamespace] = runtime.CacheConfig{} - } - - if len(multinamespace) > 0 { - for _, namespace := range strings.FieldsFunc(multinamespace, func(c rune) bool { - return c != '-' && !unicode.IsLetter(c) && !unicode.IsNumber(c) - }) { - options.Cache.DefaultNamespaces[namespace] = runtime.CacheConfig{} + if len(envs.SingleNamespace) > 0 { + options.Cache.DefaultNamespaces[envs.SingleNamespace] = runtime.CacheConfig{} } - } - options.Controller.GroupKindConcurrency = map[string]int{ - "PostgresCluster." + v1beta1.GroupVersion.Group: 1, - "PGUpgrade." + v1beta1.GroupVersion.Group: 1, - "PGAdmin." + v1beta1.GroupVersion.Group: 1, - "PerconaPGCluster." + v2.GroupVersion.Group: 1, - "PerconaPGUpgrade." + v2.GroupVersion.Group: 1, - "PerconaPGBackup." + v2.GroupVersion.Group: 1, - "PerconaPGRestore." + v2.GroupVersion.Group: 1, - } - - if s := os.Getenv("PGO_WORKERS"); s != "" { - if i, err := strconv.Atoi(s); err == nil && i > 0 { - for kind := range options.Controller.GroupKindConcurrency { - options.Controller.GroupKindConcurrency[kind] = i + if len(envs.MultiNamespaces) > 0 { + for _, namespace := range strings.FieldsFunc(envs.MultiNamespaces, func(c rune) bool { + return c != '-' && !unicode.IsLetter(c) && !unicode.IsNumber(c) + }) { + options.Cache.DefaultNamespaces[namespace] = runtime.CacheConfig{} } - } else { - log.Error(err, "PGO_WORKERS must be a positive number") } } - options.PprofBindAddress = os.Getenv("PPROF_BIND_ADDRESS") + if envs.Workers < 0 { + log.Error(nil, "PGO_WORKERS must be a non-negative number; 0 disables the override") + } else if envs.Workers > 0 { + for kind := range options.Controller.GroupKindConcurrency { + options.Controller.GroupKindConcurrency[kind] = envs.Workers + } + } return options, nil } @@ -516,3 +516,20 @@ func isOpenshift(ctx context.Context, cfg *rest.Config) bool { return false } + +type envConfig struct { + LeaderElection bool `default:"true" envconfig:"PGO_CONTROLLER_LEADER_ELECTION_ENABLED"` + LeaderElectionID string `envconfig:"PGO_CONTROLLER_LEASE_NAME"` + LeaderElectionNamespace string `envconfig:"PGO_NAMESPACE"` + + LeaseDuration time.Duration `default:"60s" envconfig:"PGO_CONTROLLER_LEASE_DURATION"` + RenewDeadline time.Duration `default:"40s" envconfig:"PGO_CONTROLLER_RENEW_DEADLINE"` + RetryPeriod time.Duration `default:"10s" envconfig:"PGO_CONTROLLER_RETRY_PERIOD"` + + SingleNamespace string `envconfig:"PGO_TARGET_NAMESPACE"` + MultiNamespaces string `envconfig:"PGO_TARGET_NAMESPACES"` + + PprofBindAddress string `envconfig:"PPROF_BIND_ADDRESS"` + + Workers int `envconfig:"PGO_WORKERS"` +} diff --git a/cmd/postgres-operator/main_test.go b/cmd/postgres-operator/main_test.go index 38d55b600..30c21f73d 100644 --- a/cmd/postgres-operator/main_test.go +++ b/cmd/postgres-operator/main_test.go @@ -5,7 +5,6 @@ package main import ( - "context" "reflect" "testing" "time" @@ -15,7 +14,7 @@ import ( ) func TestInitManager(t *testing.T) { - ctx := context.Background() + ctx := t.Context() t.Run("Defaults", func(t *testing.T) { options, err := initManager(ctx) assert.NilError(t, err) @@ -39,6 +38,9 @@ func TestInitManager(t *testing.T) { assert.Assert(t, options.Cache.DefaultNamespaces == nil) assert.Assert(t, options.LeaderElection == true) + assert.Assert(t, options.LeaseDuration.Seconds() == 60) + assert.Assert(t, options.RenewDeadline.Seconds() == 40) + assert.Assert(t, options.RetryPeriod.Seconds() == 10) { options.Cache.SyncPeriod = nil @@ -46,6 +48,9 @@ func TestInitManager(t *testing.T) { options.HealthProbeBindAddress = "" options.LeaderElection = false options.LeaderElectionID = "" + options.LeaseDuration = nil + options.RenewDeadline = nil + options.RetryPeriod = nil assert.Assert(t, reflect.ValueOf(options).IsZero(), "expected remaining fields to be unset:\n%+v", options) @@ -62,6 +67,13 @@ func TestInitManager(t *testing.T) { assert.ErrorContains(t, err, "PGO_CONTROLLER_LEASE_NAME") assert.ErrorContains(t, err, "invalid") + assert.Assert(t, options.LeaderElection == true) + assert.Equal(t, options.LeaderElectionNamespace, "") + + t.Setenv("PGO_CONTROLLER_LEADER_ELECTION_ENABLED", "false") + options, err = initManager(ctx) + assert.NilError(t, err) + assert.Assert(t, options.LeaderElection == false) assert.Equal(t, options.LeaderElectionNamespace, "") }) @@ -106,7 +118,11 @@ func TestInitManager(t *testing.T) { t.Setenv("PGO_WORKERS", v) options, err := initManager(ctx) - assert.NilError(t, err) + if v == "3.14" { + assert.ErrorContains(t, err, "parse env vars: envconfig.Process: assigning PGO_WORKERS to Workers: converting '3.14' to type int. details: strconv.ParseInt: parsing \"3.14\": invalid syntax") + } else { + assert.NilError(t, err) + } assert.DeepEqual(t, options.Controller.GroupKindConcurrency, map[string]int{ "PGAdmin.postgres-operator.crunchydata.com": 1, @@ -148,4 +164,16 @@ func TestInitManager(t *testing.T) { assert.NilError(t, err) assert.DeepEqual(t, options.PprofBindAddress, "pprof-addr") }) + + t.Run("Duration options", func(t *testing.T) { + t.Setenv("PGO_CONTROLLER_LEASE_DURATION", "1s") + t.Setenv("PGO_CONTROLLER_RENEW_DEADLINE", "2s") + t.Setenv("PGO_CONTROLLER_RETRY_PERIOD", "3s") + options, err := initManager(ctx) + assert.NilError(t, err) + + assert.Equal(t, options.LeaseDuration.Seconds(), float64(1)) + assert.Equal(t, options.RenewDeadline.Seconds(), float64(2)) + assert.Equal(t, options.RetryPeriod.Seconds(), float64(3)) + }) } diff --git a/config/manager/default/manager.yaml b/config/manager/default/manager.yaml index d7a647692..db9951f80 100644 --- a/config/manager/default/manager.yaml +++ b/config/manager/default/manager.yaml @@ -39,6 +39,16 @@ spec: value: "1" - name: PPROF_BIND_ADDRESS value: "0" + - name: PGO_CONTROLLER_LEADER_ELECTION_ENABLED + value: "true" + - name: PGO_CONTROLLER_LEASE_NAME + value: "" + - name: PGO_CONTROLLER_LEASE_DURATION + value: "60s" + - name: PGO_CONTROLLER_RENEW_DEADLINE + value: "40s" + - name: PGO_CONTROLLER_RETRY_PERIOD + value: "10s" ports: - containerPort: 8080 name: metrics diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml index 87e3aa52f..ced7c4c45 100644 --- a/deploy/bundle.yaml +++ b/deploy/bundle.yaml @@ -54215,6 +54215,16 @@ spec: value: "1" - name: PPROF_BIND_ADDRESS value: "0" + - name: PGO_CONTROLLER_LEADER_ELECTION_ENABLED + value: "true" + - name: PGO_CONTROLLER_LEASE_NAME + value: "" + - name: PGO_CONTROLLER_LEASE_DURATION + value: 60s + - name: PGO_CONTROLLER_RENEW_DEADLINE + value: 40s + - name: PGO_CONTROLLER_RETRY_PERIOD + value: 10s image: docker.io/perconalab/percona-postgresql-operator:main imagePullPolicy: Always livenessProbe: diff --git a/deploy/cw-bundle.yaml b/deploy/cw-bundle.yaml index 11519a814..dda485b41 100644 --- a/deploy/cw-bundle.yaml +++ b/deploy/cw-bundle.yaml @@ -54213,6 +54213,16 @@ spec: value: "1" - name: PPROF_BIND_ADDRESS value: "0" + - name: PGO_CONTROLLER_LEADER_ELECTION_ENABLED + value: "true" + - name: PGO_CONTROLLER_LEASE_NAME + value: "" + - name: PGO_CONTROLLER_LEASE_DURATION + value: 60s + - name: PGO_CONTROLLER_RENEW_DEADLINE + value: 40s + - name: PGO_CONTROLLER_RETRY_PERIOD + value: 10s image: docker.io/perconalab/percona-postgresql-operator:main imagePullPolicy: Always livenessProbe: diff --git a/deploy/cw-operator.yaml b/deploy/cw-operator.yaml index 0a75a83fc..b39e97e92 100644 --- a/deploy/cw-operator.yaml +++ b/deploy/cw-operator.yaml @@ -46,6 +46,16 @@ spec: value: "1" - name: PPROF_BIND_ADDRESS value: "0" + - name: PGO_CONTROLLER_LEADER_ELECTION_ENABLED + value: "true" + - name: PGO_CONTROLLER_LEASE_NAME + value: "" + - name: PGO_CONTROLLER_LEASE_DURATION + value: 60s + - name: PGO_CONTROLLER_RENEW_DEADLINE + value: 40s + - name: PGO_CONTROLLER_RETRY_PERIOD + value: 10s image: docker.io/perconalab/percona-postgresql-operator:main imagePullPolicy: Always livenessProbe: diff --git a/deploy/operator.yaml b/deploy/operator.yaml index 3595d64cf..9dffa041c 100644 --- a/deploy/operator.yaml +++ b/deploy/operator.yaml @@ -49,6 +49,16 @@ spec: value: "1" - name: PPROF_BIND_ADDRESS value: "0" + - name: PGO_CONTROLLER_LEADER_ELECTION_ENABLED + value: "true" + - name: PGO_CONTROLLER_LEASE_NAME + value: "" + - name: PGO_CONTROLLER_LEASE_DURATION + value: 60s + - name: PGO_CONTROLLER_RENEW_DEADLINE + value: 40s + - name: PGO_CONTROLLER_RETRY_PERIOD + value: 10s image: docker.io/perconalab/percona-postgresql-operator:main imagePullPolicy: Always livenessProbe: diff --git a/go.mod b/go.mod index 932440377..6893de356 100644 --- a/go.mod +++ b/go.mod @@ -4,21 +4,26 @@ go 1.25.1 require ( github.com/Percona-Lab/percona-version-service v0.0.0-20230404081016-ea25e30cdcbc + github.com/aws/aws-sdk-go v1.55.8 github.com/go-logr/logr v1.4.3 github.com/go-openapi/errors v0.22.6 github.com/go-openapi/runtime v0.29.2 github.com/go-openapi/strfmt v0.25.0 github.com/go-openapi/swag v0.25.4 github.com/go-openapi/validate v0.25.1 + github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/hashicorp/go-version v1.8.0 + github.com/kelseyhightower/envconfig v1.4.0 github.com/kubernetes-csi/external-snapshotter/client/v8 v8.4.0 github.com/onsi/ginkgo/v2 v2.28.1 github.com/onsi/gomega v1.39.1 github.com/pganalyze/pg_query_go/v6 v6.2.2 github.com/pkg/errors v0.9.1 + github.com/robfig/cron/v3 v3.0.1 github.com/sirupsen/logrus v1.9.4 + github.com/stretchr/testify v1.11.1 github.com/xdg-go/stringprep v1.0.4 go.nhat.io/grpcmock v0.34.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 @@ -30,6 +35,7 @@ require ( go.opentelemetry.io/otel/trace v1.40.0 go.uber.org/zap v1.27.1 golang.org/x/crypto v0.47.0 + golang.org/x/tools v0.41.0 google.golang.org/grpc v1.78.0 gotest.tools/v3 v3.5.2 k8s.io/api v0.35.0 @@ -37,16 +43,31 @@ require ( k8s.io/apimachinery v0.35.0 k8s.io/client-go v0.35.0 k8s.io/component-base v0.35.0 + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 sigs.k8s.io/controller-runtime v0.23.1 sigs.k8s.io/yaml v1.6.0 ) require ( github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/bool64/shared v0.1.6 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/analysis v0.24.1 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/jsonreference v0.21.3 // indirect + github.com/go-openapi/loads v0.23.2 // indirect + github.com/go-openapi/spec v0.22.1 // indirect github.com/go-openapi/swag/cmdutils v0.25.4 // indirect github.com/go-openapi/swag/conv v0.25.4 // indirect github.com/go-openapi/swag/fileutils v0.25.4 // indirect @@ -60,40 +81,12 @@ require ( github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect - github.com/google/btree v1.1.3 // indirect - github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect - github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect - github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.yaml.in/yaml/v2 v2.4.3 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/mod v0.32.0 // indirect - gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect - sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect -) - -require ( - github.com/aws/aws-sdk-go v1.55.8 - github.com/beorn7/perks v1.0.1 // indirect - github.com/blang/semver/v4 v4.0.0 // indirect - github.com/bool64/shared v0.1.6 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.12.2 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/analysis v0.24.1 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.3 // indirect - github.com/go-openapi/loads v0.23.2 // indirect - github.com/go-openapi/spec v0.22.1 // indirect - github.com/golang-jwt/jwt/v5 v5.3.1 github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 github.com/iancoleman/orderedmap v0.3.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -102,25 +95,29 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect - github.com/robfig/cron/v3 v3.0.1 github.com/sergi/go-diff v1.4.0 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/pflag v1.0.9 // indirect - github.com/stretchr/testify v1.11.1 github.com/swaggest/assertjson v1.10.0 // indirect + github.com/x448/float16 v0.8.4 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect go.mongodb.org/mongo-driver v1.17.6 // indirect go.nhat.io/matcher/v2 v2.0.0 // indirect go.nhat.io/wait v0.1.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.40.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/mod v0.32.0 // indirect golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.19.0 // indirect @@ -128,15 +125,16 @@ require ( golang.org/x/term v0.39.0 // indirect golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.41.0 gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 // indirect ) diff --git a/go.sum b/go.sum index fb7bc92f9..4ee0047e2 100644 --- a/go.sum +++ b/go.sum @@ -136,6 +136,8 @@ github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=