From 75db015b55acdeea64d46d484278bcf767a3b015 Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Thu, 11 Jun 2026 13:38:43 +0300 Subject: [PATCH 01/23] feat: implement DVP provisioning strategy with bootstrap and teardown functionality --- cmd/bootstrap-cluster/main.go | 34 ++++++++++ cmd/remove-cluster/main.go | 36 ++++++++++ pkg/{ => clusterprovider}/config/config.go | 2 +- .../config/config_test.go | 0 pkg/clusterprovider/provider/dvp.go | 48 +++++++++++++ pkg/clusterprovider/provider/provider.go | 67 +++++++++++++++++++ 6 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 cmd/bootstrap-cluster/main.go create mode 100644 cmd/remove-cluster/main.go rename pkg/{ => clusterprovider}/config/config.go (92%) rename pkg/{ => clusterprovider}/config/config_test.go (100%) create mode 100644 pkg/clusterprovider/provider/dvp.go create mode 100644 pkg/clusterprovider/provider/provider.go diff --git a/cmd/bootstrap-cluster/main.go b/cmd/bootstrap-cluster/main.go new file mode 100644 index 0000000..f0e620e --- /dev/null +++ b/cmd/bootstrap-cluster/main.go @@ -0,0 +1,34 @@ +// Command bootstrap-cluster is the CI-only entrypoint that provisions a +// cluster. It is a thin wrapper: load ClusterConfig, resolve the strategy's +// Constructor from the provider Registry, build the Provider, then Bootstrap. +package main + +import ( + "context" + "log" + + "github.com/deckhouse/storage-e2e/pkg/clusterprovider/config" + "github.com/deckhouse/storage-e2e/pkg/clusterprovider/provider" +) + +func main() { + cfg, err := config.New() + if err != nil { + log.Fatal("failed to initialize config - ", err) + } + + newProvider, registryGetErr := provider.DefaultRegistry.Get(cfg.ClusterProvider) + if registryGetErr != nil { + log.Fatal("failed to get provider", registryGetErr) + } + + clusterProvider, err := newProvider(cfg) + if err != nil { + log.Fatal("failed to build provider", err) + } + + bootstrapErr := clusterProvider.Bootstrap(context.Background()) + if bootstrapErr != nil { + log.Fatal("failed to bootstrap cluster", bootstrapErr) + } +} diff --git a/cmd/remove-cluster/main.go b/cmd/remove-cluster/main.go new file mode 100644 index 0000000..a009c92 --- /dev/null +++ b/cmd/remove-cluster/main.go @@ -0,0 +1,36 @@ +// Command remove-cluster is the CI-only entrypoint that tears a cluster down. +// It is a thin wrapper: load ClusterConfig, resolve the strategy's Constructor +// from the provider Registry, build the Provider, then run the idempotent +// Teardown (which derives target resources from config, not from bootstrap +// artifacts). +package main + +import ( + "context" + "log" + + "github.com/deckhouse/storage-e2e/pkg/clusterprovider/config" + "github.com/deckhouse/storage-e2e/pkg/clusterprovider/provider" +) + +func main() { + cfg, err := config.New() + if err != nil { + log.Fatal("failed to initialize config - ", err) + } + + newProvider, registryGetErr := provider.DefaultRegistry.Get(cfg.ClusterProvider) + if registryGetErr != nil { + log.Fatal("failed to get provider", registryGetErr) + } + + clusterProvider, err := newProvider(cfg) + if err != nil { + log.Fatal("failed to build provider", err) + } + + teardownErr := clusterProvider.Teardown(context.Background()) + if teardownErr != nil { + log.Fatal("failed to tear down cluster", teardownErr) + } +} diff --git a/pkg/config/config.go b/pkg/clusterprovider/config/config.go similarity index 92% rename from pkg/config/config.go rename to pkg/clusterprovider/config/config.go index ee888ba..e275a9c 100644 --- a/pkg/config/config.go +++ b/pkg/clusterprovider/config/config.go @@ -19,7 +19,7 @@ package config import "github.com/caarlos0/env/v11" type ClusterConfig struct { - ClusterProvider string `env:"TEST_CLUSTER_PROVIDER,required"` + ClusterProvider string `env:"E2E_TEST_CLUSTER_PROVIDER,required"` } func New() (*ClusterConfig, error) { diff --git a/pkg/config/config_test.go b/pkg/clusterprovider/config/config_test.go similarity index 100% rename from pkg/config/config_test.go rename to pkg/clusterprovider/config/config_test.go diff --git a/pkg/clusterprovider/provider/dvp.go b/pkg/clusterprovider/provider/dvp.go new file mode 100644 index 0000000..6c8b000 --- /dev/null +++ b/pkg/clusterprovider/provider/dvp.go @@ -0,0 +1,48 @@ +package provider + +import ( + "context" + + "github.com/caarlos0/env/v11" + "github.com/deckhouse/storage-e2e/pkg/clusterprovider/config" +) + +func init() { + DefaultRegistry.Register(ModeDvp, newDVPProvider) +} + +// dvpProvider provisions clusters using the DVP (Deckhouse Virtualization +// Platform) strategy. +type dvpProvider struct { + cfg *config.ClusterConfig + dvpConf *dvpConfig +} + +type dvpConfig struct { + ClusterBootstrapConfig string `env:"YAML_CONFIG_FILENAME,required"` +} + +func (p *dvpProvider) Name() string { return ModeDvp } + +func (p *dvpProvider) Bootstrap(ctx context.Context) error { + panic("not implemented") +} + +func (p *dvpProvider) Teardown(ctx context.Context) error { + // TODO: implement — idempotent teardown by deterministic cluster name. + panic("not implemented") +} + +// newDVPProvider builds a dvpProvider, loading the DVP-specific env. Registered +// as the dvp strategy's Constructor. +func newDVPProvider(config *config.ClusterConfig) (Provider, error) { + dvpConf := &dvpConfig{} + if err := env.Parse(dvpConf); err != nil { + return nil, err + } + + return &dvpProvider{ + cfg: config, + dvpConf: dvpConf, + }, nil +} diff --git a/pkg/clusterprovider/provider/provider.go b/pkg/clusterprovider/provider/provider.go new file mode 100644 index 0000000..fb93044 --- /dev/null +++ b/pkg/clusterprovider/provider/provider.go @@ -0,0 +1,67 @@ +// Package provider contains the CI-only provisioning strategies. It is used +// exclusively by the cmd/* binaries to bootstrap and tear down clusters; tests +// never import it. +// +// Strategies self-register a Constructor into a Registry via init (Open/Closed). +// Only two strategies exist: dvp and commander. +package provider + +import ( + "context" + "fmt" + "sync" + + "github.com/deckhouse/storage-e2e/pkg/clusterprovider/config" +) + +const ( + ModeDvp = "dvp" + ModeCommander = "commander" +) + +// Provider is a provisioning strategy. Bootstrap returns Endpoints (not a +// Cluster); Teardown is idempotent and derives target resources from config. +type Provider interface { + Name() string + Bootstrap(ctx context.Context) error + Teardown(ctx context.Context) error +} + +// Constructor builds a Provider, loading its own strategy-specific env. It runs +// lazily — only for the strategy selected at runtime — so unselected strategies +// never read their env. Validate on the resulting Provider stays a pure check +// (no loading, no I/O). +type Constructor func(config *config.ClusterConfig) (Provider, error) + +// Registry maps strategy names to their Constructor. +type Registry struct { + mu sync.RWMutex + constructors map[string]Constructor +} + +// NewRegistry returns an empty Registry. +func NewRegistry() *Registry { + return &Registry{constructors: make(map[string]Constructor)} +} + +// DefaultRegistry is the package-level registry strategies self-register into. +var DefaultRegistry = NewRegistry() + +// Register adds a Constructor under name. A later registration with the same +// name overwrites the earlier one. +func (r *Registry) Register(name string, c Constructor) { + r.mu.Lock() + defer r.mu.Unlock() + r.constructors[name] = c +} + +// Get returns the Constructor registered under name. +func (r *Registry) Get(name string) (Constructor, error) { + r.mu.RLock() + defer r.mu.RUnlock() + c, ok := r.constructors[name] + if !ok { + return nil, fmt.Errorf("provider %q is not registered", name) + } + return c, nil +} From 5151973894714e48475fe0ddc630cdea591b73c0 Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Sat, 13 Jun 2026 20:24:08 +0300 Subject: [PATCH 02/23] feat: refactor cluster provider package and add DVP provisioning strategy --- cmd/bootstrap-cluster/main.go | 16 ++-- cmd/remove-cluster/main.go | 7 +- go.mod | 21 +++-- go.sum | 40 ++++++--- internal/config/cluster_config.go | 85 +++++++++++++++++++ internal/provisioning/dvp/config.go | 21 +++++ internal/provisioning/dvp/provider.go | 76 +++++++++++++++++ pkg/clusterprovider/{config => }/config.go | 9 +- .../{config => }/config_test.go | 30 +++---- pkg/clusterprovider/mode.go | 41 +++++++++ pkg/clusterprovider/provider.go | 27 ++++++ pkg/clusterprovider/provider/dvp.go | 48 ----------- .../{provider/provider.go => registry.go} | 51 +++++------ 13 files changed, 346 insertions(+), 126 deletions(-) create mode 100644 internal/config/cluster_config.go create mode 100644 internal/provisioning/dvp/config.go create mode 100644 internal/provisioning/dvp/provider.go rename pkg/clusterprovider/{config => }/config.go (76%) rename pkg/clusterprovider/{config => }/config_test.go (78%) create mode 100644 pkg/clusterprovider/mode.go create mode 100644 pkg/clusterprovider/provider.go delete mode 100644 pkg/clusterprovider/provider/dvp.go rename pkg/clusterprovider/{provider/provider.go => registry.go} (55%) diff --git a/cmd/bootstrap-cluster/main.go b/cmd/bootstrap-cluster/main.go index f0e620e..16987b5 100644 --- a/cmd/bootstrap-cluster/main.go +++ b/cmd/bootstrap-cluster/main.go @@ -6,28 +6,32 @@ package main import ( "context" "log" + "time" - "github.com/deckhouse/storage-e2e/pkg/clusterprovider/config" - "github.com/deckhouse/storage-e2e/pkg/clusterprovider/provider" + "github.com/deckhouse/storage-e2e/internal/logger" + "github.com/deckhouse/storage-e2e/pkg/clusterprovider" ) func main() { - cfg, err := config.New() + cfg, err := clusterprovider.New() if err != nil { log.Fatal("failed to initialize config - ", err) } - newProvider, registryGetErr := provider.DefaultRegistry.Get(cfg.ClusterProvider) + newProvider, registryGetErr := clusterprovider.DefaultRegistry.Get(cfg.ClusterProvider) if registryGetErr != nil { log.Fatal("failed to get provider", registryGetErr) } - clusterProvider, err := newProvider(cfg) + slogger := logger.GetLogger() + clusterProvider, err := newProvider(slogger, cfg) if err != nil { log.Fatal("failed to build provider", err) } - bootstrapErr := clusterProvider.Bootstrap(context.Background()) + bootstrapCtx, bootstrapCancel := context.WithTimeout(context.Background(), time.Minute*45) + defer bootstrapCancel() + bootstrapErr := clusterProvider.Bootstrap(bootstrapCtx) if bootstrapErr != nil { log.Fatal("failed to bootstrap cluster", bootstrapErr) } diff --git a/cmd/remove-cluster/main.go b/cmd/remove-cluster/main.go index a009c92..9baee86 100644 --- a/cmd/remove-cluster/main.go +++ b/cmd/remove-cluster/main.go @@ -9,17 +9,16 @@ import ( "context" "log" - "github.com/deckhouse/storage-e2e/pkg/clusterprovider/config" - "github.com/deckhouse/storage-e2e/pkg/clusterprovider/provider" + "github.com/deckhouse/storage-e2e/pkg/clusterprovider" ) func main() { - cfg, err := config.New() + cfg, err := clusterprovider.New() if err != nil { log.Fatal("failed to initialize config - ", err) } - newProvider, registryGetErr := provider.DefaultRegistry.Get(cfg.ClusterProvider) + newProvider, registryGetErr := clusterprovider.DefaultRegistry.Get(cfg.ClusterProvider) if registryGetErr != nil { log.Fatal("failed to get provider", registryGetErr) } diff --git a/go.mod b/go.mod index f242b67..8722dbd 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,18 @@ module github.com/deckhouse/storage-e2e go 1.26.0 require ( + github.com/caarlos0/env/v11 v11.4.1 github.com/deckhouse/deckhouse v1.74.0 github.com/deckhouse/sds-node-configurator/api v0.0.0-20260114125558-7fd7152586ff github.com/deckhouse/virtualization/api v1.8.0 github.com/go-logr/logr v1.4.3 + github.com/go-playground/validator/v10 v10.30.3 github.com/onsi/ginkgo/v2 v2.23.3 github.com/onsi/gomega v1.37.0 github.com/pkg/sftp v1.13.10 - golang.org/x/crypto v0.46.0 - golang.org/x/term v0.38.0 + golang.org/x/crypto v0.52.0 + golang.org/x/sync v0.20.0 + golang.org/x/term v0.43.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.34.2 k8s.io/apimachinery v0.34.2 @@ -21,15 +24,17 @@ require ( require ( github.com/Masterminds/semver/v3 v3.3.1 // indirect - github.com/caarlos0/env/v11 v11.4.1 // 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/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/go-openapi/jsonpointer v0.22.1 // indirect github.com/go-openapi/jsonreference v0.21.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.7.0 // indirect @@ -39,8 +44,8 @@ require ( github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kelseyhightower/envconfig v1.4.0 // indirect github.com/kr/fs v0.1.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/moby/spdystream v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -53,12 +58,12 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect + golang.org/x/net v0.54.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.39.0 // indirect + golang.org/x/tools v0.44.0 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 5a5caa4..3a4585a 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= @@ -72,6 +74,14 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8= +github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= @@ -120,8 +130,6 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= @@ -134,6 +142,8 @@ 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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= @@ -225,8 +235,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 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/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -253,8 +263,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= @@ -264,6 +274,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ 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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -287,20 +299,20 @@ golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= 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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -316,8 +328,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/config/cluster_config.go b/internal/config/cluster_config.go new file mode 100644 index 0000000..f4fb305 --- /dev/null +++ b/internal/config/cluster_config.go @@ -0,0 +1,85 @@ +/* +Copyright 2025 Flant JSC + +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 config + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +// LoadClusterDefinition reads, parses, and validates a cluster topology +// definition from the YAML file at the given path. +// +// The path is taken as-is: callers are expected to provide an explicit +// (absolute or cwd-relative) path rather than relying on the loader to guess +// the location of the file. +func LoadClusterDefinition(path string) (*ClusterDefinition, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read cluster config %q: %w", path, err) + } + + // ClusterDefinition has a custom UnmarshalYAML that accepts both a + // top-level "clusterDefinition:" key and a bare document. + var def ClusterDefinition + if err := yaml.Unmarshal(data, &def); err != nil { + return nil, fmt.Errorf("parse cluster config %q: %w", path, err) + } + + // Resolve ${NAME} references in modulePullOverride from the environment + // before validation, so CI can pin per-build image tags without editing + // the YAML. Validation then only ever sees resolved, literal tags. + if err := ResolveModulePullOverrides(&def, os.LookupEnv); err != nil { + return nil, fmt.Errorf("resolve cluster config %q: %w", path, err) + } + + if err := def.Validate(); err != nil { + return nil, fmt.Errorf("invalid cluster config %q: %w", path, err) + } + + return &def, nil +} + +// Validate checks that the cluster definition contains the minimum topology and +// DKP parameters required to bootstrap a cluster. +// +// It does not inspect modulePullOverride: ${NAME} references are resolved +// separately by ResolveModulePullOverrides at load time, so Validate stays a +// pure, environment-independent check. +func (c *ClusterDefinition) Validate() error { + if len(c.Masters) == 0 { + return fmt.Errorf("at least one master node is required") + } + + dkp := c.DKPParameters + if dkp.PodSubnetCIDR == "" { + return fmt.Errorf("dkpParameters.podSubnetCIDR is required") + } + if dkp.ServiceSubnetCIDR == "" { + return fmt.Errorf("dkpParameters.serviceSubnetCIDR is required") + } + if dkp.ClusterDomain == "" { + return fmt.Errorf("dkpParameters.clusterDomain is required") + } + if dkp.RegistryRepo == "" { + return fmt.Errorf("dkpParameters.registryRepo is required") + } + + return nil +} diff --git a/internal/provisioning/dvp/config.go b/internal/provisioning/dvp/config.go new file mode 100644 index 0000000..4295857 --- /dev/null +++ b/internal/provisioning/dvp/config.go @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Flant JSC + * + * 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 dvp + +type Config struct { + //ClusterBootstrapConfig string `env:"YAML_CONFIG_FILENAME,required"` +} diff --git a/internal/provisioning/dvp/provider.go b/internal/provisioning/dvp/provider.go new file mode 100644 index 0000000..f31e21f --- /dev/null +++ b/internal/provisioning/dvp/provider.go @@ -0,0 +1,76 @@ +/* + * Copyright 2026 Flant JSC + * + * 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 dvp + +import ( + "context" + "fmt" + "log/slog" + + "github.com/caarlos0/env/v11" + "github.com/deckhouse/storage-e2e/internal/config" + "github.com/deckhouse/storage-e2e/pkg/clusterprovider" +) + +func init() { + clusterprovider.DefaultRegistry.Register(clusterprovider.ModeDVP, NewDVPProvider) +} + +// dvpProvider provisions clusters using the DVP (Deckhouse Virtualization +// Platform) strategy. +type dvpProvider struct { + cfg *clusterprovider.ClusterConfig + dvpConf *Config + logger *slog.Logger +} + +// NewDVPProvider builds a dvpProvider, loading the DVP-specific env. Registered +// as the dvp strategy's Constructor. +func NewDVPProvider(logger *slog.Logger, config *clusterprovider.ClusterConfig) (clusterprovider.Provider, error) { + dvpConf := &Config{} + if err := env.Parse(dvpConf); err != nil { + return nil, err + } + + return &dvpProvider{ + cfg: config, + dvpConf: dvpConf, + logger: logger, + }, nil +} + +func (p *dvpProvider) Name() string { return clusterprovider.ModeDVP } + +func (p *dvpProvider) Bootstrap(ctx context.Context) error { + clusterDef, err := config.LoadClusterDefinition(p.cfg.ClusterBootstrapConfigPath) + if err != nil { + return fmt.Errorf("load cluster bootstrap config: %w", err) + } + + p.logger.Info("loaded cluster bootstrap config", + "path", p.cfg.ClusterBootstrapConfigPath, + "masters", len(clusterDef.Masters), + "workers", len(clusterDef.Workers), + ) + + return nil +} + +func (p *dvpProvider) Teardown(ctx context.Context) error { + // TODO: implement — idempotent teardown by deterministic cluster name. + panic("not implemented") +} diff --git a/pkg/clusterprovider/config/config.go b/pkg/clusterprovider/config.go similarity index 76% rename from pkg/clusterprovider/config/config.go rename to pkg/clusterprovider/config.go index e275a9c..e7a6f1d 100644 --- a/pkg/clusterprovider/config/config.go +++ b/pkg/clusterprovider/config.go @@ -14,12 +14,15 @@ * limitations under the License. */ -package config +package clusterprovider -import "github.com/caarlos0/env/v11" +import ( + "github.com/caarlos0/env/v11" +) type ClusterConfig struct { - ClusterProvider string `env:"E2E_TEST_CLUSTER_PROVIDER,required"` + ClusterProvider ProviderMode `env:"E2E_TEST_CLUSTER_PROVIDER,required"` + ClusterBootstrapConfigPath string `env:"E2E_CLUSTER_CONFIG_YAML_PATH,required"` } func New() (*ClusterConfig, error) { diff --git a/pkg/clusterprovider/config/config_test.go b/pkg/clusterprovider/config_test.go similarity index 78% rename from pkg/clusterprovider/config/config_test.go rename to pkg/clusterprovider/config_test.go index 4c66cef..2c4135d 100644 --- a/pkg/clusterprovider/config/config_test.go +++ b/pkg/clusterprovider/config_test.go @@ -1,20 +1,20 @@ /* -Copyright 2026 Flant JSC + * Copyright 2026 Flant JSC + * + * 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. + */ -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 config +package clusterprovider import ( "os" diff --git a/pkg/clusterprovider/mode.go b/pkg/clusterprovider/mode.go new file mode 100644 index 0000000..cae5203 --- /dev/null +++ b/pkg/clusterprovider/mode.go @@ -0,0 +1,41 @@ +/* + * Copyright 2026 Flant JSC + * + * 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 clusterprovider + +import "fmt" + +type ProviderMode string + +const ( + ModeDVP = "dvp" + ModeCommander = "commander" +) + +func (m *ProviderMode) UnmarshalText(text []byte) error { + v := ProviderMode(text) + switch v { + case ModeDVP, ModeCommander: + *m = v + return nil + default: + return fmt.Errorf("invalid MODE: %q (allowed: dvp, commander)", text) + } +} + +func (m ProviderMode) String() string { + return string(m) +} diff --git a/pkg/clusterprovider/provider.go b/pkg/clusterprovider/provider.go new file mode 100644 index 0000000..ccac085 --- /dev/null +++ b/pkg/clusterprovider/provider.go @@ -0,0 +1,27 @@ +/* + * Copyright 2026 Flant JSC + * + * 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 clusterprovider + +import ( + "context" +) + +type Provider interface { + Name() string + Bootstrap(ctx context.Context) error + Teardown(ctx context.Context) error +} diff --git a/pkg/clusterprovider/provider/dvp.go b/pkg/clusterprovider/provider/dvp.go deleted file mode 100644 index 6c8b000..0000000 --- a/pkg/clusterprovider/provider/dvp.go +++ /dev/null @@ -1,48 +0,0 @@ -package provider - -import ( - "context" - - "github.com/caarlos0/env/v11" - "github.com/deckhouse/storage-e2e/pkg/clusterprovider/config" -) - -func init() { - DefaultRegistry.Register(ModeDvp, newDVPProvider) -} - -// dvpProvider provisions clusters using the DVP (Deckhouse Virtualization -// Platform) strategy. -type dvpProvider struct { - cfg *config.ClusterConfig - dvpConf *dvpConfig -} - -type dvpConfig struct { - ClusterBootstrapConfig string `env:"YAML_CONFIG_FILENAME,required"` -} - -func (p *dvpProvider) Name() string { return ModeDvp } - -func (p *dvpProvider) Bootstrap(ctx context.Context) error { - panic("not implemented") -} - -func (p *dvpProvider) Teardown(ctx context.Context) error { - // TODO: implement — idempotent teardown by deterministic cluster name. - panic("not implemented") -} - -// newDVPProvider builds a dvpProvider, loading the DVP-specific env. Registered -// as the dvp strategy's Constructor. -func newDVPProvider(config *config.ClusterConfig) (Provider, error) { - dvpConf := &dvpConfig{} - if err := env.Parse(dvpConf); err != nil { - return nil, err - } - - return &dvpProvider{ - cfg: config, - dvpConf: dvpConf, - }, nil -} diff --git a/pkg/clusterprovider/provider/provider.go b/pkg/clusterprovider/registry.go similarity index 55% rename from pkg/clusterprovider/provider/provider.go rename to pkg/clusterprovider/registry.go index fb93044..c2af351 100644 --- a/pkg/clusterprovider/provider/provider.go +++ b/pkg/clusterprovider/registry.go @@ -1,37 +1,35 @@ -// Package provider contains the CI-only provisioning strategies. It is used -// exclusively by the cmd/* binaries to bootstrap and tear down clusters; tests -// never import it. -// -// Strategies self-register a Constructor into a Registry via init (Open/Closed). -// Only two strategies exist: dvp and commander. -package provider +/* + * Copyright 2026 Flant JSC + * + * 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 clusterprovider import ( - "context" "fmt" + "log/slog" "sync" - - "github.com/deckhouse/storage-e2e/pkg/clusterprovider/config" -) - -const ( - ModeDvp = "dvp" - ModeCommander = "commander" ) -// Provider is a provisioning strategy. Bootstrap returns Endpoints (not a -// Cluster); Teardown is idempotent and derives target resources from config. -type Provider interface { - Name() string - Bootstrap(ctx context.Context) error - Teardown(ctx context.Context) error -} +// DefaultRegistry is the package-level registry strategies self-register into. +var DefaultRegistry = NewRegistry() // Constructor builds a Provider, loading its own strategy-specific env. It runs // lazily — only for the strategy selected at runtime — so unselected strategies // never read their env. Validate on the resulting Provider stays a pure check // (no loading, no I/O). -type Constructor func(config *config.ClusterConfig) (Provider, error) +type Constructor func(logger *slog.Logger, config *ClusterConfig) (Provider, error) // Registry maps strategy names to their Constructor. type Registry struct { @@ -44,9 +42,6 @@ func NewRegistry() *Registry { return &Registry{constructors: make(map[string]Constructor)} } -// DefaultRegistry is the package-level registry strategies self-register into. -var DefaultRegistry = NewRegistry() - // Register adds a Constructor under name. A later registration with the same // name overwrites the earlier one. func (r *Registry) Register(name string, c Constructor) { @@ -56,10 +51,10 @@ func (r *Registry) Register(name string, c Constructor) { } // Get returns the Constructor registered under name. -func (r *Registry) Get(name string) (Constructor, error) { +func (r *Registry) Get(name ProviderMode) (Constructor, error) { r.mu.RLock() defer r.mu.RUnlock() - c, ok := r.constructors[name] + c, ok := r.constructors[name.String()] if !ok { return nil, fmt.Errorf("provider %q is not registered", name) } From 988b4f5d247a17f9c651519abfa1b253daffc893 Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Sat, 13 Jun 2026 20:29:15 +0300 Subject: [PATCH 03/23] feat: add module pull override resolution with error reporting --- internal/config/module_overrides.go | 92 +++++++++++++ internal/config/module_overrides_test.go | 159 +++++++++++++++++++++++ 2 files changed, 251 insertions(+) create mode 100644 internal/config/module_overrides.go create mode 100644 internal/config/module_overrides_test.go diff --git a/internal/config/module_overrides.go b/internal/config/module_overrides.go new file mode 100644 index 0000000..a440ad9 --- /dev/null +++ b/internal/config/module_overrides.go @@ -0,0 +1,92 @@ +/* +Copyright 2026 Flant JSC + +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 config + +import ( + "fmt" + "regexp" + "strings" +) + +// EnvLookup resolves a variable by name and reports whether it is set. It +// mirrors os.LookupEnv so the standard environment can be injected directly, +// while tests can supply a map-backed source without mutating the process +// environment. +type EnvLookup func(name string) (value string, ok bool) + +// envRefPattern matches a single ${NAME} reference. Only the braced form with +// shell-identifier characters is recognized, so a tag containing a bare '$' +// is never rewritten and substitution intent is always explicit. The same +// pattern drives both detection and replacement, so a reference can never be +// detected one way and substituted another. +var envRefPattern = regexp.MustCompile(`\$\{([A-Za-z_][A-Za-z0-9_]*)\}`) + +// ResolveModulePullOverrides substitutes ${NAME} references in every module's +// ModulePullOverride using lookup, letting CI point modules at per-build image +// tags without editing the YAML: +// +// modules: +// - name: csi-ceph +// modulePullOverride: "${CSI_CEPH_TAG}" +// +// Each module may reference its own variable, and a field may contain several +// references. All problems across all modules are reported together: +// - a reference to a variable that lookup does not provide, and +// - a residual "${" left after substitution (a malformed reference such as +// ${bad-name}), which guarantees no placeholder ever reaches the cluster. +// +// Modules without references are left untouched. +func ResolveModulePullOverrides(def *ClusterDefinition, lookup EnvLookup) error { + if def == nil { + return nil + } + + var problems []string + for _, m := range def.DKPParameters.Modules { + if m == nil || m.ModulePullOverride == "" { + continue + } + + original := m.ModulePullOverride + resolved := envRefPattern.ReplaceAllStringFunc(original, func(ref string) string { + name := envRefPattern.FindStringSubmatch(ref)[1] + value, ok := lookup(name) + if !ok { + problems = append(problems, fmt.Sprintf("module %q references unset ${%s}", m.Name, name)) + // Value is irrelevant: any problem aborts the whole load. Drop + // the reference so it is not re-reported by the residual check. + return "" + } + return value + }) + + if strings.Contains(resolved, "${") { + problems = append(problems, fmt.Sprintf( + "module %q has malformed modulePullOverride %q (only ${NAME} with letters, digits, and underscores is supported)", + m.Name, original, + )) + continue + } + + m.ModulePullOverride = resolved + } + + if len(problems) > 0 { + return fmt.Errorf("modulePullOverride resolution failed: %s", strings.Join(problems, "; ")) + } + return nil +} diff --git a/internal/config/module_overrides_test.go b/internal/config/module_overrides_test.go new file mode 100644 index 0000000..57283ae --- /dev/null +++ b/internal/config/module_overrides_test.go @@ -0,0 +1,159 @@ +/* +Copyright 2026 Flant JSC + +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 config + +import ( + "strings" + "testing" +) + +// lookupFrom returns an EnvLookup backed by the given map, so tests stay +// hermetic and never touch the process environment. +func lookupFrom(env map[string]string) EnvLookup { + return func(name string) (string, bool) { + v, ok := env[name] + return v, ok + } +} + +func defWithOverrides(overrides ...string) *ClusterDefinition { + modules := make([]*ModuleConfig, 0, len(overrides)) + for i, o := range overrides { + modules = append(modules, &ModuleConfig{ + Name: "module-" + string(rune('a'+i)), + ModulePullOverride: o, + }) + } + return &ClusterDefinition{DKPParameters: DKPParameters{Modules: modules}} +} + +func TestResolveModulePullOverrides(t *testing.T) { + tests := []struct { + name string + input string + env map[string]string + want string + wantErr []string // substrings expected in the error; empty means success + }{ + { + name: "literal tag untouched", + input: "main", + want: "main", + }, + { + name: "empty override untouched", + input: "", + want: "", + }, + { + name: "single reference resolved", + input: "${MODULE_IMAGE_TAG}", + env: map[string]string{"MODULE_IMAGE_TAG": "pr131"}, + want: "pr131", + }, + { + name: "multiple references in one value", + input: "${PREFIX}-${NAME}", + env: map[string]string{"PREFIX": "branch", "NAME": "ms-crc"}, + want: "branch-ms-crc", + }, + { + name: "unset reference reported", + input: "${MISSING_TAG}", + wantErr: []string{"module-a", "MISSING_TAG", "unset"}, + }, + { + name: "malformed reference reported", + input: "${bad-name}", + wantErr: []string{"module-a", "malformed", "bad-name"}, + }, + { + name: "bare dollar is a literal, not a reference", + input: "tag-$NAME", + env: map[string]string{"NAME": "should-not-be-used"}, + want: "tag-$NAME", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + def := defWithOverrides(tt.input) + err := ResolveModulePullOverrides(def, lookupFrom(tt.env)) + + if len(tt.wantErr) > 0 { + if err == nil { + t.Fatalf("expected error, got nil") + } + for _, want := range tt.wantErr { + if !strings.Contains(err.Error(), want) { + t.Errorf("error %q does not contain %q", err.Error(), want) + } + } + return + } + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got := def.DKPParameters.Modules[0].ModulePullOverride; got != tt.want { + t.Errorf("got %q, want %q", got, tt.want) + } + }) + } +} + +func TestResolveModulePullOverrides_PerModuleVariables(t *testing.T) { + def := defWithOverrides("${CSI_CEPH_TAG}", "${SDS_ELASTIC_TAG}", "main") + env := map[string]string{"CSI_CEPH_TAG": "pr131", "SDS_ELASTIC_TAG": "mr41"} + + if err := ResolveModulePullOverrides(def, lookupFrom(env)); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + want := []string{"pr131", "mr41", "main"} + for i, w := range want { + if got := def.DKPParameters.Modules[i].ModulePullOverride; got != w { + t.Errorf("module %d: got %q, want %q", i, got, w) + } + } +} + +func TestResolveModulePullOverrides_AggregatesAllProblems(t *testing.T) { + def := defWithOverrides("${MISSING_ONE}", "${MISSING_TWO}", "${good}") + err := ResolveModulePullOverrides(def, lookupFrom(nil)) + if err == nil { + t.Fatalf("expected error, got nil") + } + for _, want := range []string{"MISSING_ONE", "MISSING_TWO", "good"} { + if !strings.Contains(err.Error(), want) { + t.Errorf("error %q should report %q", err.Error(), want) + } + } +} + +func TestResolveModulePullOverrides_NilSafety(t *testing.T) { + if err := ResolveModulePullOverrides(nil, lookupFrom(nil)); err != nil { + t.Fatalf("nil definition: unexpected error: %v", err) + } + + def := &ClusterDefinition{DKPParameters: DKPParameters{ + Modules: []*ModuleConfig{nil, {Name: "csi-ceph", ModulePullOverride: "main"}}, + }} + if err := ResolveModulePullOverrides(def, lookupFrom(nil)); err != nil { + t.Fatalf("nil module entry: unexpected error: %v", err) + } +} From fddfbdd3069d0b2e0987bb1edfedfc8a93463f7e Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Sat, 13 Jun 2026 21:14:33 +0300 Subject: [PATCH 04/23] feat: rename teardown method to remove and update related references --- cmd/remove-cluster/main.go | 8 +++++--- internal/config/module_overrides.go | 2 -- internal/provisioning/dvp/config.go | 28 +++++++++++++-------------- internal/provisioning/dvp/provider.go | 2 +- pkg/clusterprovider/provider.go | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/cmd/remove-cluster/main.go b/cmd/remove-cluster/main.go index 9baee86..4ebbf41 100644 --- a/cmd/remove-cluster/main.go +++ b/cmd/remove-cluster/main.go @@ -1,7 +1,7 @@ // Command remove-cluster is the CI-only entrypoint that tears a cluster down. // It is a thin wrapper: load ClusterConfig, resolve the strategy's Constructor // from the provider Registry, build the Provider, then run the idempotent -// Teardown (which derives target resources from config, not from bootstrap +// Remove (which derives target resources from config, not from bootstrap // artifacts). package main @@ -9,6 +9,7 @@ import ( "context" "log" + "github.com/deckhouse/storage-e2e/internal/logger" "github.com/deckhouse/storage-e2e/pkg/clusterprovider" ) @@ -23,12 +24,13 @@ func main() { log.Fatal("failed to get provider", registryGetErr) } - clusterProvider, err := newProvider(cfg) + slogger := logger.GetLogger() + clusterProvider, err := newProvider(slogger, cfg) if err != nil { log.Fatal("failed to build provider", err) } - teardownErr := clusterProvider.Teardown(context.Background()) + teardownErr := clusterProvider.Remove(context.TODO()) if teardownErr != nil { log.Fatal("failed to tear down cluster", teardownErr) } diff --git a/internal/config/module_overrides.go b/internal/config/module_overrides.go index a440ad9..f48af15 100644 --- a/internal/config/module_overrides.go +++ b/internal/config/module_overrides.go @@ -67,8 +67,6 @@ func ResolveModulePullOverrides(def *ClusterDefinition, lookup EnvLookup) error value, ok := lookup(name) if !ok { problems = append(problems, fmt.Sprintf("module %q references unset ${%s}", m.Name, name)) - // Value is irrelevant: any problem aborts the whole load. Drop - // the reference so it is not re-reported by the residual check. return "" } return value diff --git a/internal/provisioning/dvp/config.go b/internal/provisioning/dvp/config.go index 4295857..de74713 100644 --- a/internal/provisioning/dvp/config.go +++ b/internal/provisioning/dvp/config.go @@ -1,18 +1,18 @@ /* - * Copyright 2026 Flant JSC - * - * 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. - */ +Copyright 2026 Flant JSC + +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 dvp diff --git a/internal/provisioning/dvp/provider.go b/internal/provisioning/dvp/provider.go index f31e21f..8ac58ce 100644 --- a/internal/provisioning/dvp/provider.go +++ b/internal/provisioning/dvp/provider.go @@ -70,7 +70,7 @@ func (p *dvpProvider) Bootstrap(ctx context.Context) error { return nil } -func (p *dvpProvider) Teardown(ctx context.Context) error { +func (p *dvpProvider) Remove(ctx context.Context) error { // TODO: implement — idempotent teardown by deterministic cluster name. panic("not implemented") } diff --git a/pkg/clusterprovider/provider.go b/pkg/clusterprovider/provider.go index ccac085..65266e9 100644 --- a/pkg/clusterprovider/provider.go +++ b/pkg/clusterprovider/provider.go @@ -23,5 +23,5 @@ import ( type Provider interface { Name() string Bootstrap(ctx context.Context) error - Teardown(ctx context.Context) error + Remove(ctx context.Context) error } From a989cd1f925b14c6e8162e26a348ab71b7ae85ec Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Sun, 14 Jun 2026 12:35:31 +0300 Subject: [PATCH 05/23] feat: add OpenTunnel method for SSH tunnel establishment and enhance configuration management --- cmd/bootstrap-cluster/main.go | 4 +- cmd/remove-cluster/main.go | 4 +- internal/infrastructure/ssh/client.go | 13 ++ internal/infrastructure/ssh/interface.go | 6 + internal/infrastructure/ssh/tunnel.go | 19 ++- internal/provisioning/dvp/config.go | 27 +++- internal/provisioning/dvp/config_test.go | 133 ++++++++++++++++++ internal/provisioning/dvp/connection.go | 131 +++++++++++++++++ internal/provisioning/dvp/kubeconfig.go | 94 +++++++++++++ internal/provisioning/dvp/provider.go | 30 +++- .../{ => registry}/registry.go | 15 +- 11 files changed, 458 insertions(+), 18 deletions(-) create mode 100644 internal/provisioning/dvp/config_test.go create mode 100644 internal/provisioning/dvp/connection.go create mode 100644 internal/provisioning/dvp/kubeconfig.go rename pkg/clusterprovider/{ => registry}/registry.go (77%) diff --git a/cmd/bootstrap-cluster/main.go b/cmd/bootstrap-cluster/main.go index 16987b5..7f2197a 100644 --- a/cmd/bootstrap-cluster/main.go +++ b/cmd/bootstrap-cluster/main.go @@ -9,6 +9,8 @@ import ( "time" "github.com/deckhouse/storage-e2e/internal/logger" + "github.com/deckhouse/storage-e2e/pkg/clusterprovider/registry" + "github.com/deckhouse/storage-e2e/pkg/clusterprovider" ) @@ -18,7 +20,7 @@ func main() { log.Fatal("failed to initialize config - ", err) } - newProvider, registryGetErr := clusterprovider.DefaultRegistry.Get(cfg.ClusterProvider) + newProvider, registryGetErr := registry.DefaultRegistry.Get(cfg.ClusterProvider) if registryGetErr != nil { log.Fatal("failed to get provider", registryGetErr) } diff --git a/cmd/remove-cluster/main.go b/cmd/remove-cluster/main.go index 4ebbf41..c1790f1 100644 --- a/cmd/remove-cluster/main.go +++ b/cmd/remove-cluster/main.go @@ -10,6 +10,8 @@ import ( "log" "github.com/deckhouse/storage-e2e/internal/logger" + "github.com/deckhouse/storage-e2e/pkg/clusterprovider/registry" + "github.com/deckhouse/storage-e2e/pkg/clusterprovider" ) @@ -19,7 +21,7 @@ func main() { log.Fatal("failed to initialize config - ", err) } - newProvider, registryGetErr := clusterprovider.DefaultRegistry.Get(cfg.ClusterProvider) + newProvider, registryGetErr := registry.DefaultRegistry.Get(cfg.ClusterProvider) if registryGetErr != nil { log.Fatal("failed to get provider", registryGetErr) } diff --git a/internal/infrastructure/ssh/client.go b/internal/infrastructure/ssh/client.go index b72254f..f2ba172 100644 --- a/internal/infrastructure/ssh/client.go +++ b/internal/infrastructure/ssh/client.go @@ -421,6 +421,12 @@ func (c *client) StartTunnel(ctx context.Context, localPort, remotePort string) return runTunnelLoop(ctx, localPort, dialer) } +// OpenTunnel establishes a tunnel forwarding remotePort to a free local port, +// retrying on transient connection errors. See openTunnel for details. +func (c *client) OpenTunnel(ctx context.Context, remotePort string) (*TunnelInfo, error) { + return openTunnel(ctx, c, remotePort) +} + // Exec executes a command on the remote host with automatic retry and reconnection func (c *client) Exec(ctx context.Context, cmd string) (string, error) { var output string @@ -925,6 +931,13 @@ func (c *jumpHostClient) StartTunnel(ctx context.Context, localPort, remotePort return runTunnelLoop(ctx, localPort, dialer) } +// OpenTunnel establishes a tunnel forwarding remotePort to a free local port +// through the jump host, retrying on transient connection errors. See +// openTunnel for details. +func (c *jumpHostClient) OpenTunnel(ctx context.Context, remotePort string) (*TunnelInfo, error) { + return openTunnel(ctx, c, remotePort) +} + // tunnelDialer abstracts the per-tunnel concerns that runTunnelLoop needs to // know about: how to open a fresh remote connection through the active SSH // session, how to re-establish that session when it dies, and a human-readable diff --git a/internal/infrastructure/ssh/interface.go b/internal/infrastructure/ssh/interface.go index 018de88..682ce4f 100644 --- a/internal/infrastructure/ssh/interface.go +++ b/internal/infrastructure/ssh/interface.go @@ -30,6 +30,12 @@ type SSHClient interface { // It returns a function to stop the tunnel and an error if the tunnel fails to start StartTunnel(ctx context.Context, localPort, remotePort string) (stop func() error, err error) + // OpenTunnel establishes an SSH tunnel forwarding remotePort to an + // automatically chosen free local port, retrying on transient connection + // errors. It returns the tunnel info (including the local port and a stop + // function) and an error if the tunnel cannot be established. + OpenTunnel(ctx context.Context, remotePort string) (*TunnelInfo, error) + // Exec executes a command on the remote host Exec(ctx context.Context, cmd string) (string, error) diff --git a/internal/infrastructure/ssh/tunnel.go b/internal/infrastructure/ssh/tunnel.go index b5730d1..6c4743b 100644 --- a/internal/infrastructure/ssh/tunnel.go +++ b/internal/infrastructure/ssh/tunnel.go @@ -143,11 +143,22 @@ func StartTunnel(ctx context.Context, sshClient *ssh.Client, localPort, remotePo return stop, nil } -// EstablishSSHTunnel establishes an SSH tunnel with port forwarding from remote node to local port on the client -// It automatically finds a free local port to avoid conflicts when running parallel tests. -// Uses retry logic for transient connection errors. -// Returns the tunnel info, local port and error if the tunnel fails to start +// EstablishSSHTunnel establishes an SSH tunnel with port forwarding from remote node to local port on the client. +// +// Deprecated: use SSHClient.OpenTunnel instead. The tunnel logic now lives on +// the client so every SSH consumer gets free-port allocation and retry without +// calling a free function. This wrapper is kept for backward compatibility and +// simply delegates to sshClient.OpenTunnel. func EstablishSSHTunnel(ctx context.Context, sshClient SSHClient, remotePort string) (*TunnelInfo, error) { + return sshClient.OpenTunnel(ctx, remotePort) +} + +// openTunnel establishes an SSH tunnel with port forwarding from a remote node +// to a local port on the client. It automatically finds a free local port to +// avoid conflicts when running parallel tests and retries on transient +// connection errors. It works with any SSHClient implementation, so both the +// direct and jump-host clients delegate their OpenTunnel method here. +func openTunnel(ctx context.Context, sshClient SSHClient, remotePort string) (*TunnelInfo, error) { // Parse remote port to integer remotePortInt, err := strconv.Atoi(remotePort) if err != nil { diff --git a/internal/provisioning/dvp/config.go b/internal/provisioning/dvp/config.go index de74713..a43b59b 100644 --- a/internal/provisioning/dvp/config.go +++ b/internal/provisioning/dvp/config.go @@ -17,5 +17,30 @@ limitations under the License. package dvp type Config struct { - //ClusterBootstrapConfig string `env:"YAML_CONFIG_FILENAME,required"` + SSHUser string `env:"E2E_DVP_SSH_USER,required"` + SSHHost string `env:"E2E_DVP_SSH_HOST,required"` + SSHKeyPath string `env:"E2E_DVP_SSH_KEY_PATH,required"` + + SSHJumpHost string `env:"E2E_DVP_SSH_JUMP_HOST"` + SSHJumpUser string `env:"E2E_DVP_SSH_JUMP_USER"` + SSHJumpKeyPath string `env:"E2E_DVP_SSH_JUMP_KEY_PATH"` +} + +// baseEndpoint builds the SSH endpoint for the DVP base cluster control-plane, +// routing through the jump host when one is configured. +func (c *Config) baseEndpoint() sshEndpoint { + ep := sshEndpoint{User: c.SSHUser, Host: c.SSHHost, KeyPath: c.SSHKeyPath} + if c.SSHJumpHost == "" { + return ep + } + + jump := sshEndpoint{User: c.SSHJumpUser, Host: c.SSHJumpHost, KeyPath: c.SSHJumpKeyPath} + if jump.User == "" { + jump.User = c.SSHUser + } + if jump.KeyPath == "" { + jump.KeyPath = c.SSHKeyPath + } + ep.Jump = &jump + return ep } diff --git a/internal/provisioning/dvp/config_test.go b/internal/provisioning/dvp/config_test.go new file mode 100644 index 0000000..6963a4f --- /dev/null +++ b/internal/provisioning/dvp/config_test.go @@ -0,0 +1,133 @@ +/* +Copyright 2026 Flant JSC + +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 dvp + +import ( + "testing" +) + +func TestConfigBaseEndpoint(t *testing.T) { + tests := []struct { + name string + cfg Config + want sshEndpoint + }{ + { + name: "no jump host", + cfg: Config{ + SSHUser: "deckhouse", + SSHHost: "10.0.0.1", + SSHKeyPath: "/keys/id_rsa", + }, + want: sshEndpoint{ + User: "deckhouse", + Host: "10.0.0.1", + KeyPath: "/keys/id_rsa", + Jump: nil, + }, + }, + { + name: "jump host fully specified", + cfg: Config{ + SSHUser: "deckhouse", + SSHHost: "10.0.0.1", + SSHKeyPath: "/keys/target", + SSHJumpHost: "jump.example.com", + SSHJumpUser: "jumper", + SSHJumpKeyPath: "/keys/jump", + }, + want: sshEndpoint{ + User: "deckhouse", + Host: "10.0.0.1", + KeyPath: "/keys/target", + Jump: &sshEndpoint{ + User: "jumper", + Host: "jump.example.com", + KeyPath: "/keys/jump", + }, + }, + }, + { + name: "jump host inherits user and key from target", + cfg: Config{ + SSHUser: "deckhouse", + SSHHost: "10.0.0.1", + SSHKeyPath: "/keys/target", + SSHJumpHost: "jump.example.com", + }, + want: sshEndpoint{ + User: "deckhouse", + Host: "10.0.0.1", + KeyPath: "/keys/target", + Jump: &sshEndpoint{ + User: "deckhouse", + Host: "jump.example.com", + KeyPath: "/keys/target", + }, + }, + }, + { + name: "jump host inherits only missing fields", + cfg: Config{ + SSHUser: "deckhouse", + SSHHost: "10.0.0.1", + SSHKeyPath: "/keys/target", + SSHJumpHost: "jump.example.com", + SSHJumpUser: "jumper", + }, + want: sshEndpoint{ + User: "deckhouse", + Host: "10.0.0.1", + KeyPath: "/keys/target", + Jump: &sshEndpoint{ + User: "jumper", + Host: "jump.example.com", + KeyPath: "/keys/target", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.cfg.baseEndpoint() + + if got.User != tt.want.User || got.Host != tt.want.Host || got.KeyPath != tt.want.KeyPath { + t.Errorf("endpoint = {User:%q Host:%q KeyPath:%q}, want {User:%q Host:%q KeyPath:%q}", + got.User, got.Host, got.KeyPath, tt.want.User, tt.want.Host, tt.want.KeyPath) + } + + switch { + case tt.want.Jump == nil && got.Jump != nil: + t.Errorf("Jump = %+v, want nil", got.Jump) + case tt.want.Jump != nil && got.Jump == nil: + t.Fatalf("Jump = nil, want %+v", tt.want.Jump) + case tt.want.Jump != nil && got.Jump != nil: + if got.Jump.User != tt.want.Jump.User || + got.Jump.Host != tt.want.Jump.Host || + got.Jump.KeyPath != tt.want.Jump.KeyPath { + t.Errorf("Jump = {User:%q Host:%q KeyPath:%q}, want {User:%q Host:%q KeyPath:%q}", + got.Jump.User, got.Jump.Host, got.Jump.KeyPath, + tt.want.Jump.User, tt.want.Jump.Host, tt.want.Jump.KeyPath) + } + if got.Jump.Jump != nil { + t.Errorf("Jump.Jump = %+v, want nil (no nested jump chain)", got.Jump.Jump) + } + } + }) + } +} diff --git a/internal/provisioning/dvp/connection.go b/internal/provisioning/dvp/connection.go new file mode 100644 index 0000000..5288aac --- /dev/null +++ b/internal/provisioning/dvp/connection.go @@ -0,0 +1,131 @@ +/* +Copyright 2026 Flant JSC + +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 dvp + +import ( + "context" + "errors" + "fmt" + + "k8s.io/client-go/rest" + + "github.com/deckhouse/storage-e2e/internal/infrastructure/ssh" +) + +// apiServerRemotePort is the port the cluster API server listens on. It is +// forwarded to an ephemeral local port through the SSH tunnel. +const apiServerRemotePort = "6445" + +// sshEndpoint describes how to reach a host over SSH. When Jump is non-nil the +// connection is routed through it (jump -> target); otherwise it is direct. +type sshEndpoint struct { + User string + Host string + KeyPath string + Jump *sshEndpoint +} + +// dial opens an SSH connection to the endpoint, transparently routing through a +// jump host when one is configured. +func (e sshEndpoint) dial() (ssh.SSHClient, error) { + if e.Jump != nil { + return ssh.NewClientWithJumpHost( + e.Jump.User, e.Jump.Host, e.Jump.KeyPath, + e.User, e.Host, e.KeyPath, + ) + } + return ssh.NewClient(e.User, e.Host, e.KeyPath) +} + +// clusterConnection is a live SSH tunnel to a cluster's API server together +// with the kubeconfig that targets it through that tunnel. Close releases both +// the tunnel and the SSH connection. +type clusterConnection struct { + ssh ssh.SSHClient + tunnel *ssh.TunnelInfo + + // Kubeconfig is a client-go config whose server already points at the local + // end of the tunnel; ready to use once openClusterConnection returns. + Kubeconfig *rest.Config + // KubeconfigPath is the on-disk kubeconfig (server rewritten to the local + // tunnel port, mode 0600) for tooling such as kubectl/dhctl. + KubeconfigPath string +} + +// openClusterConnection connects to a (possibly closed) cluster over SSH, +// reads its kubeconfig from the control-plane node, forwards the API server +// through a local SSH tunnel, and returns a kubeconfig already pointing at that +// tunnel. On any failure all partially-acquired resources are released. +func openClusterConnection(ctx context.Context, ep sshEndpoint, kubeconfigDir string) (*clusterConnection, error) { + sshClient, err := ep.dial() + if err != nil { + return nil, fmt.Errorf("ssh dial %s@%s: %w", ep.User, ep.Host, err) + } + + conn := &clusterConnection{ssh: sshClient} + + // The tunnel's lifetime is bound to ctx: it stops on ctx cancellation + // (e.g. the Bootstrap deadline) or when Close is called explicitly. + conn.tunnel, err = sshClient.OpenTunnel(ctx, apiServerRemotePort) + if err != nil { + _ = conn.Close() + return nil, fmt.Errorf("establish API server tunnel: %w", err) + } + + raw, err := fetchKubeconfig(ctx, sshClient) + if err != nil { + _ = conn.Close() + return nil, fmt.Errorf("fetch kubeconfig from %s@%s: %w", ep.User, ep.Host, err) + } + + path, err := kubeconfigFilePath(kubeconfigDir, ep.Host) + if err != nil { + _ = conn.Close() + return nil, err + } + + server := fmt.Sprintf("https://127.0.0.1:%d", conn.tunnel.LocalPort) + conn.Kubeconfig, err = buildKubeconfig(raw, server, path) + if err != nil { + _ = conn.Close() + return nil, fmt.Errorf("build kubeconfig: %w", err) + } + conn.KubeconfigPath = path + + return conn, nil +} + +// Close stops the API server tunnel and closes the SSH connection, joining any +// errors. It is safe to call on a nil or partially-initialised connection. +func (c *clusterConnection) Close() error { + if c == nil { + return nil + } + + var errs []error + if c.tunnel != nil && c.tunnel.StopFunc != nil { + if err := c.tunnel.StopFunc(); err != nil { + errs = append(errs, fmt.Errorf("stop API server tunnel: %w", err)) + } + } + if c.ssh != nil { + if err := c.ssh.Close(); err != nil { + errs = append(errs, fmt.Errorf("close ssh client: %w", err)) + } + } + return errors.Join(errs...) +} diff --git a/internal/provisioning/dvp/kubeconfig.go b/internal/provisioning/dvp/kubeconfig.go new file mode 100644 index 0000000..1e10f75 --- /dev/null +++ b/internal/provisioning/dvp/kubeconfig.go @@ -0,0 +1,94 @@ +/* +Copyright 2026 Flant JSC + +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 dvp + +import ( + "context" + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + + "github.com/deckhouse/storage-e2e/internal/infrastructure/ssh" +) + +const kubeconfigReadCommand = "sudo -n /bin/cat /etc/kubernetes/super-admin.conf 2>/dev/null " + + "|| sudo -n /bin/cat /etc/kubernetes/admin.conf" + +func fetchKubeconfig(ctx context.Context, sshClient ssh.SSHClient) ([]byte, error) { + stdout, stderr, err := sshClient.ExecCapture(ctx, kubeconfigReadCommand) + if err != nil { + return nil, fmt.Errorf("%w (remote stderr: %s)", err, strings.TrimSpace(stderr)) + } + if strings.TrimSpace(stdout) == "" { + return nil, fmt.Errorf("empty kubeconfig output (remote stderr: %s)", strings.TrimSpace(stderr)) + } + return []byte(stdout), nil +} + +func buildKubeconfig(raw []byte, server, path string) (*rest.Config, error) { + apiCfg, err := clientcmd.Load(raw) + if err != nil { + return nil, fmt.Errorf("parse kubeconfig: %w", err) + } + for _, cluster := range apiCfg.Clusters { + cluster.Server = server + } + + if err := clientcmd.WriteToFile(*apiCfg, path); err != nil { + return nil, fmt.Errorf("write kubeconfig %q: %w", path, err) + } + + restCfg, err := clientcmd.NewDefaultClientConfig(*apiCfg, &clientcmd.ConfigOverrides{}).ClientConfig() + + if err != nil { + return nil, fmt.Errorf("build rest config: %w", err) + } + configureTunnelTimeouts(restCfg) + return restCfg, nil +} + +func kubeconfigFilePath(dir, host string) (string, error) { + if err := os.MkdirAll(dir, 0o755); err != nil { + return "", fmt.Errorf("create kubeconfig dir %q: %w", dir, err) + } + return filepath.Join(dir, fmt.Sprintf("kubeconfig-%s.yml", host)), nil +} + +func configureTunnelTimeouts(cfg *rest.Config) { + cfg.Timeout = 2 * time.Minute + + prev := cfg.WrapTransport + cfg.WrapTransport = func(rt http.RoundTripper) http.RoundTripper { + if prev != nil { + rt = prev(rt) + } + if t, ok := rt.(*http.Transport); ok { + t = t.Clone() + t.TLSHandshakeTimeout = 30 * time.Second + t.ResponseHeaderTimeout = 60 * time.Second + t.IdleConnTimeout = 90 * time.Second + return t + } + return rt + } +} diff --git a/internal/provisioning/dvp/provider.go b/internal/provisioning/dvp/provider.go index 8ac58ce..53db98e 100644 --- a/internal/provisioning/dvp/provider.go +++ b/internal/provisioning/dvp/provider.go @@ -26,10 +26,6 @@ import ( "github.com/deckhouse/storage-e2e/pkg/clusterprovider" ) -func init() { - clusterprovider.DefaultRegistry.Register(clusterprovider.ModeDVP, NewDVPProvider) -} - // dvpProvider provisions clusters using the DVP (Deckhouse Virtualization // Platform) strategy. type dvpProvider struct { @@ -38,8 +34,8 @@ type dvpProvider struct { logger *slog.Logger } -// NewDVPProvider builds a dvpProvider, loading the DVP-specific env. Registered -// as the dvp strategy's Constructor. +// NewDVPProvider builds a dvpProvider, loading the DVP-specific env. It is wired +// into the provider Registry by internal/provisioning/register. func NewDVPProvider(logger *slog.Logger, config *clusterprovider.ClusterConfig) (clusterprovider.Provider, error) { dvpConf := &Config{} if err := env.Parse(dvpConf); err != nil { @@ -67,6 +63,28 @@ func (p *dvpProvider) Bootstrap(ctx context.Context) error { "workers", len(clusterDef.Workers), ) + // Step 2: connect to the (closed) DVP base cluster — open an SSH tunnel to + // its API server and load its kubeconfig. The connection lives only for the + // duration of Bootstrap and is closed on return; it is NOT shared with + // Remove, which runs in a separate process/CI job and opens its own. + p.logger.Info("connecting to DVP base cluster", + "host", p.dvpConf.SSHHost, + "jumpHost", p.dvpConf.SSHJumpHost, + ) + conn, err := openClusterConnection(ctx, p.dvpConf.baseEndpoint(), config.E2ETempDir) + if err != nil { + return fmt.Errorf("connect to DVP base cluster: %w", err) + } + defer func() { + if cerr := conn.Close(); cerr != nil { + p.logger.Warn("close DVP base cluster connection", "err", cerr) + } + }() + p.logger.Info("connected to DVP base cluster", + "kubeconfig", conn.KubeconfigPath, + "apiServer", conn.Kubeconfig.Host, + ) + return nil } diff --git a/pkg/clusterprovider/registry.go b/pkg/clusterprovider/registry/registry.go similarity index 77% rename from pkg/clusterprovider/registry.go rename to pkg/clusterprovider/registry/registry.go index c2af351..26b029d 100644 --- a/pkg/clusterprovider/registry.go +++ b/pkg/clusterprovider/registry/registry.go @@ -14,12 +14,15 @@ * limitations under the License. */ -package clusterprovider +package registry import ( "fmt" "log/slog" "sync" + + "github.com/deckhouse/storage-e2e/internal/provisioning/dvp" + "github.com/deckhouse/storage-e2e/pkg/clusterprovider" ) // DefaultRegistry is the package-level registry strategies self-register into. @@ -29,7 +32,7 @@ var DefaultRegistry = NewRegistry() // lazily — only for the strategy selected at runtime — so unselected strategies // never read their env. Validate on the resulting Provider stays a pure check // (no loading, no I/O). -type Constructor func(logger *slog.Logger, config *ClusterConfig) (Provider, error) +type Constructor func(logger *slog.Logger, config *clusterprovider.ClusterConfig) (clusterprovider.Provider, error) // Registry maps strategy names to their Constructor. type Registry struct { @@ -37,9 +40,11 @@ type Registry struct { constructors map[string]Constructor } -// NewRegistry returns an empty Registry. +// NewRegistry returns a Registry. func NewRegistry() *Registry { - return &Registry{constructors: make(map[string]Constructor)} + return &Registry{constructors: map[string]Constructor{ + clusterprovider.ModeDVP: dvp.NewDVPProvider, + }} } // Register adds a Constructor under name. A later registration with the same @@ -51,7 +56,7 @@ func (r *Registry) Register(name string, c Constructor) { } // Get returns the Constructor registered under name. -func (r *Registry) Get(name ProviderMode) (Constructor, error) { +func (r *Registry) Get(name clusterprovider.ProviderMode) (Constructor, error) { r.mu.RLock() defer r.mu.RUnlock() c, ok := r.constructors[name.String()] From 2b4b3efeaaa3eeb3313d5739d165e41e935ebd2e Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Sun, 14 Jun 2026 13:43:07 +0300 Subject: [PATCH 06/23] feat: enhance DVP cluster connection and module readiness verification --- internal/config/config.go | 2 +- internal/provisioning/dvp/config.go | 28 ++++++-- internal/provisioning/dvp/connection.go | 12 ++-- internal/provisioning/dvp/kubeconfig.go | 38 +++++++--- internal/provisioning/dvp/provider.go | 20 ++++-- pkg/cluster/cluster.go | 18 ++--- pkg/kubernetes/modules.go | 92 +++++++++++++++++++------ 7 files changed, 148 insertions(+), 62 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 9b19b18..41bd3cd 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -54,7 +54,7 @@ const ( BootstrapUploadTimeout = 5 * time.Minute // Timeout for uploading bootstrap files // Kubernetes operations - ModuleCheckTimeout = 10 * time.Second // Timeout for checking module status + ModuleCheckTimeout = 60 * time.Second // Timeout for checking module status NamespaceTimeout = 30 * time.Second // Timeout for creating namespace NodeGroupTimeout = 2 * time.Minute // Timeout for creating NodeGroup (API can be slow right after bootstrap) SecretsWaitTimeout = 2 * time.Minute // Timeout for waiting for bootstrap secrets to appear diff --git a/internal/provisioning/dvp/config.go b/internal/provisioning/dvp/config.go index a43b59b..781d282 100644 --- a/internal/provisioning/dvp/config.go +++ b/internal/provisioning/dvp/config.go @@ -16,14 +16,30 @@ limitations under the License. package dvp +import ( + "fmt" + "os" +) + type Config struct { - SSHUser string `env:"E2E_DVP_SSH_USER,required"` - SSHHost string `env:"E2E_DVP_SSH_HOST,required"` - SSHKeyPath string `env:"E2E_DVP_SSH_KEY_PATH,required"` + SSHUser string `env:"E2E_DVP_BASE_CLUSTER_SSH_USER,required"` + SSHHost string `env:"E2E_DVP_BASE_CLUSTER_SSH_HOST,required"` + SSHKeyPath string `env:"E2E_DVP_BASE_CLUSTER_SSH_KEY_PATH,required"` + SSHPassphrase string `env:"E2E_DVP_BASE_CLUSTER_SSH_PASSPHRASE"` + + SSHJumpHost string `env:"E2E_DVP_BASE_CLUSTER_SSH_JUMP_HOST"` + SSHJumpUser string `env:"E2E_DVP_BASE_CLUSTER_SSH_JUMP_USER"` + SSHJumpKeyPath string `env:"E2E_DVP_BASE_CLUSTER_SSH_JUMP_KEY_PATH"` - SSHJumpHost string `env:"E2E_DVP_SSH_JUMP_HOST"` - SSHJumpUser string `env:"E2E_DVP_SSH_JUMP_USER"` - SSHJumpKeyPath string `env:"E2E_DVP_SSH_JUMP_KEY_PATH"` + KubeConfigPath string `env:"E2E_DVP_BASE_CLUSTER_KUBE_CONFIG_PATH,required"` +} + +func (c *Config) SetPassphrase() error { + err := os.Setenv("SSH_PASSPHRASE", c.SSHPassphrase) + if err != nil { + return fmt.Errorf("failed to set SSH_PASSPHRASE: %w", err) + } + return nil } // baseEndpoint builds the SSH endpoint for the DVP base cluster control-plane, diff --git a/internal/provisioning/dvp/connection.go b/internal/provisioning/dvp/connection.go index 5288aac..61251e3 100644 --- a/internal/provisioning/dvp/connection.go +++ b/internal/provisioning/dvp/connection.go @@ -67,10 +67,10 @@ type clusterConnection struct { } // openClusterConnection connects to a (possibly closed) cluster over SSH, -// reads its kubeconfig from the control-plane node, forwards the API server -// through a local SSH tunnel, and returns a kubeconfig already pointing at that -// tunnel. On any failure all partially-acquired resources are released. -func openClusterConnection(ctx context.Context, ep sshEndpoint, kubeconfigDir string) (*clusterConnection, error) { +// forwards the API server through a local SSH tunnel, loads the user-supplied +// kubeconfig from kubeconfigSrcPath, and returns a kubeconfig already pointing +// at that tunnel. On any failure all partially-acquired resources are released. +func openSSHTonnelToCluster(ctx context.Context, ep sshEndpoint, kubeconfigDir, kubeconfigSrcPath string) (*clusterConnection, error) { sshClient, err := ep.dial() if err != nil { return nil, fmt.Errorf("ssh dial %s@%s: %w", ep.User, ep.Host, err) @@ -86,10 +86,10 @@ func openClusterConnection(ctx context.Context, ep sshEndpoint, kubeconfigDir st return nil, fmt.Errorf("establish API server tunnel: %w", err) } - raw, err := fetchKubeconfig(ctx, sshClient) + raw, err := readKubeconfig(kubeconfigSrcPath) if err != nil { _ = conn.Close() - return nil, fmt.Errorf("fetch kubeconfig from %s@%s: %w", ep.User, ep.Host, err) + return nil, fmt.Errorf("load base cluster kubeconfig: %w", err) } path, err := kubeconfigFilePath(kubeconfigDir, ep.Host) diff --git a/internal/provisioning/dvp/kubeconfig.go b/internal/provisioning/dvp/kubeconfig.go index 1e10f75..5915d74 100644 --- a/internal/provisioning/dvp/kubeconfig.go +++ b/internal/provisioning/dvp/kubeconfig.go @@ -17,7 +17,6 @@ limitations under the License. package dvp import ( - "context" "fmt" "net/http" "os" @@ -27,22 +26,39 @@ import ( "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" - - "github.com/deckhouse/storage-e2e/internal/infrastructure/ssh" ) -const kubeconfigReadCommand = "sudo -n /bin/cat /etc/kubernetes/super-admin.conf 2>/dev/null " + - "|| sudo -n /bin/cat /etc/kubernetes/admin.conf" +// readKubeconfig loads the user-supplied base cluster kubeconfig from disk. +// The path may contain ~ and ${VAR} placeholders, which are expanded here. +func readKubeconfig(path string) ([]byte, error) { + resolved, err := expandKubeconfigPath(path) + if err != nil { + return nil, err + } + raw, err := os.ReadFile(resolved) + if err != nil { + return nil, fmt.Errorf("read kubeconfig %q: %w", resolved, err) + } + if strings.TrimSpace(string(raw)) == "" { + return nil, fmt.Errorf("kubeconfig %q is empty", resolved) + } + return raw, nil +} -func fetchKubeconfig(ctx context.Context, sshClient ssh.SSHClient) ([]byte, error) { - stdout, stderr, err := sshClient.ExecCapture(ctx, kubeconfigReadCommand) +// expandKubeconfigPath resolves ${VAR} and a leading ~ in a kubeconfig path. +func expandKubeconfigPath(path string) (string, error) { + expanded := os.ExpandEnv(path) + if !strings.HasPrefix(expanded, "~") { + return expanded, nil + } + home, err := os.UserHomeDir() if err != nil { - return nil, fmt.Errorf("%w (remote stderr: %s)", err, strings.TrimSpace(stderr)) + return "", fmt.Errorf("resolve home directory for %q: %w", path, err) } - if strings.TrimSpace(stdout) == "" { - return nil, fmt.Errorf("empty kubeconfig output (remote stderr: %s)", strings.TrimSpace(stderr)) + if expanded == "~" { + return home, nil } - return []byte(stdout), nil + return filepath.Join(home, strings.TrimPrefix(expanded, "~/")), nil } func buildKubeconfig(raw []byte, server, path string) (*rest.Config, error) { diff --git a/internal/provisioning/dvp/provider.go b/internal/provisioning/dvp/provider.go index 53db98e..db3c090 100644 --- a/internal/provisioning/dvp/provider.go +++ b/internal/provisioning/dvp/provider.go @@ -24,6 +24,7 @@ import ( "github.com/caarlos0/env/v11" "github.com/deckhouse/storage-e2e/internal/config" "github.com/deckhouse/storage-e2e/pkg/clusterprovider" + "github.com/deckhouse/storage-e2e/pkg/kubernetes" ) // dvpProvider provisions clusters using the DVP (Deckhouse Virtualization @@ -41,6 +42,10 @@ func NewDVPProvider(logger *slog.Logger, config *clusterprovider.ClusterConfig) if err := env.Parse(dvpConf); err != nil { return nil, err } + err := dvpConf.SetPassphrase() + if err != nil { + return nil, err + } return &dvpProvider{ cfg: config, @@ -63,15 +68,12 @@ func (p *dvpProvider) Bootstrap(ctx context.Context) error { "workers", len(clusterDef.Workers), ) - // Step 2: connect to the (closed) DVP base cluster — open an SSH tunnel to - // its API server and load its kubeconfig. The connection lives only for the - // duration of Bootstrap and is closed on return; it is NOT shared with - // Remove, which runs in a separate process/CI job and opens its own. p.logger.Info("connecting to DVP base cluster", "host", p.dvpConf.SSHHost, "jumpHost", p.dvpConf.SSHJumpHost, + "kubeconfigSource", p.dvpConf.KubeConfigPath, ) - conn, err := openClusterConnection(ctx, p.dvpConf.baseEndpoint(), config.E2ETempDir) + conn, err := openSSHTonnelToCluster(ctx, p.dvpConf.baseEndpoint(), config.E2ETempDir, p.dvpConf.KubeConfigPath) if err != nil { return fmt.Errorf("connect to DVP base cluster: %w", err) } @@ -85,6 +87,14 @@ func (p *dvpProvider) Bootstrap(ctx context.Context) error { "apiServer", conn.Kubeconfig.Host, ) + p.logger.Info("waiting for virtualization module to become ready", + "timeout", config.ModuleCheckTimeout, + ) + if err := kubernetes.WaitForModuleReady(ctx, conn.Kubeconfig, "virtualization", config.ModuleCheckTimeout); err != nil { + return fmt.Errorf("virtualization module not ready: %w", err) + } + p.logger.Info("virtualization module is ready") + return nil } diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index a7ccb69..6f3877d 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -43,7 +43,6 @@ import ( "github.com/deckhouse/storage-e2e/internal/config" "github.com/deckhouse/storage-e2e/internal/infrastructure/ssh" "github.com/deckhouse/storage-e2e/internal/kubernetes/commander" - "github.com/deckhouse/storage-e2e/internal/kubernetes/deckhouse" "github.com/deckhouse/storage-e2e/internal/kubernetes/virtualization" "github.com/deckhouse/storage-e2e/internal/logger" "github.com/deckhouse/storage-e2e/pkg/kubernetes" @@ -316,19 +315,14 @@ func CreateTestCluster( logger.StepComplete(2, "Connected to base cluster successfully") logger.Step(3, "Verifying virtualization module is Ready") - // Step 3: Verify virtualization module is Ready - moduleCtx, cancel := context.WithTimeout(ctx, config.ModuleCheckTimeout) - module, err := deckhouse.GetModule(moduleCtx, baseClusterResources.Kubeconfig, "virtualization") - cancel() - if err != nil { - baseClusterResources.SSHClient.Close() - baseClusterResources.TunnelInfo.StopFunc() - return nil, fmt.Errorf("failed to get virtualization module: %w", err) - } - if module.Status.Phase != "Ready" { + // Step 3: Verify virtualization module is Ready. + // Poll until the module converges instead of taking a single snapshot — + // the one-shot phase check was flaky because the module can briefly report + // a non-Ready phase right after we connect to the base cluster. + if err := kubernetes.WaitForModuleReady(ctx, baseClusterResources.Kubeconfig, "virtualization", config.ModuleCheckTimeout); err != nil { baseClusterResources.SSHClient.Close() baseClusterResources.TunnelInfo.StopFunc() - return nil, fmt.Errorf("virtualization module is not Ready (phase: %s)", module.Status.Phase) + return nil, fmt.Errorf("virtualization module is not Ready: %w", err) } logger.StepComplete(3, "Virtualization module is Ready") diff --git a/pkg/kubernetes/modules.go b/pkg/kubernetes/modules.go index 94490a7..e5de6c3 100644 --- a/pkg/kubernetes/modules.go +++ b/pkg/kubernetes/modules.go @@ -25,6 +25,7 @@ import ( "k8s.io/client-go/rest" + deckhousev1alpha1 "github.com/deckhouse/deckhouse/deckhouse-controller/pkg/apis/deckhouse.io/v1alpha1" "github.com/deckhouse/storage-e2e/internal/config" "github.com/deckhouse/storage-e2e/internal/infrastructure/ssh" "github.com/deckhouse/storage-e2e/internal/kubernetes/deckhouse" @@ -571,37 +572,86 @@ func WaitForModulesReady(ctx context.Context, kubeconfig *rest.Config, clusterDe return nil } -// WaitForModuleReady waits for a module to reach the Ready phase -// It continues waiting even if the module is temporarily in Error phase, as modules can recover. -// Only fails if the timeout is exceeded and the module is still not Ready. +// moduleReadyPollInterval is how often WaitForModuleReady re-reads the Module +// status while waiting for it to converge to the Ready phase. +const moduleReadyPollInterval = 2 * time.Second + +// WaitForModuleReady polls a Deckhouse Module until it reaches the Ready phase. +// +// Reliability notes (this replaces an earlier one-shot phase check that was +// flaky during cluster creation): +// - The whole wait is bounded by a derived context deadline, so persistent +// GetModule failures (dropped SSH tunnel, API hiccup) also time out instead +// of hanging until the parent context is cancelled. +// - Transient GetModule errors and intermediate phases (Downloading, +// Installing, Reconciling, and even Error — modules can recover) are +// tolerated; only the timeout is terminal. +// - On timeout the error carries the last observed phase and the IsReady +// condition message so a stuck module is diagnosable from logs alone. func WaitForModuleReady(ctx context.Context, kubeconfig *rest.Config, moduleName string, timeout time.Duration) error { - deadline := time.Now().Add(timeout) - ticker := time.NewTicker(2 * time.Second) + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + var lastPhase, lastCondition string + + // ready re-reads the module and reports whether it has converged, recording + // the latest phase/condition for diagnostics on timeout. + ready := func() bool { + module, err := deckhouse.GetModule(ctx, kubeconfig, moduleName) + if err != nil { + // Module may not be registered yet, or the API/tunnel had a + // transient failure. Keep waiting until the context deadline. + lastPhase = "" + logger.Debug("Waiting for module %s: not retrievable yet: %v", moduleName, err) + return false + } + lastPhase = module.Status.Phase + lastCondition = moduleReadyConditionMessage(module) + return module.Status.Phase == deckhousev1alpha1.ModulePhaseReady + } + + // Check once up front so an already-Ready module returns without paying the + // first poll interval. + if ready() { + return nil + } + + ticker := time.NewTicker(moduleReadyPollInterval) defer ticker.Stop() for { select { case <-ctx.Done(): - return ctx.Err() + return fmt.Errorf("timeout waiting for module %s to become ready after %v (last phase %q%s): %w", + moduleName, timeout, lastPhase, moduleConditionSuffix(lastCondition), ctx.Err()) case <-ticker.C: - module, err := deckhouse.GetModule(ctx, kubeconfig, moduleName) - if err != nil { - // Module doesn't exist yet, continue waiting - continue - } - - if module.Status.Phase == "Ready" { + if ready() { return nil } + } + } +} - // Check timeout only after checking the phase - // This ensures we wait the full timeout period even if module is in Error phase - if time.Now().After(deadline) { - if module.Status.Phase == "Error" { - return fmt.Errorf("timeout waiting for module %s to be ready: module is still in Error phase after %v", moduleName, timeout) - } - return fmt.Errorf("timeout waiting for module %s to be ready: module is in %s phase after %v", moduleName, module.Status.Phase, timeout) - } +// moduleReadyConditionMessage extracts a human-readable summary of the module's +// IsReady condition for diagnostics. Returns "" when the condition is absent. +func moduleReadyConditionMessage(module *deckhouse.Module) string { + for _, cond := range module.Status.Conditions { + if cond.Type != deckhousev1alpha1.ModuleConditionIsReady { + continue + } + if cond.Reason != "" || cond.Message != "" { + return fmt.Sprintf("IsReady=%s reason=%q message=%q", cond.Status, cond.Reason, cond.Message) } + return fmt.Sprintf("IsReady=%s", cond.Status) + } + return "" +} + +// moduleConditionSuffix formats a condition summary as a ", " suffix, +// or "" when there is nothing to report. +func moduleConditionSuffix(condition string) string { + if condition == "" { + return "" } + return ", " + condition } From 6bbba10c8d1af9acc8d1c8e0cd30ed9e8cc635fb Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Sun, 14 Jun 2026 13:52:22 +0300 Subject: [PATCH 07/23] feat: refactor SSH tunnel connection and kubeconfig loading for improved clarity and resource management --- internal/provisioning/dvp/connection.go | 45 +++++-------------------- internal/provisioning/dvp/kubeconfig.go | 24 +++++++++++++ internal/provisioning/dvp/provider.go | 17 +++++++--- 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/internal/provisioning/dvp/connection.go b/internal/provisioning/dvp/connection.go index 61251e3..c16b098 100644 --- a/internal/provisioning/dvp/connection.go +++ b/internal/provisioning/dvp/connection.go @@ -21,8 +21,6 @@ import ( "errors" "fmt" - "k8s.io/client-go/rest" - "github.com/deckhouse/storage-e2e/internal/infrastructure/ssh" ) @@ -51,26 +49,19 @@ func (e sshEndpoint) dial() (ssh.SSHClient, error) { return ssh.NewClient(e.User, e.Host, e.KeyPath) } -// clusterConnection is a live SSH tunnel to a cluster's API server together -// with the kubeconfig that targets it through that tunnel. Close releases both -// the tunnel and the SSH connection. +// clusterConnection owns a live SSH tunnel to a cluster's API server and the +// underlying SSH connection. Close releases both. It does not own the derived +// kubeconfig; see loadKubeconfigViaTunnel for that. type clusterConnection struct { ssh ssh.SSHClient tunnel *ssh.TunnelInfo - - // Kubeconfig is a client-go config whose server already points at the local - // end of the tunnel; ready to use once openClusterConnection returns. - Kubeconfig *rest.Config - // KubeconfigPath is the on-disk kubeconfig (server rewritten to the local - // tunnel port, mode 0600) for tooling such as kubectl/dhctl. - KubeconfigPath string } -// openClusterConnection connects to a (possibly closed) cluster over SSH, -// forwards the API server through a local SSH tunnel, loads the user-supplied -// kubeconfig from kubeconfigSrcPath, and returns a kubeconfig already pointing -// at that tunnel. On any failure all partially-acquired resources are released. -func openSSHTonnelToCluster(ctx context.Context, ep sshEndpoint, kubeconfigDir, kubeconfigSrcPath string) (*clusterConnection, error) { +// openTunnel connects to a (possibly closed) cluster over SSH and forwards its +// API server through a local SSH tunnel. The returned connection owns the SSH +// client and the tunnel; the caller must Close it (e.g. via defer) once done. +// On any failure all partially-acquired resources are released. +func openTunnel(ctx context.Context, ep sshEndpoint) (*clusterConnection, error) { sshClient, err := ep.dial() if err != nil { return nil, fmt.Errorf("ssh dial %s@%s: %w", ep.User, ep.Host, err) @@ -86,26 +77,6 @@ func openSSHTonnelToCluster(ctx context.Context, ep sshEndpoint, kubeconfigDir, return nil, fmt.Errorf("establish API server tunnel: %w", err) } - raw, err := readKubeconfig(kubeconfigSrcPath) - if err != nil { - _ = conn.Close() - return nil, fmt.Errorf("load base cluster kubeconfig: %w", err) - } - - path, err := kubeconfigFilePath(kubeconfigDir, ep.Host) - if err != nil { - _ = conn.Close() - return nil, err - } - - server := fmt.Sprintf("https://127.0.0.1:%d", conn.tunnel.LocalPort) - conn.Kubeconfig, err = buildKubeconfig(raw, server, path) - if err != nil { - _ = conn.Close() - return nil, fmt.Errorf("build kubeconfig: %w", err) - } - conn.KubeconfigPath = path - return conn, nil } diff --git a/internal/provisioning/dvp/kubeconfig.go b/internal/provisioning/dvp/kubeconfig.go index 5915d74..3f08992 100644 --- a/internal/provisioning/dvp/kubeconfig.go +++ b/internal/provisioning/dvp/kubeconfig.go @@ -61,6 +61,30 @@ func expandKubeconfigPath(path string) (string, error) { return filepath.Join(home, strings.TrimPrefix(expanded, "~/")), nil } +// loadKubeconfigViaTunnel reads the user-supplied base cluster kubeconfig from +// kubeconfigSrcPath, rewrites its API server to the local end of an SSH tunnel +// (127.0.0.1:localPort), writes the result under kubeconfigDir (named after +// host), and returns the ready-to-use client config along with the on-disk +// path. It owns no live resources, so there is nothing to release. +func loadKubeconfigViaTunnel(localPort int, kubeconfigDir, host, kubeconfigSrcPath string) (*rest.Config, string, error) { + raw, err := readKubeconfig(kubeconfigSrcPath) + if err != nil { + return nil, "", fmt.Errorf("load base cluster kubeconfig: %w", err) + } + + path, err := kubeconfigFilePath(kubeconfigDir, host) + if err != nil { + return nil, "", err + } + + server := fmt.Sprintf("https://127.0.0.1:%d", localPort) + cfg, err := buildKubeconfig(raw, server, path) + if err != nil { + return nil, "", fmt.Errorf("build kubeconfig: %w", err) + } + return cfg, path, nil +} + func buildKubeconfig(raw []byte, server, path string) (*rest.Config, error) { apiCfg, err := clientcmd.Load(raw) if err != nil { diff --git a/internal/provisioning/dvp/provider.go b/internal/provisioning/dvp/provider.go index db3c090..894f0b4 100644 --- a/internal/provisioning/dvp/provider.go +++ b/internal/provisioning/dvp/provider.go @@ -73,24 +73,31 @@ func (p *dvpProvider) Bootstrap(ctx context.Context) error { "jumpHost", p.dvpConf.SSHJumpHost, "kubeconfigSource", p.dvpConf.KubeConfigPath, ) - conn, err := openSSHTonnelToCluster(ctx, p.dvpConf.baseEndpoint(), config.E2ETempDir, p.dvpConf.KubeConfigPath) + conn, err := openTunnel(ctx, p.dvpConf.baseEndpoint()) if err != nil { - return fmt.Errorf("connect to DVP base cluster: %w", err) + return fmt.Errorf("open tunnel to DVP base cluster: %w", err) } defer func() { if cerr := conn.Close(); cerr != nil { p.logger.Warn("close DVP base cluster connection", "err", cerr) } }() + + kubeconfig, kubeconfigPath, err := loadKubeconfigViaTunnel( + conn.tunnel.LocalPort, config.E2ETempDir, p.dvpConf.SSHHost, p.dvpConf.KubeConfigPath, + ) + if err != nil { + return fmt.Errorf("build kubeconfig for DVP base cluster: %w", err) + } p.logger.Info("connected to DVP base cluster", - "kubeconfig", conn.KubeconfigPath, - "apiServer", conn.Kubeconfig.Host, + "kubeconfig", kubeconfigPath, + "apiServer", kubeconfig.Host, ) p.logger.Info("waiting for virtualization module to become ready", "timeout", config.ModuleCheckTimeout, ) - if err := kubernetes.WaitForModuleReady(ctx, conn.Kubeconfig, "virtualization", config.ModuleCheckTimeout); err != nil { + if err := kubernetes.WaitForModuleReady(ctx, kubeconfig, "virtualization", config.ModuleCheckTimeout); err != nil { return fmt.Errorf("virtualization module not ready: %w", err) } p.logger.Info("virtualization module is ready") From ff41d9d8f50a971d599a7b35eea1aa1e5f7764cd Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Sun, 14 Jun 2026 13:56:53 +0300 Subject: [PATCH 08/23] feat: add namespace configuration for DVP test cluster and ensure its existence --- internal/provisioning/dvp/config.go | 4 ++++ internal/provisioning/dvp/provider.go | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/internal/provisioning/dvp/config.go b/internal/provisioning/dvp/config.go index 781d282..ef34e86 100644 --- a/internal/provisioning/dvp/config.go +++ b/internal/provisioning/dvp/config.go @@ -32,6 +32,10 @@ type Config struct { SSHJumpKeyPath string `env:"E2E_DVP_BASE_CLUSTER_SSH_JUMP_KEY_PATH"` KubeConfigPath string `env:"E2E_DVP_BASE_CLUSTER_KUBE_CONFIG_PATH,required"` + + // Namespace is the base-cluster namespace where DVP virtual machines for the + // test cluster are provisioned. It is created during Bootstrap if absent. + Namespace string `env:"E2E_DVP_BASE_CLUSTER_NAMESPACE" envDefault:"e2e-test-cluster"` } func (c *Config) SetPassphrase() error { diff --git a/internal/provisioning/dvp/provider.go b/internal/provisioning/dvp/provider.go index 894f0b4..dda93a3 100644 --- a/internal/provisioning/dvp/provider.go +++ b/internal/provisioning/dvp/provider.go @@ -102,6 +102,17 @@ func (p *dvpProvider) Bootstrap(ctx context.Context) error { } p.logger.Info("virtualization module is ready") + p.logger.Info("ensuring test namespace exists", + "namespace", p.dvpConf.Namespace, + "timeout", config.NamespaceTimeout, + ) + nsCtx, cancel := context.WithTimeout(ctx, config.NamespaceTimeout) + defer cancel() + if _, err := kubernetes.CreateNamespaceIfNotExists(nsCtx, kubeconfig, p.dvpConf.Namespace); err != nil { + return fmt.Errorf("ensure namespace %q: %w", p.dvpConf.Namespace, err) + } + p.logger.Info("test namespace is ready", "namespace", p.dvpConf.Namespace) + return nil } From 8ecfb22b08d6ec0068f5cf2f37cf1e79c5d6b786 Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Mon, 15 Jun 2026 13:04:36 +0300 Subject: [PATCH 09/23] feat: improve kubeconfig path handling and enhance module readiness verification --- internal/provisioning/dvp/config.go | 6 +----- internal/provisioning/dvp/connection.go | 17 ----------------- internal/provisioning/dvp/kubeconfig.go | 12 ++---------- internal/provisioning/dvp/provider.go | 4 ---- pkg/cluster/cluster.go | 4 ---- 5 files changed, 3 insertions(+), 40 deletions(-) diff --git a/internal/provisioning/dvp/config.go b/internal/provisioning/dvp/config.go index ef34e86..9ef3077 100644 --- a/internal/provisioning/dvp/config.go +++ b/internal/provisioning/dvp/config.go @@ -31,10 +31,8 @@ type Config struct { SSHJumpUser string `env:"E2E_DVP_BASE_CLUSTER_SSH_JUMP_USER"` SSHJumpKeyPath string `env:"E2E_DVP_BASE_CLUSTER_SSH_JUMP_KEY_PATH"` - KubeConfigPath string `env:"E2E_DVP_BASE_CLUSTER_KUBE_CONFIG_PATH,required"` + KubeConfigPath string `env:"E2E_DVP_BASE_CLUSTER_KUBECONFIG_PATH,required"` - // Namespace is the base-cluster namespace where DVP virtual machines for the - // test cluster are provisioned. It is created during Bootstrap if absent. Namespace string `env:"E2E_DVP_BASE_CLUSTER_NAMESPACE" envDefault:"e2e-test-cluster"` } @@ -46,8 +44,6 @@ func (c *Config) SetPassphrase() error { return nil } -// baseEndpoint builds the SSH endpoint for the DVP base cluster control-plane, -// routing through the jump host when one is configured. func (c *Config) baseEndpoint() sshEndpoint { ep := sshEndpoint{User: c.SSHUser, Host: c.SSHHost, KeyPath: c.SSHKeyPath} if c.SSHJumpHost == "" { diff --git a/internal/provisioning/dvp/connection.go b/internal/provisioning/dvp/connection.go index c16b098..4bab332 100644 --- a/internal/provisioning/dvp/connection.go +++ b/internal/provisioning/dvp/connection.go @@ -24,12 +24,8 @@ import ( "github.com/deckhouse/storage-e2e/internal/infrastructure/ssh" ) -// apiServerRemotePort is the port the cluster API server listens on. It is -// forwarded to an ephemeral local port through the SSH tunnel. const apiServerRemotePort = "6445" -// sshEndpoint describes how to reach a host over SSH. When Jump is non-nil the -// connection is routed through it (jump -> target); otherwise it is direct. type sshEndpoint struct { User string Host string @@ -37,8 +33,6 @@ type sshEndpoint struct { Jump *sshEndpoint } -// dial opens an SSH connection to the endpoint, transparently routing through a -// jump host when one is configured. func (e sshEndpoint) dial() (ssh.SSHClient, error) { if e.Jump != nil { return ssh.NewClientWithJumpHost( @@ -49,18 +43,11 @@ func (e sshEndpoint) dial() (ssh.SSHClient, error) { return ssh.NewClient(e.User, e.Host, e.KeyPath) } -// clusterConnection owns a live SSH tunnel to a cluster's API server and the -// underlying SSH connection. Close releases both. It does not own the derived -// kubeconfig; see loadKubeconfigViaTunnel for that. type clusterConnection struct { ssh ssh.SSHClient tunnel *ssh.TunnelInfo } -// openTunnel connects to a (possibly closed) cluster over SSH and forwards its -// API server through a local SSH tunnel. The returned connection owns the SSH -// client and the tunnel; the caller must Close it (e.g. via defer) once done. -// On any failure all partially-acquired resources are released. func openTunnel(ctx context.Context, ep sshEndpoint) (*clusterConnection, error) { sshClient, err := ep.dial() if err != nil { @@ -69,8 +56,6 @@ func openTunnel(ctx context.Context, ep sshEndpoint) (*clusterConnection, error) conn := &clusterConnection{ssh: sshClient} - // The tunnel's lifetime is bound to ctx: it stops on ctx cancellation - // (e.g. the Bootstrap deadline) or when Close is called explicitly. conn.tunnel, err = sshClient.OpenTunnel(ctx, apiServerRemotePort) if err != nil { _ = conn.Close() @@ -80,8 +65,6 @@ func openTunnel(ctx context.Context, ep sshEndpoint) (*clusterConnection, error) return conn, nil } -// Close stops the API server tunnel and closes the SSH connection, joining any -// errors. It is safe to call on a nil or partially-initialised connection. func (c *clusterConnection) Close() error { if c == nil { return nil diff --git a/internal/provisioning/dvp/kubeconfig.go b/internal/provisioning/dvp/kubeconfig.go index 3f08992..a7c9a99 100644 --- a/internal/provisioning/dvp/kubeconfig.go +++ b/internal/provisioning/dvp/kubeconfig.go @@ -28,10 +28,8 @@ import ( "k8s.io/client-go/tools/clientcmd" ) -// readKubeconfig loads the user-supplied base cluster kubeconfig from disk. -// The path may contain ~ and ${VAR} placeholders, which are expanded here. func readKubeconfig(path string) ([]byte, error) { - resolved, err := expandKubeconfigPath(path) + resolved, err := expandUserPath(path) if err != nil { return nil, err } @@ -45,8 +43,7 @@ func readKubeconfig(path string) ([]byte, error) { return raw, nil } -// expandKubeconfigPath resolves ${VAR} and a leading ~ in a kubeconfig path. -func expandKubeconfigPath(path string) (string, error) { +func expandUserPath(path string) (string, error) { expanded := os.ExpandEnv(path) if !strings.HasPrefix(expanded, "~") { return expanded, nil @@ -61,11 +58,6 @@ func expandKubeconfigPath(path string) (string, error) { return filepath.Join(home, strings.TrimPrefix(expanded, "~/")), nil } -// loadKubeconfigViaTunnel reads the user-supplied base cluster kubeconfig from -// kubeconfigSrcPath, rewrites its API server to the local end of an SSH tunnel -// (127.0.0.1:localPort), writes the result under kubeconfigDir (named after -// host), and returns the ready-to-use client config along with the on-disk -// path. It owns no live resources, so there is nothing to release. func loadKubeconfigViaTunnel(localPort int, kubeconfigDir, host, kubeconfigSrcPath string) (*rest.Config, string, error) { raw, err := readKubeconfig(kubeconfigSrcPath) if err != nil { diff --git a/internal/provisioning/dvp/provider.go b/internal/provisioning/dvp/provider.go index dda93a3..0d3e247 100644 --- a/internal/provisioning/dvp/provider.go +++ b/internal/provisioning/dvp/provider.go @@ -27,16 +27,12 @@ import ( "github.com/deckhouse/storage-e2e/pkg/kubernetes" ) -// dvpProvider provisions clusters using the DVP (Deckhouse Virtualization -// Platform) strategy. type dvpProvider struct { cfg *clusterprovider.ClusterConfig dvpConf *Config logger *slog.Logger } -// NewDVPProvider builds a dvpProvider, loading the DVP-specific env. It is wired -// into the provider Registry by internal/provisioning/register. func NewDVPProvider(logger *slog.Logger, config *clusterprovider.ClusterConfig) (clusterprovider.Provider, error) { dvpConf := &Config{} if err := env.Parse(dvpConf); err != nil { diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 6f3877d..742c6dd 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -315,10 +315,6 @@ func CreateTestCluster( logger.StepComplete(2, "Connected to base cluster successfully") logger.Step(3, "Verifying virtualization module is Ready") - // Step 3: Verify virtualization module is Ready. - // Poll until the module converges instead of taking a single snapshot — - // the one-shot phase check was flaky because the module can briefly report - // a non-Ready phase right after we connect to the base cluster. if err := kubernetes.WaitForModuleReady(ctx, baseClusterResources.Kubeconfig, "virtualization", config.ModuleCheckTimeout); err != nil { baseClusterResources.SSHClient.Close() baseClusterResources.TunnelInfo.StopFunc() From 797e7569d6e22b5451cb85aea6bc61b74873846f Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Mon, 15 Jun 2026 13:14:06 +0300 Subject: [PATCH 10/23] feat: rename New function to NewClusterConfig for clarity and update related tests --- cmd/bootstrap-cluster/main.go | 2 +- cmd/remove-cluster/main.go | 2 +- pkg/clusterprovider/config.go | 2 +- pkg/clusterprovider/config_test.go | 42 +++++++++++------------------- 4 files changed, 18 insertions(+), 30 deletions(-) diff --git a/cmd/bootstrap-cluster/main.go b/cmd/bootstrap-cluster/main.go index 7f2197a..9795cdb 100644 --- a/cmd/bootstrap-cluster/main.go +++ b/cmd/bootstrap-cluster/main.go @@ -15,7 +15,7 @@ import ( ) func main() { - cfg, err := clusterprovider.New() + cfg, err := clusterprovider.NewClusterConfig() if err != nil { log.Fatal("failed to initialize config - ", err) } diff --git a/cmd/remove-cluster/main.go b/cmd/remove-cluster/main.go index c1790f1..ee23e4e 100644 --- a/cmd/remove-cluster/main.go +++ b/cmd/remove-cluster/main.go @@ -16,7 +16,7 @@ import ( ) func main() { - cfg, err := clusterprovider.New() + cfg, err := clusterprovider.NewClusterConfig() if err != nil { log.Fatal("failed to initialize config - ", err) } diff --git a/pkg/clusterprovider/config.go b/pkg/clusterprovider/config.go index e7a6f1d..41bdfe1 100644 --- a/pkg/clusterprovider/config.go +++ b/pkg/clusterprovider/config.go @@ -25,7 +25,7 @@ type ClusterConfig struct { ClusterBootstrapConfigPath string `env:"E2E_CLUSTER_CONFIG_YAML_PATH,required"` } -func New() (*ClusterConfig, error) { +func NewClusterConfig() (*ClusterConfig, error) { cfg := &ClusterConfig{} parseErr := env.Parse(cfg) if parseErr != nil { diff --git a/pkg/clusterprovider/config_test.go b/pkg/clusterprovider/config_test.go index 2c4135d..7100969 100644 --- a/pkg/clusterprovider/config_test.go +++ b/pkg/clusterprovider/config_test.go @@ -21,10 +21,11 @@ import ( "testing" ) -const clusterProviderEnv = "TEST_CLUSTER_PROVIDER" +const ( + clusterProviderEnv = "E2E_TEST_CLUSTER_PROVIDER" + clusterConfigYamlPathEnv = "E2E_CLUSTER_CONFIG_YAML_PATH" +) -// unsetClusterProviderEnv removes TEST_CLUSTER_PROVIDER for the duration of the -// test and restores its original value (set or unset) afterwards. func unsetClusterProviderEnv(t *testing.T) { t.Helper() orig, had := os.LookupEnv(clusterProviderEnv) @@ -41,16 +42,17 @@ func unsetClusterProviderEnv(t *testing.T) { } func TestNew_ParsesProvider(t *testing.T) { - t.Setenv(clusterProviderEnv, "static") + t.Setenv(clusterProviderEnv, ModeDVP) + t.Setenv(clusterConfigYamlPathEnv, "/tmp/cluster-config.yaml") - cfg, err := New() + cfg, err := NewClusterConfig() if err != nil { t.Fatalf("unexpected error: %v", err) } if cfg == nil { t.Fatal("expected non-nil config") } - if cfg.ClusterProvider != "static" { + if cfg.ClusterProvider != ModeDVP { t.Errorf("ClusterProvider = %q, want %q", cfg.ClusterProvider, "static") } } @@ -58,7 +60,7 @@ func TestNew_ParsesProvider(t *testing.T) { func TestNew_RequiredProviderMissing(t *testing.T) { unsetClusterProviderEnv(t) - cfg, err := New() + cfg, err := NewClusterConfig() if err == nil { t.Fatal("expected error when required env var is missing") } @@ -67,34 +69,20 @@ func TestNew_RequiredProviderMissing(t *testing.T) { } } -// env/v11 treats the `required` tag as a presence check: an empty value still -// counts as set, so parsing succeeds and the field is left empty. -func TestNew_EmptyProviderAccepted(t *testing.T) { - t.Setenv(clusterProviderEnv, "") - - cfg, err := New() - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if cfg.ClusterProvider != "" { - t.Errorf("ClusterProvider = %q, want empty string", cfg.ClusterProvider) - } -} - func TestNew_ProviderValues(t *testing.T) { tests := []struct { name string - value string + value ProviderMode }{ - {name: "static", value: "static"}, - {name: "cloud-ephemeral", value: "CloudEphemeral"}, - {name: "with-spaces", value: "some provider"}, + {name: "dvp", value: ModeDVP}, + {name: "commander", value: ModeCommander}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - t.Setenv(clusterProviderEnv, tt.value) + t.Setenv(clusterProviderEnv, tt.value.String()) + t.Setenv(clusterConfigYamlPathEnv, "/tmp/cluster-config.yaml") - cfg, err := New() + cfg, err := NewClusterConfig() if err != nil { t.Fatalf("unexpected error: %v", err) } From 9a182efbc163398995166726869c6c8b67f8a8ac Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Mon, 15 Jun 2026 13:15:38 +0300 Subject: [PATCH 11/23] feat: simplify registry constructor and enhance thread safety for provider registration --- pkg/clusterprovider/registry/registry.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pkg/clusterprovider/registry/registry.go b/pkg/clusterprovider/registry/registry.go index 26b029d..f9d3b13 100644 --- a/pkg/clusterprovider/registry/registry.go +++ b/pkg/clusterprovider/registry/registry.go @@ -25,37 +25,27 @@ import ( "github.com/deckhouse/storage-e2e/pkg/clusterprovider" ) -// DefaultRegistry is the package-level registry strategies self-register into. var DefaultRegistry = NewRegistry() -// Constructor builds a Provider, loading its own strategy-specific env. It runs -// lazily — only for the strategy selected at runtime — so unselected strategies -// never read their env. Validate on the resulting Provider stays a pure check -// (no loading, no I/O). type Constructor func(logger *slog.Logger, config *clusterprovider.ClusterConfig) (clusterprovider.Provider, error) -// Registry maps strategy names to their Constructor. type Registry struct { mu sync.RWMutex constructors map[string]Constructor } -// NewRegistry returns a Registry. func NewRegistry() *Registry { return &Registry{constructors: map[string]Constructor{ clusterprovider.ModeDVP: dvp.NewDVPProvider, }} } -// Register adds a Constructor under name. A later registration with the same -// name overwrites the earlier one. func (r *Registry) Register(name string, c Constructor) { r.mu.Lock() defer r.mu.Unlock() r.constructors[name] = c } -// Get returns the Constructor registered under name. func (r *Registry) Get(name clusterprovider.ProviderMode) (Constructor, error) { r.mu.RLock() defer r.mu.RUnlock() From 903c40b58c62092321247b444e286e574b1cc3d2 Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Mon, 15 Jun 2026 13:21:18 +0300 Subject: [PATCH 12/23] feat: clean up comments and improve readability in module overrides --- internal/config/module_overrides.go | 24 ------------------------ internal/config/module_overrides_test.go | 2 -- 2 files changed, 26 deletions(-) diff --git a/internal/config/module_overrides.go b/internal/config/module_overrides.go index f48af15..fc1200d 100644 --- a/internal/config/module_overrides.go +++ b/internal/config/module_overrides.go @@ -22,34 +22,10 @@ import ( "strings" ) -// EnvLookup resolves a variable by name and reports whether it is set. It -// mirrors os.LookupEnv so the standard environment can be injected directly, -// while tests can supply a map-backed source without mutating the process -// environment. type EnvLookup func(name string) (value string, ok bool) -// envRefPattern matches a single ${NAME} reference. Only the braced form with -// shell-identifier characters is recognized, so a tag containing a bare '$' -// is never rewritten and substitution intent is always explicit. The same -// pattern drives both detection and replacement, so a reference can never be -// detected one way and substituted another. var envRefPattern = regexp.MustCompile(`\$\{([A-Za-z_][A-Za-z0-9_]*)\}`) -// ResolveModulePullOverrides substitutes ${NAME} references in every module's -// ModulePullOverride using lookup, letting CI point modules at per-build image -// tags without editing the YAML: -// -// modules: -// - name: csi-ceph -// modulePullOverride: "${CSI_CEPH_TAG}" -// -// Each module may reference its own variable, and a field may contain several -// references. All problems across all modules are reported together: -// - a reference to a variable that lookup does not provide, and -// - a residual "${" left after substitution (a malformed reference such as -// ${bad-name}), which guarantees no placeholder ever reaches the cluster. -// -// Modules without references are left untouched. func ResolveModulePullOverrides(def *ClusterDefinition, lookup EnvLookup) error { if def == nil { return nil diff --git a/internal/config/module_overrides_test.go b/internal/config/module_overrides_test.go index 57283ae..f35bcec 100644 --- a/internal/config/module_overrides_test.go +++ b/internal/config/module_overrides_test.go @@ -21,8 +21,6 @@ import ( "testing" ) -// lookupFrom returns an EnvLookup backed by the given map, so tests stay -// hermetic and never touch the process environment. func lookupFrom(env map[string]string) EnvLookup { return func(name string) (string, bool) { v, ok := env[name] From d2e55528b2650e62fb6802280e47169e7f15d5cd Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Mon, 15 Jun 2026 13:22:01 +0300 Subject: [PATCH 13/23] feat: refactor LoadClusterDefinition and Validate functions for improved clarity and maintainability --- internal/config/cluster_config.go | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/internal/config/cluster_config.go b/internal/config/cluster_config.go index f4fb305..27ae39a 100644 --- a/internal/config/cluster_config.go +++ b/internal/config/cluster_config.go @@ -23,28 +23,17 @@ import ( "gopkg.in/yaml.v3" ) -// LoadClusterDefinition reads, parses, and validates a cluster topology -// definition from the YAML file at the given path. -// -// The path is taken as-is: callers are expected to provide an explicit -// (absolute or cwd-relative) path rather than relying on the loader to guess -// the location of the file. func LoadClusterDefinition(path string) (*ClusterDefinition, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read cluster config %q: %w", path, err) } - // ClusterDefinition has a custom UnmarshalYAML that accepts both a - // top-level "clusterDefinition:" key and a bare document. var def ClusterDefinition if err := yaml.Unmarshal(data, &def); err != nil { return nil, fmt.Errorf("parse cluster config %q: %w", path, err) } - // Resolve ${NAME} references in modulePullOverride from the environment - // before validation, so CI can pin per-build image tags without editing - // the YAML. Validation then only ever sees resolved, literal tags. if err := ResolveModulePullOverrides(&def, os.LookupEnv); err != nil { return nil, fmt.Errorf("resolve cluster config %q: %w", path, err) } @@ -56,12 +45,6 @@ func LoadClusterDefinition(path string) (*ClusterDefinition, error) { return &def, nil } -// Validate checks that the cluster definition contains the minimum topology and -// DKP parameters required to bootstrap a cluster. -// -// It does not inspect modulePullOverride: ${NAME} references are resolved -// separately by ResolveModulePullOverrides at load time, so Validate stays a -// pure, environment-independent check. func (c *ClusterDefinition) Validate() error { if len(c.Masters) == 0 { return fmt.Errorf("at least one master node is required") From 677ba303ad5aac00346a8b9c38ecac2b5a0282a0 Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Tue, 16 Jun 2026 16:20:32 +0300 Subject: [PATCH 14/23] feat: enhance error handling and improve variable naming for clarity Signed-off-by: Daniil Studenikin --- cmd/bootstrap-cluster/main.go | 16 +- cmd/remove-cluster/main.go | 20 +- go.mod | 213 +++++++++++- go.sum | 437 ++++++++++++++++++++++++ internal/provisioning/dvp/kubeconfig.go | 4 +- internal/provisioning/dvp/provider.go | 5 +- pkg/cluster/cluster.go | 4 +- pkg/kubernetes/modules.go | 4 +- 8 files changed, 671 insertions(+), 32 deletions(-) diff --git a/cmd/bootstrap-cluster/main.go b/cmd/bootstrap-cluster/main.go index 9795cdb..5bfff22 100644 --- a/cmd/bootstrap-cluster/main.go +++ b/cmd/bootstrap-cluster/main.go @@ -1,6 +1,3 @@ -// Command bootstrap-cluster is the CI-only entrypoint that provisions a -// cluster. It is a thin wrapper: load ClusterConfig, resolve the strategy's -// Constructor from the provider Registry, build the Provider, then Bootstrap. package main import ( @@ -15,9 +12,12 @@ import ( ) func main() { + slogger := logger.GetLogger() + cfg, err := clusterprovider.NewClusterConfig() if err != nil { - log.Fatal("failed to initialize config - ", err) + slogger.Error("failed to initialize config - ", err) + return } newProvider, registryGetErr := registry.DefaultRegistry.Get(cfg.ClusterProvider) @@ -25,16 +25,18 @@ func main() { log.Fatal("failed to get provider", registryGetErr) } - slogger := logger.GetLogger() clusterProvider, err := newProvider(slogger, cfg) if err != nil { - log.Fatal("failed to build provider", err) + slogger.Error("failed to build provider", err) + return } bootstrapCtx, bootstrapCancel := context.WithTimeout(context.Background(), time.Minute*45) defer bootstrapCancel() + bootstrapErr := clusterProvider.Bootstrap(bootstrapCtx) if bootstrapErr != nil { - log.Fatal("failed to bootstrap cluster", bootstrapErr) + slogger.Error("failed to bootstrap cluster", bootstrapErr) + return } } diff --git a/cmd/remove-cluster/main.go b/cmd/remove-cluster/main.go index ee23e4e..d002e7c 100644 --- a/cmd/remove-cluster/main.go +++ b/cmd/remove-cluster/main.go @@ -1,13 +1,7 @@ -// Command remove-cluster is the CI-only entrypoint that tears a cluster down. -// It is a thin wrapper: load ClusterConfig, resolve the strategy's Constructor -// from the provider Registry, build the Provider, then run the idempotent -// Remove (which derives target resources from config, not from bootstrap -// artifacts). package main import ( "context" - "log" "github.com/deckhouse/storage-e2e/internal/logger" "github.com/deckhouse/storage-e2e/pkg/clusterprovider/registry" @@ -16,24 +10,28 @@ import ( ) func main() { + slogger := logger.GetLogger() cfg, err := clusterprovider.NewClusterConfig() if err != nil { - log.Fatal("failed to initialize config - ", err) + slogger.Error("failed to initialize config - ", err) + return } newProvider, registryGetErr := registry.DefaultRegistry.Get(cfg.ClusterProvider) if registryGetErr != nil { - log.Fatal("failed to get provider", registryGetErr) + slogger.Error("failed to get provider", registryGetErr) + return } - slogger := logger.GetLogger() clusterProvider, err := newProvider(slogger, cfg) if err != nil { - log.Fatal("failed to build provider", err) + slogger.Error("failed to build provider", err) + return } teardownErr := clusterProvider.Remove(context.TODO()) if teardownErr != nil { - log.Fatal("failed to tear down cluster", teardownErr) + slogger.Error("failed to tear down cluster", teardownErr) + return } } diff --git a/go.mod b/go.mod index 8722dbd..8665fdd 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,8 @@ require ( github.com/deckhouse/virtualization/api v1.8.0 github.com/go-logr/logr v1.4.3 github.com/go-playground/validator/v10 v10.30.3 - github.com/onsi/ginkgo/v2 v2.23.3 - github.com/onsi/gomega v1.37.0 + github.com/onsi/ginkgo/v2 v2.28.2 + github.com/onsi/gomega v1.39.1 github.com/pkg/sftp v1.13.10 golang.org/x/crypto v0.52.0 golang.org/x/sync v0.20.0 @@ -23,12 +23,75 @@ require ( ) require ( - github.com/Masterminds/semver/v3 v3.3.1 // indirect + 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect + 4d63.com/gochecknoglobals v0.2.2 // indirect + charm.land/lipgloss/v2 v2.0.3 // indirect + codeberg.org/chavacava/garif v0.2.0 // indirect + codeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect + dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect + dev.gaijin.team/go/golib v0.6.0 // indirect + github.com/4meepo/tagalign v1.4.3 // indirect + github.com/Abirdcfly/dupword v0.1.7 // indirect + github.com/AdminBenni/iota-mixing v1.0.0 // indirect + github.com/AlwxSin/noinlineerr v1.0.5 // indirect + github.com/Antonboom/errname v1.1.1 // indirect + github.com/Antonboom/nilnil v1.1.1 // indirect + github.com/Antonboom/testifylint v1.6.4 // indirect + github.com/BurntSushi/toml v1.6.0 // indirect + github.com/ClickHouse/clickhouse-go-linter v1.2.0 // indirect + github.com/Djarvur/go-err113 v0.1.1 // indirect + github.com/Masterminds/semver/v3 v3.5.0 // indirect + github.com/MirrexOne/unqueryvet v1.5.4 // indirect + github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect + github.com/alecthomas/chroma/v2 v2.24.1 // indirect + github.com/alecthomas/go-check-sumtype v0.3.1 // indirect + github.com/alexkohler/nakedret/v2 v2.0.6 // indirect + github.com/alexkohler/prealloc v1.1.0 // indirect + github.com/alfatraining/structtag v1.0.0 // indirect + github.com/alingse/asasalint v0.0.11 // indirect + github.com/alingse/nilnesserr v0.2.0 // indirect + github.com/ashanbrown/forbidigo/v2 v2.3.1 // indirect + github.com/ashanbrown/makezero/v2 v2.2.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bkielbasa/cyclop v1.2.3 // indirect + github.com/blizzy78/varnamelen v0.8.0 // indirect + github.com/bombsimon/wsl/v4 v4.7.0 // indirect + github.com/bombsimon/wsl/v5 v5.8.0 // indirect + github.com/breml/bidichk v0.3.3 // indirect + github.com/breml/errchkjson v0.4.1 // indirect + github.com/butuzov/ireturn v0.4.1 // indirect + github.com/butuzov/mirror v1.3.0 // indirect + github.com/catenacyber/perfsprint v0.10.1 // indirect + github.com/ccojocar/zxcvbn-go v1.0.4 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/charithe/durationcheck v0.0.11 // indirect + github.com/charmbracelet/colorprofile v0.4.3 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 // indirect + github.com/charmbracelet/x/ansi v0.11.7 // indirect + github.com/charmbracelet/x/term v0.2.2 // indirect + github.com/charmbracelet/x/termios v0.1.1 // indirect + github.com/charmbracelet/x/windows v0.2.2 // indirect + github.com/ckaznocha/intrange v0.3.1 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect + github.com/curioswitch/go-reassign v0.3.0 // indirect + github.com/daixiang0/gci v0.13.7 // indirect + github.com/dave/dst v0.27.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/denis-tingaikin/go-header v0.5.0 // indirect + github.com/dlclark/regexp2 v1.12.0 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/ettle/strcase v0.2.0 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/fatih/color v1.19.0 // indirect + github.com/fatih/structtag v1.2.0 // indirect + github.com/firefart/nonamedreturns v1.0.6 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/fzipp/gocyclo v0.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect + github.com/ghostiam/protogetter v0.3.20 // indirect + github.com/go-critic/go-critic v0.14.3 // indirect github.com/go-openapi/jsonpointer v0.22.1 // indirect github.com/go-openapi/jsonreference v0.21.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect @@ -36,37 +99,171 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/go-toolsmith/astcast v1.1.0 // indirect + github.com/go-toolsmith/astcopy v1.1.0 // indirect + github.com/go-toolsmith/astequal v1.2.0 // indirect + github.com/go-toolsmith/astfmt v1.1.0 // indirect + github.com/go-toolsmith/astp v1.1.0 // indirect + github.com/go-toolsmith/strparse v1.1.0 // indirect + github.com/go-toolsmith/typep v1.1.0 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect + github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/godoc-lint/godoc-lint v0.11.2 // indirect + github.com/gofrs/flock v0.13.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golangci/asciicheck v0.5.0 // indirect + github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 // indirect + github.com/golangci/go-printf-func-name v0.1.1 // indirect + github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect + github.com/golangci/golangci-lint/v2 v2.12.0 // indirect + github.com/golangci/golines v0.15.0 // indirect + github.com/golangci/misspell v0.8.0 // indirect + github.com/golangci/plugin-module-register v0.1.2 // indirect + github.com/golangci/revgrep v0.8.0 // indirect + github.com/golangci/rowserrcheck v0.0.0-20260419091836-c5f79b8a11ba // indirect + github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect + github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect + github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gordonklaus/ineffassign v0.2.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect + github.com/gostaticanalysis/analysisutil v0.7.1 // indirect + github.com/gostaticanalysis/comment v1.5.0 // indirect + github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect + github.com/gostaticanalysis/nilerr v0.1.2 // indirect + github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect + github.com/hashicorp/go-version v1.9.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/hcl v1.0.1-vault-7 // indirect + github.com/hexops/gotextdiff v1.0.3 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jgautheron/goconst v1.10.0 // indirect + github.com/jjti/go-spancheck v0.6.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/julz/importas v0.2.0 // indirect + github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect + github.com/kisielk/errcheck v1.10.0 // indirect + github.com/kkHAIKE/contextcheck v1.1.6 // indirect github.com/kr/fs v0.1.0 // indirect + github.com/kulti/thelper v0.7.1 // indirect + github.com/kunwardeep/paralleltest v1.0.15 // indirect + github.com/lasiar/canonicalheader v1.1.2 // indirect + github.com/ldez/exptostd v0.4.5 // indirect + github.com/ldez/gomoddirectives v0.8.0 // indirect + github.com/ldez/grignotin v0.10.1 // indirect + github.com/ldez/structtags v0.6.1 // indirect + github.com/ldez/tagliatelle v0.7.2 // indirect + github.com/ldez/usetesting v0.5.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/leonklingele/grouper v1.1.2 // indirect + github.com/lucasb-eyer/go-colorful v1.4.0 // indirect + github.com/macabu/inamedparam v0.2.0 // indirect + github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect + github.com/manuelarte/funcorder v0.6.0 // indirect + github.com/maratori/testableexamples v1.0.1 // indirect + github.com/maratori/testpackage v1.1.2 // indirect + github.com/matoous/godox v1.1.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.23 // indirect + github.com/mgechev/revive v1.15.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/spdystream v0.5.0 // indirect 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/moricho/tparallel v0.3.2 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/nakabonne/nestif v0.3.1 // indirect + github.com/nishanths/exhaustive v0.12.0 // indirect + github.com/nishanths/predeclared v0.2.2 // indirect + github.com/nunnatsa/ginkgolinter v0.23.0 // indirect github.com/openshift/custom-resource-status v1.1.2 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/spf13/pflag v1.0.7 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/quasilyte/go-ruleguard v0.4.5 // indirect + github.com/quasilyte/go-ruleguard/dsl v0.3.23 // indirect + github.com/quasilyte/gogrep v0.5.0 // indirect + github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect + github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect + github.com/raeperd/recvcheck v0.2.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/ryancurrah/gomodguard v1.4.1 // indirect + github.com/ryancurrah/gomodguard/v2 v2.1.0 // indirect + github.com/ryanrolds/sqlclosecheck v0.6.0 // indirect + github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect + github.com/sashamelentyev/interfacebloat v1.1.0 // indirect + github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect + github.com/securego/gosec/v2 v2.26.1 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/sivchari/containedctx v1.0.3 // indirect + github.com/sonatard/noctx v0.5.1 // indirect + github.com/sourcegraph/go-diff v0.8.0 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/cobra v1.10.2 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/spf13/viper v1.12.0 // indirect + github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect + github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/testify v1.11.1 // indirect + github.com/subosito/gotenv v1.4.1 // indirect + github.com/tetafro/godot v1.5.6 // indirect + github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 // indirect + github.com/timonwong/loggercheck v0.11.0 // indirect + github.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect + github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect + github.com/ultraware/funlen v0.2.0 // indirect + github.com/ultraware/whitespace v0.2.0 // indirect + github.com/uudashr/gocognit v1.2.1 // indirect + github.com/uudashr/iface v1.4.1 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/xen0n/gosmopolitan v1.3.0 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yagipy/maintidx v1.0.0 // indirect + github.com/yeya24/promlinter v0.3.0 // indirect + github.com/ykadowak/zerologlint v0.1.5 // indirect + gitlab.com/bosi/decorder v0.4.2 // indirect + go-simpler.org/musttag v0.14.0 // indirect + go-simpler.org/sloglint v0.12.0 // indirect + go.augendre.info/arangolint v0.4.0 // indirect + go.augendre.info/fatcontext v0.9.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 // indirect + golang.org/x/mod v0.35.0 // indirect golang.org/x/net v0.54.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sys v0.45.0 // indirect golang.org/x/text v0.37.0 // indirect golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.44.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect + google.golang.org/protobuf v1.36.10 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.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 + honnef.co/go/tools v0.7.0 // indirect k8s.io/apiextensions-apiserver v0.34.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect @@ -74,8 +271,12 @@ require ( kubevirt.io/api v1.6.2 // indirect kubevirt.io/containerized-data-importer-api v1.60.3-0.20241105012228-50fbed985de9 // indirect kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 // indirect + mvdan.cc/gofumpt v0.9.2 // indirect + mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) + +tool github.com/golangci/golangci-lint/v2/cmd/golangci-lint diff --git a/go.sum b/go.sum index 3a4585a..a0b6b09 100644 --- a/go.sum +++ b/go.sum @@ -1,25 +1,130 @@ +4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A= +4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= +4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= +4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= +charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU= +charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY= +codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ= +codeberg.org/polyfloyd/go-errorlint v1.9.0 h1:VkdEEmA1VBpH6ecQoMR4LdphVI3fA4RrCh2an7YmodI= +codeberg.org/polyfloyd/go-errorlint v1.9.0/go.mod h1:GPRRu2LzVijNn4YkrZYJfatQIdS+TrcK8rL5Xs24qw8= +dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y= +dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI= +dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo= +dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE= +github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8= +github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= +github.com/Abirdcfly/dupword v0.1.7 h1:2j8sInznrje4I0CMisSL6ipEBkeJUJAmK1/lfoNGWrQ= +github.com/Abirdcfly/dupword v0.1.7/go.mod h1:K0DkBeOebJ4VyOICFdppB23Q0YMOgVafM0zYW0n9lF4= +github.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo= +github.com/AdminBenni/iota-mixing v1.0.0/go.mod h1:i4+tpAaB+qMVIV9OK3m4/DAynOd5bQFaOu+2AhtBCNY= +github.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY= +github.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc= +github.com/Antonboom/errname v1.1.1 h1:bllB7mlIbTVzO9jmSWVWLjxTEbGBVQ1Ff/ClQgtPw9Q= +github.com/Antonboom/errname v1.1.1/go.mod h1:gjhe24xoxXp0ScLtHzjiXp0Exi1RFLKJb0bVBtWKCWQ= +github.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksufQ= +github.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II= +github.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ= +github.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/ClickHouse/clickhouse-go-linter v1.2.0 h1:zbm174up3hTKjp0wKZVnTzRiG7tSF5XZF0FJG/MuCBI= +github.com/ClickHouse/clickhouse-go-linter v1.2.0/go.mod h1:pLorS7ffPTfuUV9M0SJgfHA/h/WQPQUk2FWG9x74cQ4= +github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g= +github.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k= github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/MirrexOne/unqueryvet v1.5.4 h1:38QOxShO7JmMWT+eCdDMbcUgGCOeJphVkzzRgyLJgsQ= +github.com/MirrexOne/unqueryvet v1.5.4/go.mod h1:fs9Zq6eh1LRIhsDIsxf9PONVUjYdFHdtkHIgZdJnyPU= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= +github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/alecthomas/chroma/v2 v2.24.1 h1:m5ffpfZbIb++k8AqFEKy9uVgY12xIQtBsQlc6DfZJQM= +github.com/alecthomas/chroma/v2 v2.24.1/go.mod h1:l+ohZ9xRXIbGe7cIW+YZgOGbvuVLjMps/FYN/CwuabI= +github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= +github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= +github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ= +github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q= +github.com/alexkohler/prealloc v1.1.0 h1:cKGRBqlXw5iyQGLYhrXrDlcHxugXpTq4tQ5c91wkf8M= +github.com/alexkohler/prealloc v1.1.0/go.mod h1:fT39Jge3bQrfA7nPMDngUfvUbQGQeJyGQnR+913SCig= +github.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc= +github.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus= +github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= +github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= +github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w= +github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/ashanbrown/forbidigo/v2 v2.3.1 h1:KAZijvQ7zeIBKbhikT4jCm0TLYXC4u78bTiLh/8JROI= +github.com/ashanbrown/forbidigo/v2 v2.3.1/go.mod h1:2QDkLTzU6TV937eFROamXrW92M3paehdae4HCDCOZCM= +github.com/ashanbrown/makezero/v2 v2.2.1 h1:A7uU8dgB1PA9aelTxHMfHIQ8Qev8AB3JLxJUBUsejqM= +github.com/ashanbrown/makezero/v2 v2.2.1/go.mod h1:aEGT/9q3S8DHeE57C88z2a6xydvgx8J5hgXIGWgo0MY= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= +github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= +github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= +github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= +github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ= +github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg= +github.com/bombsimon/wsl/v5 v5.8.0 h1:JTkyfs4yl8SPejrCF2GdABXE+mO1WvM7iUYzRWlsxDs= +github.com/bombsimon/wsl/v5 v5.8.0/go.mod h1:AbOLsulgkqP4ZnitHf9gwPtCOGlrzkk0jb0uNxRSY0o= +github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE= +github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE= +github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg= +github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s= +github.com/butuzov/ireturn v0.4.1 h1:vWb3NO4t77iku/sjCQ/2pHTQeOmxEhjIriJqRLg1Y+I= +github.com/butuzov/ireturn v0.4.1/go.mod h1:q+DXKzTDV5guNuXLnIab9fKXizTn2miZHLhxH7V/GB4= +github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= +github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= github.com/caarlos0/env/v11 v11.4.1 h1:fYwH0sWEsBSMPG7t4e/PEfTFzrWrpjyygXyUnWiSwEw= github.com/caarlos0/env/v11 v11.4.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= +github.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl/WpKm0PQ= +github.com/catenacyber/perfsprint v0.10.1/go.mod h1:DJTGsi/Zufpuus6XPGJyKOTMELe347o6akPvWG9Zcsc= +github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc= +github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1DidBcABctk= +github.com/charithe/durationcheck v0.0.11/go.mod h1:x5iZaixRNl8ctbM+3B2RrPG5t856TxRyVQEnbIEM2X4= +github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= +github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= +github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 h1:OqDqxQZliC7C8adA7KjelW3OjtAxREfeHkNcd66wpeI= +github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318/go.mod h1:Y6kE2GzHfkyQQVCSL9r2hwokSrIlHGzZG+71+wDYSZI= +github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI= +github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ= +github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= +github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= +github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= +github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= +github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= +github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs= +github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= +github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= +github.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ= +github.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ= +github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY= +github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -30,6 +135,10 @@ github.com/deckhouse/sds-node-configurator/api v0.0.0-20260114125558-7fd7152586f github.com/deckhouse/sds-node-configurator/api v0.0.0-20260114125558-7fd7152586ff/go.mod h1:X5ftUa4MrSXMKiwQYa4lwFuGtrs+HoCNa8Zl6TPrGo8= github.com/deckhouse/virtualization/api v1.8.0 h1:wR4Ivcg56OWJRGWrZjEL+0mQrHFEG0gKn0xrq1yzjy0= github.com/deckhouse/virtualization/api v1.8.0/go.mod h1:jqKdfrs7bhU5kbn6JTJUix8N180UkugJIa3TnOTqdmA= +github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= +github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= +github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8= +github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -39,17 +148,33 @@ github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= +github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E= +github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= +github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0= +github.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI= +github.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog= +github.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -85,6 +210,33 @@ github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= +github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= +github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= +github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= +github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= +github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= +github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= +github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= +github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= +github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= +github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= +github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= +github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= +github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= +github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= +github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godoc-lint/godoc-lint v0.11.2 h1:Bp0FkJWoSdNsBikdNgIcgtaoo+xz6I/Y9s5WSBQUeeM= +github.com/godoc-lint/godoc-lint v0.11.2/go.mod h1:iVpGdL1JCikNH2gGeAn3Hh+AgN5Gx/I/cxV+91L41jo= +github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= +github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -100,13 +252,40 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 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/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0= +github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ= +github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 h1:CbTB8KpqnViI6lIXxp03Oclc4VFHi3K4BWC1TacsZ+A= +github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= +github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U= +github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= +github.com/golangci/golangci-lint/v2 v2.12.0 h1:fd61aD+XaAl+APBGWcbxzi+K0tb33JogvMG3ypJLtH8= +github.com/golangci/golangci-lint/v2 v2.12.0/go.mod h1:e/wBh0xvA13ag/OWByUmvjc9oYPtcKGpXycldJbc7t0= +github.com/golangci/golines v0.15.0 h1:Qnph25g8Y1c5fdo1X7GaRDGgnMHgnxh4Gk4VfPTtRx0= +github.com/golangci/golines v0.15.0/go.mod h1:AZjXd23tbHMpowhtnGlj9KCNsysj72aeZVVHnVcZx10= +github.com/golangci/misspell v0.8.0 h1:qvxQhiE2/5z+BVRo1kwYA8yGz+lOlu5Jfvtx2b04Jbg= +github.com/golangci/misspell v0.8.0/go.mod h1:WZyyI2P3hxPY2UVHs3cS8YcllAeyfquQcKfdeE9AFVg= +github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3HIYbzSuP53UAYgOpg= +github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw= +github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= +github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= +github.com/golangci/rowserrcheck v0.0.0-20260419091836-c5f79b8a11ba h1:lqtcnSMDuuJdu/LrKWi5RJzpSNLOJXYe/nzQutTI5kg= +github.com/golangci/rowserrcheck v0.0.0-20260419091836-c5f79b8a11ba/go.mod h1:sCBNcpRmhJCtbFGz49+IM3ETTFf7QdJ30AeYCd43NKk= +github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM= +github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s= +github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM= +github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -114,24 +293,62 @@ github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs= +github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= +github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= +github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= +github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= +github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= +github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= +github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= +github.com/gostaticanalysis/nilerr v0.1.2 h1:S6nk8a9N8g062nsx63kUkF6AzbHGw7zzyHMcpu52xQU= +github.com/gostaticanalysis/nilerr v0.1.2/go.mod h1:A19UHhoY3y8ahoL7YKz6sdjDtduwTSI4CsymaC2htPA= +github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= +github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= +github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= +github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jgautheron/goconst v1.10.0 h1:Ptt+OoE4NaEWKhLrWrrN3IpZdGLiqaf7WLnEX/iv4Jw= +github.com/jgautheron/goconst v1.10.0/go.mod h1:0p+wv1lFOiUr0IlNNT1nrm6+8DB8u2sU6KHGzFRXHDc= +github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= +github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 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/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= +github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= +github.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhExWKxD/fP6q0= +github.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/errcheck v1.10.0 h1:Lvs/YAHP24YKg08LA8oDw2z9fJVme090RAXd90S+rrw= +github.com/kisielk/errcheck v1.10.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= +github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -142,14 +359,63 @@ 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/kulti/thelper v0.7.1 h1:fI8QITAoFVLx+y+vSyuLBP+rcVIB8jKooNSCT2EiI98= +github.com/kulti/thelper v0.7.1/go.mod h1:NsMjfQEy6sd+9Kfw8kCP61W1I0nerGSYSFnGaxQkcbs= +github.com/kunwardeep/paralleltest v1.0.15 h1:ZMk4Qt306tHIgKISHWFJAO1IDQJLc6uDyJMLyncOb6w= +github.com/kunwardeep/paralleltest v1.0.15/go.mod h1:di4moFqtfz3ToSKxhNjhOZL+696QtJGCFe132CbBLGk= +github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= +github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= +github.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ= +github.com/ldez/exptostd v0.4.5/go.mod h1:QRjHRMXJrCTIm9WxVNH6VW7oN7KrGSht69bIRwvdFsM= +github.com/ldez/gomoddirectives v0.8.0 h1:JqIuTtgvFC2RdH1s357vrE23WJF2cpDCPFgA/TWDGpk= +github.com/ldez/gomoddirectives v0.8.0/go.mod h1:jutzamvZR4XYJLr0d5Honycp4Gy6GEg2mS9+2YX3F1Q= +github.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o= +github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas= +github.com/ldez/structtags v0.6.1 h1:bUooFLbXx41tW8SvkfwfFkkjPYvFFs59AAMgVg6DUBk= +github.com/ldez/structtags v0.6.1/go.mod h1:YDxVSgDy/MON6ariaxLF2X09bh19qL7MtGBN5MrvbdY= +github.com/ldez/tagliatelle v0.7.2 h1:KuOlL70/fu9paxuxbeqlicJnCspCRjH0x8FW+NfgYUk= +github.com/ldez/tagliatelle v0.7.2/go.mod h1:PtGgm163ZplJfZMZ2sf5nhUT170rSuPgBimoyYtdaSI= +github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc= +github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= +github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= +github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= +github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE= +github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww= +github.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM= +github.com/manuelarte/funcorder v0.6.0 h1:0hBngc4fa1IgNiI65A7sFGkMvoMCc878RjqB5V7rWP0= +github.com/manuelarte/funcorder v0.6.0/go.mod h1:id3NDhXdQBmeqXH7eVC6Z89xS6JxvZ8kF9xUxpArU/g= +github.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6Fcevpzy4q8= +github.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ= +github.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs= +github.com/maratori/testpackage v1.1.2/go.mod h1:8F24GdVDFW5Ew43Et02jamrVMNXLUNaOynhDssITGfc= +github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= +github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= +github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mgechev/revive v1.15.0 h1:vJ0HzSBzfNyPbHKolgiFjHxLek9KUijhqh42yGoqZ8Q= +github.com/mgechev/revive v1.15.0/go.mod h1:LlAKO3QQe9OJ0pVZzI2GPa8CbXGZ/9lNpCGvK4T/a8A= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +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/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= @@ -160,12 +426,24 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= +github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= +github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= +github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= +github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= +github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= +github.com/nunnatsa/ginkgolinter v0.23.0 h1:x3o4DGYOWbBMP/VdNQKgSj+25aJKx2Pe6lHr8gBcgf8= +github.com/nunnatsa/ginkgolinter v0.23.0/go.mod h1:9qN1+0akwXEccwV1CAcCDfcoBlWXHB+ML9884pL4SZ4= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -176,6 +454,7 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0= github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= +github.com/onsi/ginkgo/v2 v2.28.2/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= @@ -183,8 +462,18 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= github.com/openshift/custom-resource-status v1.1.2 h1:C3DL44LEbvlbItfd8mT5jWrqPfHnSOQoQf/sypqA6A4= github.com/openshift/custom-resource-status v1.1.2/go.mod h1:DB/Mf2oTeiAmVVX1gN+NEqweonAPY0TKUwADizj8+ZA= +github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM= +github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU= @@ -201,29 +490,132 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/quasilyte/go-ruleguard v0.4.5 h1:AGY0tiOT5hJX9BTdx/xBdoCubQUAE2grkqY2lSwvZcA= +github.com/quasilyte/go-ruleguard v0.4.5/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE= +github.com/quasilyte/go-ruleguard/dsl v0.3.23 h1:lxjt5B6ZCiBeeNO8/oQsegE6fLeCzuMRoVWSkXC4uvY= +github.com/quasilyte/go-ruleguard/dsl v0.3.23/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= +github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= +github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= +github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= +github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= +github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= +github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= +github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g= +github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I= +github.com/ryancurrah/gomodguard/v2 v2.1.0 h1:iIIARHe7Fsp10LY5utfMmYA++hkVuKsMFGDzxnVcijU= +github.com/ryancurrah/gomodguard/v2 v2.1.0/go.mod h1:ryDqr6as4otkNbUp/U0m7zAsxGpwcJ9NtL6mvy9Zzdw= +github.com/ryanrolds/sqlclosecheck v0.6.0 h1:pEyL9okISdg1F1SEpJNlrEotkTGerv5BMk7U4AG0eVg= +github.com/ryanrolds/sqlclosecheck v0.6.0/go.mod h1:xyX16hsDaCMXHrMJ3JMzGf5OpDfHTOTTQrT7HOFUmeU= +github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= +github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= +github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= +github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ= +github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8= +github.com/securego/gosec/v2 v2.26.1 h1:gdkttGhQFVehqRJ8grKH4DrpqM/QlPKNHBnl8QgcEC4= +github.com/securego/gosec/v2 v2.26.1/go.mod h1:57UW4p0uoP3kxoTkhoo3axLdVAi+OWrLg/Ax/kdqtPE= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= +github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= +github.com/sonatard/noctx v0.5.1 h1:wklWg9c9ZYugOAk7qG4yP4PBrlQsmSLPTvW1K4PRQMs= +github.com/sonatard/noctx v0.5.1/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas= +github.com/sourcegraph/go-diff v0.8.0 h1:ipIyu4cTsLbIrln4l0qtHA3r0a7gyK4ntKjtQytHhvY= +github.com/sourcegraph/go-diff v0.8.0/go.mod h1:hWlcO7Al+UZStZAP8rBumHpCK5ZHQ5BXsMls8p4+F5E= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= +github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= +github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= +github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g= +github.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= +github.com/tetafro/godot v1.5.6 h1:IEkrFCwXaYHlOn4mGzGS3F3dkP6m9t0jpwqBFPIkKiA= +github.com/tetafro/godot v1.5.6/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU= +github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 h1:SiHe5XLTn9sFWJ5pBwJ5FN/4j34q9ZlOAD//kMoMYp0= +github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4/go.mod h1:sDHLK7rb/59v/ZxZ7KtymgcoxuUMxjXq8gtu9VMOK8M= +github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= +github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= +github.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVFL27Of9is= +github.com/tomarrell/wrapcheck/v2 v2.12.0/go.mod h1:AQhQuZd0p7b6rfW+vUwHm5OMCGgp63moQ9Qr/0BpIWo= +github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= +github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= +github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI= +github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA= +github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g= +github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= +github.com/uudashr/gocognit v1.2.1 h1:CSJynt5txTnORn/DkhiB4mZjwPuifyASC8/6Q0I/QS4= +github.com/uudashr/gocognit v1.2.1/go.mod h1:acaubQc6xYlXFEMb9nWX2dYBzJ/bIjEkc1zzvyIZg5Q= +github.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU= +github.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM= +github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= +github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= +github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= +github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= +github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= +github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= +gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= +go-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo= +go-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE= +go-simpler.org/sloglint v0.12.0 h1:UzWDlLWNE5FLqsvyq3tWYHuQMbqrervOhT8qPl4Mmw4= +go-simpler.org/sloglint v0.12.0/go.mod h1:jBjjC2bm8rYrs88oTRlFX497kWjJsyZWYoNaXkGRI6I= +go.augendre.info/arangolint v0.4.0 h1:xSCZjRoS93nXazBSg5d0OGCi9APPLNMmmLrC995tR50= +go.augendre.info/arangolint v0.4.0/go.mod h1:l+f/b4plABuFISuKnTGD4RioXiCCgghv2xqst/xOvAA= +go.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE= +go.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw= 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.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= @@ -235,16 +627,28 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 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/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc= +golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 h1:qWFG1Dj7TBjOjOvhEOkmyGPVoquqUKnIU0lEVLp8xyk= +golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -255,7 +659,9 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/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-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= @@ -263,6 +669,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -272,8 +680,11 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20200625203802-6e8e738ad208/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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -289,6 +700,7 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w 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-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -297,12 +709,18 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -311,6 +729,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= @@ -321,13 +740,20 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -357,6 +783,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -368,12 +796,15 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWM gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/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= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -382,6 +813,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU= +honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= @@ -415,6 +848,10 @@ kubevirt.io/containerized-data-importer-api v1.60.3-0.20241105012228-50fbed985de kubevirt.io/containerized-data-importer-api v1.60.3-0.20241105012228-50fbed985de9/go.mod h1:SDJjLGhbPyayDqAqawcGmVNapBp0KodOQvhKPLVGCQU= kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 h1:QMrd0nKP0BGbnxTqakhDZAUhGKxPiPiN5gSDqKUmGGc= kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90/go.mod h1:018lASpFYBsYN6XwmA2TIrPCx6e0gviTd/ZNtSitKgc= +mvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4= +mvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s= +mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 h1:ssMzja7PDPJV8FStj7hq9IKiuiKhgz9ErWw+m68e7DI= +mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15/go.mod h1:4M5MMXl2kW6fivUT6yRGpLLPNfuGtU2Z0cPvFquGDYU= sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= diff --git a/internal/provisioning/dvp/kubeconfig.go b/internal/provisioning/dvp/kubeconfig.go index a7c9a99..70772e4 100644 --- a/internal/provisioning/dvp/kubeconfig.go +++ b/internal/provisioning/dvp/kubeconfig.go @@ -86,8 +86,8 @@ func buildKubeconfig(raw []byte, server, path string) (*rest.Config, error) { cluster.Server = server } - if err := clientcmd.WriteToFile(*apiCfg, path); err != nil { - return nil, fmt.Errorf("write kubeconfig %q: %w", path, err) + if writeErr := clientcmd.WriteToFile(*apiCfg, path); writeErr != nil { + return nil, fmt.Errorf("write kubeconfig %q: %w", path, writeErr) } restCfg, err := clientcmd.NewDefaultClientConfig(*apiCfg, &clientcmd.ConfigOverrides{}).ClientConfig() diff --git a/internal/provisioning/dvp/provider.go b/internal/provisioning/dvp/provider.go index 0d3e247..6eade00 100644 --- a/internal/provisioning/dvp/provider.go +++ b/internal/provisioning/dvp/provider.go @@ -22,6 +22,7 @@ import ( "log/slog" "github.com/caarlos0/env/v11" + "github.com/deckhouse/storage-e2e/internal/config" "github.com/deckhouse/storage-e2e/pkg/clusterprovider" "github.com/deckhouse/storage-e2e/pkg/kubernetes" @@ -33,7 +34,7 @@ type dvpProvider struct { logger *slog.Logger } -func NewDVPProvider(logger *slog.Logger, config *clusterprovider.ClusterConfig) (clusterprovider.Provider, error) { +func NewDVPProvider(logger *slog.Logger, cfg *clusterprovider.ClusterConfig) (clusterprovider.Provider, error) { dvpConf := &Config{} if err := env.Parse(dvpConf); err != nil { return nil, err @@ -44,7 +45,7 @@ func NewDVPProvider(logger *slog.Logger, config *clusterprovider.ClusterConfig) } return &dvpProvider{ - cfg: config, + cfg: cfg, dvpConf: dvpConf, logger: logger, }, nil diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 742c6dd..327f321 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -315,10 +315,10 @@ func CreateTestCluster( logger.StepComplete(2, "Connected to base cluster successfully") logger.Step(3, "Verifying virtualization module is Ready") - if err := kubernetes.WaitForModuleReady(ctx, baseClusterResources.Kubeconfig, "virtualization", config.ModuleCheckTimeout); err != nil { + if waitErr := kubernetes.WaitForModuleReady(ctx, baseClusterResources.Kubeconfig, "virtualization", config.ModuleCheckTimeout); waitErr != nil { baseClusterResources.SSHClient.Close() baseClusterResources.TunnelInfo.StopFunc() - return nil, fmt.Errorf("virtualization module is not Ready: %w", err) + return nil, fmt.Errorf("virtualization module is not Ready: %w", waitErr) } logger.StepComplete(3, "Virtualization module is Ready") diff --git a/pkg/kubernetes/modules.go b/pkg/kubernetes/modules.go index e5de6c3..6e3dfb1 100644 --- a/pkg/kubernetes/modules.go +++ b/pkg/kubernetes/modules.go @@ -23,9 +23,9 @@ import ( "sync" "time" + deckhousev1alpha1 "github.com/deckhouse/deckhouse/deckhouse-controller/pkg/apis/deckhouse.io/v1alpha1" "k8s.io/client-go/rest" - deckhousev1alpha1 "github.com/deckhouse/deckhouse/deckhouse-controller/pkg/apis/deckhouse.io/v1alpha1" "github.com/deckhouse/storage-e2e/internal/config" "github.com/deckhouse/storage-e2e/internal/infrastructure/ssh" "github.com/deckhouse/storage-e2e/internal/kubernetes/deckhouse" @@ -582,7 +582,7 @@ const moduleReadyPollInterval = 2 * time.Second // flaky during cluster creation): // - The whole wait is bounded by a derived context deadline, so persistent // GetModule failures (dropped SSH tunnel, API hiccup) also time out instead -// of hanging until the parent context is cancelled. +// of hanging until the parent context is canceled. // - Transient GetModule errors and intermediate phases (Downloading, // Installing, Reconciling, and even Error — modules can recover) are // tolerated; only the timeout is terminal. From 37cdb02a8560573d166f5f8776c9ac0796b661d3 Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Tue, 16 Jun 2026 16:21:08 +0300 Subject: [PATCH 15/23] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Daniil Studenikin --- internal/provisioning/dvp/provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provisioning/dvp/provider.go b/internal/provisioning/dvp/provider.go index 6eade00..f4b20a8 100644 --- a/internal/provisioning/dvp/provider.go +++ b/internal/provisioning/dvp/provider.go @@ -115,5 +115,5 @@ func (p *dvpProvider) Bootstrap(ctx context.Context) error { func (p *dvpProvider) Remove(ctx context.Context) error { // TODO: implement — idempotent teardown by deterministic cluster name. - panic("not implemented") + return fmt.Errorf("dvp provider Remove is not implemented") } From e93b543fbc9b4dd96d902364fe53e73c0b9e4506 Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Tue, 16 Jun 2026 16:21:46 +0300 Subject: [PATCH 16/23] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Daniil Studenikin --- pkg/clusterprovider/config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/clusterprovider/config_test.go b/pkg/clusterprovider/config_test.go index 7100969..059e1f8 100644 --- a/pkg/clusterprovider/config_test.go +++ b/pkg/clusterprovider/config_test.go @@ -53,7 +53,7 @@ func TestNew_ParsesProvider(t *testing.T) { t.Fatal("expected non-nil config") } if cfg.ClusterProvider != ModeDVP { - t.Errorf("ClusterProvider = %q, want %q", cfg.ClusterProvider, "static") + t.Errorf("ClusterProvider = %q, want %q", cfg.ClusterProvider, ModeDVP) } } From f5a394692e59c8369ed4a7ae7841567ffc723e3f Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Tue, 16 Jun 2026 16:22:09 +0300 Subject: [PATCH 17/23] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Daniil Studenikin --- internal/provisioning/dvp/config.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/provisioning/dvp/config.go b/internal/provisioning/dvp/config.go index 9ef3077..e34fcc9 100644 --- a/internal/provisioning/dvp/config.go +++ b/internal/provisioning/dvp/config.go @@ -37,8 +37,10 @@ type Config struct { } func (c *Config) SetPassphrase() error { - err := os.Setenv("SSH_PASSPHRASE", c.SSHPassphrase) - if err != nil { + if c.SSHPassphrase == "" { + return nil + } + if err := os.Setenv("SSH_PASSPHRASE", c.SSHPassphrase); err != nil { return fmt.Errorf("failed to set SSH_PASSPHRASE: %w", err) } return nil From 4af6202bb3d0f09326a3a0cab212f93ed07860cb Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Tue, 16 Jun 2026 16:23:01 +0300 Subject: [PATCH 18/23] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Daniil Studenikin --- cmd/remove-cluster/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/remove-cluster/main.go b/cmd/remove-cluster/main.go index d002e7c..2ccbb51 100644 --- a/cmd/remove-cluster/main.go +++ b/cmd/remove-cluster/main.go @@ -29,7 +29,7 @@ func main() { return } - teardownErr := clusterProvider.Remove(context.TODO()) + teardownErr := clusterProvider.Remove(context.Background()) if teardownErr != nil { slogger.Error("failed to tear down cluster", teardownErr) return From aa621b4a8f53d2b7665772f849bd96d0b10a893e Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Tue, 16 Jun 2026 16:23:53 +0300 Subject: [PATCH 19/23] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Daniil Studenikin --- internal/provisioning/dvp/kubeconfig.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provisioning/dvp/kubeconfig.go b/internal/provisioning/dvp/kubeconfig.go index 70772e4..fd94755 100644 --- a/internal/provisioning/dvp/kubeconfig.go +++ b/internal/provisioning/dvp/kubeconfig.go @@ -100,7 +100,7 @@ func buildKubeconfig(raw []byte, server, path string) (*rest.Config, error) { } func kubeconfigFilePath(dir, host string) (string, error) { - if err := os.MkdirAll(dir, 0o755); err != nil { + if err := os.MkdirAll(dir, 0o700); err != nil { return "", fmt.Errorf("create kubeconfig dir %q: %w", dir, err) } return filepath.Join(dir, fmt.Sprintf("kubeconfig-%s.yml", host)), nil From face18f67daaf9c39052d4dfc5936c2c47107588 Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Tue, 16 Jun 2026 16:26:32 +0300 Subject: [PATCH 20/23] feat: improve error logging by adding structured error context and enhance timeout error messages Signed-off-by: Daniil Studenikin --- cmd/bootstrap-cluster/main.go | 6 +++--- pkg/kubernetes/modules.go | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/cmd/bootstrap-cluster/main.go b/cmd/bootstrap-cluster/main.go index 5bfff22..8fe23f5 100644 --- a/cmd/bootstrap-cluster/main.go +++ b/cmd/bootstrap-cluster/main.go @@ -16,7 +16,7 @@ func main() { cfg, err := clusterprovider.NewClusterConfig() if err != nil { - slogger.Error("failed to initialize config - ", err) + slogger.Error("failed to initialize config", "err", err) return } @@ -27,7 +27,7 @@ func main() { clusterProvider, err := newProvider(slogger, cfg) if err != nil { - slogger.Error("failed to build provider", err) + slogger.Error("failed to build provider", "err", err) return } @@ -36,7 +36,7 @@ func main() { bootstrapErr := clusterProvider.Bootstrap(bootstrapCtx) if bootstrapErr != nil { - slogger.Error("failed to bootstrap cluster", bootstrapErr) + slogger.Error("failed to bootstrap cluster", "err", bootstrapErr) return } } diff --git a/pkg/kubernetes/modules.go b/pkg/kubernetes/modules.go index 6e3dfb1..896ab8f 100644 --- a/pkg/kubernetes/modules.go +++ b/pkg/kubernetes/modules.go @@ -18,6 +18,7 @@ package kubernetes import ( "context" + "errors" "fmt" "strings" "sync" @@ -622,6 +623,13 @@ func WaitForModuleReady(ctx context.Context, kubeconfig *rest.Config, moduleName for { select { case <-ctx.Done(): + // Distinguish our derived deadline (a genuine module timeout) from a + // canceled parent context (e.g. the test was aborted), so logs aren't + // misleading about why the wait ended. + if errors.Is(ctx.Err(), context.Canceled) { + return fmt.Errorf("canceled while waiting for module %s to become ready (last phase %q%s): %w", + moduleName, lastPhase, moduleConditionSuffix(lastCondition), ctx.Err()) + } return fmt.Errorf("timeout waiting for module %s to become ready after %v (last phase %q%s): %w", moduleName, timeout, lastPhase, moduleConditionSuffix(lastCondition), ctx.Err()) case <-ticker.C: From 3104bd3432a468b4041d86a41b3b4888d44ddaf9 Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Tue, 16 Jun 2026 16:42:10 +0300 Subject: [PATCH 21/23] Enhance documentation and comments for cluster provider configuration and related types Signed-off-by: Daniil Studenikin --- .github/workflows/go-checks.yml | 2 ++ .golangci.yml | 10 +++++++++- pkg/clusterprovider/config.go | 4 ++++ pkg/clusterprovider/mode.go | 6 ++++++ pkg/clusterprovider/provider.go | 5 +++++ pkg/clusterprovider/registry/registry.go | 9 +++++++++ 6 files changed, 35 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go-checks.yml b/.github/workflows/go-checks.yml index ec0a249..edcda66 100644 --- a/.github/workflows/go-checks.yml +++ b/.github/workflows/go-checks.yml @@ -56,6 +56,8 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + # Required by actions/upload-code-coverage (GitHub Code Quality coverage, + # public preview). Without it the upload step fails with insufficient scope. code-quality: write steps: - name: Checkout repository diff --git a/.golangci.yml b/.golangci.yml index fc2abc9..a85e980 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -20,7 +20,7 @@ linters: - nilerr # return nil after a non-nil error check, and vice versa # Public API / style hygiene. - - revive # broad style linter; flags missing docs on exported symbols + - revive # broad style linter; flags missing docs on exported pkg/ symbols - unparam # unused function parameters/results (cleaner public API) - unconvert # remove unnecessary type conversions - predeclared # don't shadow predeclared identifiers (e.g. error, len) @@ -50,6 +50,8 @@ linters: - fieldalignment # too noisy; not worth the churn for a test library revive: rules: + # Doc comments are enforced on exported symbols, but only for the public + # pkg/ API (see exclusions below). internal/ favors self-documenting code. - name: exported arguments: - disableStutteringCheck @@ -104,6 +106,12 @@ linters: - revive text: "should not use dot imports" source: 'ginkgo|gomega' + # Doc comments are only required for the public pkg/ API; relax the + # exported/package-comments revive rules everywhere under internal/. + - path: internal/ + linters: + - revive + text: "should have comment or be unexported|comment on exported .* should be of the form|should have a package comment" formatters: enable: diff --git a/pkg/clusterprovider/config.go b/pkg/clusterprovider/config.go index 41bdfe1..543b859 100644 --- a/pkg/clusterprovider/config.go +++ b/pkg/clusterprovider/config.go @@ -20,11 +20,15 @@ import ( "github.com/caarlos0/env/v11" ) +// ClusterConfig holds the provider-agnostic settings needed to select and +// drive a cluster provider, populated from environment variables. type ClusterConfig struct { ClusterProvider ProviderMode `env:"E2E_TEST_CLUSTER_PROVIDER,required"` ClusterBootstrapConfigPath string `env:"E2E_CLUSTER_CONFIG_YAML_PATH,required"` } +// NewClusterConfig reads the cluster provider configuration from the +// environment, returning an error if required variables are missing or invalid. func NewClusterConfig() (*ClusterConfig, error) { cfg := &ClusterConfig{} parseErr := env.Parse(cfg) diff --git a/pkg/clusterprovider/mode.go b/pkg/clusterprovider/mode.go index cae5203..a1936bd 100644 --- a/pkg/clusterprovider/mode.go +++ b/pkg/clusterprovider/mode.go @@ -18,13 +18,18 @@ package clusterprovider import "fmt" +// ProviderMode identifies which cluster provider implementation to use. type ProviderMode string +// Supported provider modes. const ( ModeDVP = "dvp" ModeCommander = "commander" ) +// UnmarshalText parses a ProviderMode from its textual form, rejecting any +// value outside the supported set. It satisfies encoding.TextUnmarshaler so the +// mode can be populated directly from environment variables. func (m *ProviderMode) UnmarshalText(text []byte) error { v := ProviderMode(text) switch v { @@ -36,6 +41,7 @@ func (m *ProviderMode) UnmarshalText(text []byte) error { } } +// String returns the provider mode as a plain string. func (m ProviderMode) String() string { return string(m) } diff --git a/pkg/clusterprovider/provider.go b/pkg/clusterprovider/provider.go index 65266e9..0d2718c 100644 --- a/pkg/clusterprovider/provider.go +++ b/pkg/clusterprovider/provider.go @@ -14,12 +14,17 @@ * limitations under the License. */ +// Package clusterprovider defines the provider abstraction used to bootstrap +// and tear down test clusters, along with the provider mode and env-based +// configuration shared by concrete provider implementations. package clusterprovider import ( "context" ) +// Provider provisions and removes a test cluster for a specific backend +// (for example DVP). Implementations are expected to be idempotent. type Provider interface { Name() string Bootstrap(ctx context.Context) error diff --git a/pkg/clusterprovider/registry/registry.go b/pkg/clusterprovider/registry/registry.go index f9d3b13..f897a89 100644 --- a/pkg/clusterprovider/registry/registry.go +++ b/pkg/clusterprovider/registry/registry.go @@ -14,6 +14,8 @@ * limitations under the License. */ +// Package registry maps provider modes to their constructors and exposes a +// default registry pre-populated with the built-in providers. package registry import ( @@ -25,27 +27,34 @@ import ( "github.com/deckhouse/storage-e2e/pkg/clusterprovider" ) +// DefaultRegistry is the process-wide registry seeded with the built-in providers. var DefaultRegistry = NewRegistry() +// Constructor builds a Provider for a given logger and cluster configuration. type Constructor func(logger *slog.Logger, config *clusterprovider.ClusterConfig) (clusterprovider.Provider, error) +// Registry is a concurrency-safe mapping from provider mode to Constructor. type Registry struct { mu sync.RWMutex constructors map[string]Constructor } +// NewRegistry returns a Registry pre-populated with the built-in providers. func NewRegistry() *Registry { return &Registry{constructors: map[string]Constructor{ clusterprovider.ModeDVP: dvp.NewDVPProvider, }} } +// Register adds or replaces the constructor registered under name. func (r *Registry) Register(name string, c Constructor) { r.mu.Lock() defer r.mu.Unlock() r.constructors[name] = c } +// Get returns the constructor registered for the given provider mode, or an +// error if no provider is registered under that mode. func (r *Registry) Get(name clusterprovider.ProviderMode) (Constructor, error) { r.mu.RLock() defer r.mu.RUnlock() From 819dc52ca541db9fe9f73f71cfdb3cc60b16fbb4 Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Tue, 16 Jun 2026 16:47:29 +0300 Subject: [PATCH 22/23] Update go.mod and go.sum to remove unused dependencies and add new ones Signed-off-by: Daniil Studenikin --- go.mod | 7 +---- go.sum | 81 +++++++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 8665fdd..dfeba55 100644 --- a/go.mod +++ b/go.mod @@ -8,12 +8,10 @@ require ( github.com/deckhouse/sds-node-configurator/api v0.0.0-20260114125558-7fd7152586ff github.com/deckhouse/virtualization/api v1.8.0 github.com/go-logr/logr v1.4.3 - github.com/go-playground/validator/v10 v10.30.3 github.com/onsi/ginkgo/v2 v2.28.2 github.com/onsi/gomega v1.39.1 github.com/pkg/sftp v1.13.10 golang.org/x/crypto v0.52.0 - golang.org/x/sync v0.20.0 golang.org/x/term v0.43.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.34.2 @@ -89,15 +87,12 @@ require ( github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/ghostiam/protogetter v0.3.20 // indirect github.com/go-critic/go-critic v0.14.3 // indirect github.com/go-openapi/jsonpointer v0.22.1 // indirect github.com/go-openapi/jsonreference v0.21.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/swag/jsonname v0.25.1 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect @@ -158,7 +153,6 @@ require ( github.com/ldez/structtags v0.6.1 // indirect github.com/ldez/tagliatelle v0.7.2 // indirect github.com/ldez/usetesting v0.5.0 // indirect - github.com/leodido/go-urn v1.4.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect github.com/lucasb-eyer/go-colorful v1.4.0 // indirect github.com/macabu/inamedparam v0.2.0 // indirect @@ -254,6 +248,7 @@ require ( golang.org/x/mod v0.35.0 // indirect golang.org/x/net v0.54.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.45.0 // indirect golang.org/x/text v0.37.0 // indirect golang.org/x/time v0.12.0 // indirect diff --git a/go.sum b/go.sum index a0b6b09..2c9ebec 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,6 @@ github.com/ClickHouse/clickhouse-go-linter v1.2.0 h1:zbm174up3hTKjp0wKZVnTzRiG7t github.com/ClickHouse/clickhouse-go-linter v1.2.0/go.mod h1:pLorS7ffPTfuUV9M0SJgfHA/h/WQPQUk2FWG9x74cQ4= github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g= github.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k= -github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= -github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/MirrexOne/unqueryvet v1.5.4 h1:38QOxShO7JmMWT+eCdDMbcUgGCOeJphVkzzRgyLJgsQ= @@ -45,10 +43,14 @@ github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsu github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.24.1 h1:m5ffpfZbIb++k8AqFEKy9uVgY12xIQtBsQlc6DfZJQM= github.com/alecthomas/chroma/v2 v2.24.1/go.mod h1:l+ohZ9xRXIbGe7cIW+YZgOGbvuVLjMps/FYN/CwuabI= github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= +github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= +github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ= github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q= github.com/alexkohler/prealloc v1.1.0 h1:cKGRBqlXw5iyQGLYhrXrDlcHxugXpTq4tQ5c91wkf8M= @@ -125,6 +127,8 @@ github.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ= github.com/daixiang0/gci v0.13.7/go.mod h1:812WVN6JLFY9S6Tv76twqmNqevN0pa3SX3nih0brVzQ= github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY= github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= +github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo= +github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -159,6 +163,8 @@ github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4 github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E= github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo= +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.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= @@ -167,12 +173,16 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= -github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0= github.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= +github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog= github.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= @@ -199,14 +209,8 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8= -github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= +github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= @@ -222,6 +226,8 @@ github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsO github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= +github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= +github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= @@ -233,6 +239,8 @@ github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUW github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godoc-lint/godoc-lint v0.11.2 h1:Bp0FkJWoSdNsBikdNgIcgtaoo+xz6I/Y9s5WSBQUeeM= github.com/godoc-lint/godoc-lint v0.11.2/go.mod h1:iVpGdL1JCikNH2gGeAn3Hh+AgN5Gx/I/cxV+91L41jo= github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= @@ -291,8 +299,7 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -315,8 +322,12 @@ github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXS github.com/gostaticanalysis/nilerr v0.1.2 h1:S6nk8a9N8g062nsx63kUkF6AzbHGw7zzyHMcpu52xQU= github.com/gostaticanalysis/nilerr v0.1.2/go.mod h1:A19UHhoY3y8ahoL7YKz6sdjDtduwTSI4CsymaC2htPA= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= +github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8= +github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -336,6 +347,8 @@ github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgY github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -377,8 +390,6 @@ github.com/ldez/tagliatelle v0.7.2 h1:KuOlL70/fu9paxuxbeqlicJnCspCRjH0x8FW+NfgYU github.com/ldez/tagliatelle v0.7.2/go.mod h1:PtGgm163ZplJfZMZ2sf5nhUT170rSuPgBimoyYtdaSI= github.com/ldez/usetesting v0.5.0 h1:3/QtzZObBKLy1F4F8jLuKJiKBjjVFi1IavpoWbmqLwc= github.com/ldez/usetesting v0.5.0/go.mod h1:Spnb4Qppf8JTuRgblLrEWb7IE6rDmUpGvxY3iRrzvDQ= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= @@ -400,8 +411,11 @@ github.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6 github.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ= github.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs= github.com/maratori/testpackage v1.1.2/go.mod h1:8F24GdVDFW5Ew43Et02jamrVMNXLUNaOynhDssITGfc= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= @@ -409,6 +423,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/mgechev/revive v1.15.0 h1:vJ0HzSBzfNyPbHKolgiFjHxLek9KUijhqh42yGoqZ8Q= github.com/mgechev/revive v1.15.0/go.mod h1:LlAKO3QQe9OJ0pVZzI2GPa8CbXGZ/9lNpCGvK4T/a8A= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -452,20 +468,20 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0= -github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= +github.com/onsi/ginkgo/v2 v2.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps= github.com/onsi/ginkgo/v2 v2.28.2/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= -github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= github.com/openshift/custom-resource-status v1.1.2 h1:C3DL44LEbvlbItfd8mT5jWrqPfHnSOQoQf/sypqA6A4= github.com/openshift/custom-resource-status v1.1.2/go.mod h1:DB/Mf2oTeiAmVVX1gN+NEqweonAPY0TKUwADizj8+ZA= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= @@ -523,6 +539,8 @@ github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iM github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8= github.com/securego/gosec/v2 v2.26.1 h1:gdkttGhQFVehqRJ8grKH4DrpqM/QlPKNHBnl8QgcEC4= github.com/securego/gosec/v2 v2.26.1/go.mod h1:57UW4p0uoP3kxoTkhoo3axLdVAi+OWrLg/Ax/kdqtPE= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= @@ -541,8 +559,6 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= -github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -566,10 +582,20 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.5.6 h1:IEkrFCwXaYHlOn4mGzGS3F3dkP6m9t0jpwqBFPIkKiA= github.com/tetafro/godot v1.5.6/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 h1:SiHe5XLTn9sFWJ5pBwJ5FN/4j34q9ZlOAD//kMoMYp0= github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4/go.mod h1:sDHLK7rb/59v/ZxZ7KtymgcoxuUMxjXq8gtu9VMOK8M= github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= @@ -608,6 +634,8 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= +go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= +go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= go-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo= go-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE= go-simpler.org/sloglint v0.12.0 h1:UzWDlLWNE5FLqsvyq3tWYHuQMbqrervOhT8qPl4Mmw4= @@ -616,6 +644,8 @@ go.augendre.info/arangolint v0.4.0 h1:xSCZjRoS93nXazBSg5d0OGCi9APPLNMmmLrC995tR5 go.augendre.info/arangolint v0.4.0/go.mod h1:l+f/b4plABuFISuKnTGD4RioXiCCgghv2xqst/xOvAA= go.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE= go.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= @@ -632,6 +662,7 @@ golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc= +golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 h1:qWFG1Dj7TBjOjOvhEOkmyGPVoquqUKnIU0lEVLp8xyk= @@ -756,6 +787,10 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= +golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -781,8 +816,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj 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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 477702587649268a2f2a196970835c01f1afb93c Mon Sep 17 00:00:00 2001 From: Daniil Studenikin Date: Tue, 16 Jun 2026 16:53:41 +0300 Subject: [PATCH 23/23] Refactor error logging in main.go for improved clarity and update golangci.yml to enforce linter issue limits Signed-off-by: Daniil Studenikin --- .golangci.yml | 4 ++++ cmd/remove-cluster/main.go | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index a85e980..18f97a9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -113,6 +113,10 @@ linters: - revive text: "should have comment or be unexported|comment on exported .* should be of the form|should have a package comment" +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + formatters: enable: - gofmt diff --git a/cmd/remove-cluster/main.go b/cmd/remove-cluster/main.go index 2ccbb51..ac42650 100644 --- a/cmd/remove-cluster/main.go +++ b/cmd/remove-cluster/main.go @@ -13,25 +13,25 @@ func main() { slogger := logger.GetLogger() cfg, err := clusterprovider.NewClusterConfig() if err != nil { - slogger.Error("failed to initialize config - ", err) + slogger.Error("failed to initialize config", "error", err) return } newProvider, registryGetErr := registry.DefaultRegistry.Get(cfg.ClusterProvider) if registryGetErr != nil { - slogger.Error("failed to get provider", registryGetErr) + slogger.Error("failed to get provider", "error", registryGetErr) return } clusterProvider, err := newProvider(slogger, cfg) if err != nil { - slogger.Error("failed to build provider", err) + slogger.Error("failed to build provider", "error", err) return } teardownErr := clusterProvider.Remove(context.Background()) if teardownErr != nil { - slogger.Error("failed to tear down cluster", teardownErr) + slogger.Error("failed to tear down cluster", "error", teardownErr) return } }