diff --git a/.builds-windows.goreleaser.yml b/.builds-windows.goreleaser.yml index ab186df3b82..36c46e726ed 100644 --- a/.builds-windows.goreleaser.yml +++ b/.builds-windows.goreleaser.yml @@ -21,5 +21,6 @@ builds: - -X main.builtBy=goreleaser - -X github.com/kubeshop/testkube/pkg/telemetry.TestkubeMeasurementID={{.Env.ANALYTICS_TRACKING_ID}} - -X github.com/kubeshop/testkube/pkg/telemetry.TestkubeMeasurementSecret={{.Env.ANALYTICS_API_KEY}} + - -X github.com/kubeshop/testkube/pkg/diagnostics/validators/license.KeygenOfflinePublicKey={{.Env.KEYGEN_PUBLIC_KEY}} archives: - format: binary diff --git a/cmd/kubectl-testkube/commands/common/cloudcontext.go b/cmd/kubectl-testkube/commands/common/cloudcontext.go index 178a2813616..972e55a997f 100644 --- a/cmd/kubectl-testkube/commands/common/cloudcontext.go +++ b/cmd/kubectl-testkube/commands/common/cloudcontext.go @@ -26,7 +26,7 @@ func UiPrintContext(cfg config.Data) { } // add agent information only when need to change agent data, it's usually not needed in usual workflow - if ui.Verbose { + if ui.IsVerbose() { contextData["Agent Key"] = text.Obfuscate(cfg.CloudContext.AgentKey) contextData["Agent URI"] = cfg.CloudContext.AgentUri } diff --git a/cmd/kubectl-testkube/commands/common/helper.go b/cmd/kubectl-testkube/commands/common/helper.go index 778ba132acd..22b6f1ad0be 100644 --- a/cmd/kubectl-testkube/commands/common/helper.go +++ b/cmd/kubectl-testkube/commands/common/helper.go @@ -2,7 +2,9 @@ package common import ( "bufio" + "bytes" "context" + "encoding/base64" "encoding/json" "fmt" "io" @@ -628,6 +630,51 @@ func KubectlPrintEvents(namespace string) error { return process.ExecuteAndStreamOutput(kubectl, args...) } +func KubectlVersion() (client string, server string, err error) { + kubectl, err := exec.LookPath("kubectl") + if err != nil { + return "", "", err + } + + args := []string{ + "version", + "-o", "json", + } + + if ui.IsVerbose() { + ui.ShellCommand(kubectl, args...) + ui.NL() + } + + out, eerr := process.Execute(kubectl, args...) + if eerr != nil { + return "", "", eerr + } + + type Version struct { + ClientVersion struct { + Version string `json:"gitVersion,omitempty"` + } `json:"clientVersion,omitempty"` + ServerVersion struct { + Version string `json:"gitVersion,omitempty"` + } `json:"serverVersion,omitempty"` + } + + var v Version + + out, err = extractJSONObject(out) + if err != nil { + return "", "", err + } + + err = json.Unmarshal(out, &v) + if err != nil { + return "", "", err + } + + return strings.TrimLeft(v.ClientVersion.Version, "v"), strings.TrimLeft(v.ServerVersion.Version, "v"), nil +} + func KubectlDescribePods(namespace string) error { kubectl, err := lookupKubectlPath() if err != nil { @@ -640,8 +687,10 @@ func KubectlDescribePods(namespace string) error { "-n", namespace, } - ui.ShellCommand(kubectl, args...) - ui.NL() + if ui.IsVerbose() { + ui.ShellCommand(kubectl, args...) + ui.NL() + } return process.ExecuteAndStreamOutput(kubectl, args...) } @@ -659,8 +708,10 @@ func KubectlPrintPods(namespace string) error { "--show-labels", } - ui.ShellCommand(kubectl, args...) - ui.NL() + if ui.IsVerbose() { + ui.ShellCommand(kubectl, args...) + ui.NL() + } return process.ExecuteAndStreamOutput(kubectl, args...) } @@ -676,8 +727,10 @@ func KubectlGetStorageClass(namespace string) error { "storageclass", } - ui.ShellCommand(kubectl, args...) - ui.NL() + if ui.IsVerbose() { + ui.ShellCommand(kubectl, args...) + ui.NL() + } return process.ExecuteAndStreamOutput(kubectl, args...) } @@ -694,8 +747,10 @@ func KubectlGetServices(namespace string) error { "-n", namespace, } - ui.ShellCommand(kubectl, args...) - ui.NL() + if ui.IsVerbose() { + ui.ShellCommand(kubectl, args...) + ui.NL() + } return process.ExecuteAndStreamOutput(kubectl, args...) } @@ -713,8 +768,10 @@ func KubectlDescribeServices(namespace string) error { "-o", "yaml", } - ui.ShellCommand(kubectl, args...) - ui.NL() + if ui.IsVerbose() { + ui.ShellCommand(kubectl, args...) + ui.NL() + } return process.ExecuteAndStreamOutput(kubectl, args...) } @@ -756,6 +813,60 @@ func KubectlDescribeIngresses(namespace string) error { return process.ExecuteAndStreamOutput(kubectl, args...) } +func KubectlGetPodEnvs(selector, namespace string) (map[string]string, error) { + kubectl, clierr := lookupKubectlPath() + if clierr != nil { + return nil, clierr.ActualError + } + + args := []string{ + "get", + "pod", + selector, + "-n", namespace, + "-o", `jsonpath='{range .items[*].spec.containers[*]}{"\nContainer: "}{.name}{"\n"}{range .env[*]}{.name}={.value}{"\n"}{end}{end}'`, + } + + if ui.IsVerbose() { + ui.ShellCommand(kubectl, args...) + ui.NL() + } + + out, err := process.Execute(kubectl, args...) + if err != nil { + return nil, err + } + + return convertEnvToMap(string(out)), nil +} + +func KubectlGetSecret(selector, namespace string) (map[string]string, error) { + kubectl, clierr := lookupKubectlPath() + if clierr != nil { + return nil, clierr.ActualError + } + + args := []string{ + "get", + "secret", + selector, + "-n", namespace, + "-o", `jsonpath='{.data}'`, + } + + if ui.IsVerbose() { + ui.ShellCommand(kubectl, args...) + ui.NL() + } + + out, err := process.Execute(kubectl, args...) + if err != nil { + return nil, err + } + + return secretsJSONToMap(string(out)) +} + func lookupKubectlPath() (string, *CLIError) { kubectlPath, err := exec.LookPath("kubectl") if err != nil { @@ -1004,3 +1115,71 @@ func GetLatestVersion() (string, error) { return strings.TrimPrefix(metadata.TagName, "v"), nil } + +func convertEnvToMap(input string) map[string]string { + result := make(map[string]string) + scanner := bufio.NewScanner(strings.NewReader(input)) + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + // Skip empty lines + if line == "" { + continue + } + + // Split on first = only + parts := strings.SplitN(line, "=", 2) + if len(parts) != 2 { + continue // Skip invalid lines + } + + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + + // Store in map + result[key] = value + } + + return result +} + +func secretsJSONToMap(in string) (map[string]string, error) { + res := map[string]string{} + in = strings.TrimLeft(in, "'") + in = strings.TrimRight(in, "'") + err := json.Unmarshal([]byte(in), &res) + + if len(res) > 0 { + for k := range res { + decoded, err := base64.StdEncoding.DecodeString(res[k]) + if err != nil { + return nil, err + } + res[k] = string(decoded) + } + } + + return res, err +} + +// extractJSONObject extracts JSON from any string +func extractJSONObject(input []byte) ([]byte, error) { + // Find the first '{' and last '}' to extract JSON object + start := bytes.Index(input, []byte("{")) + end := bytes.LastIndex(input, []byte("}")) + + if start == -1 || end == -1 || start > end { + return []byte(""), fmt.Errorf("invalid JSON format") + } + + jsonStr := input[start : end+1] + + // Validate JSON + var prettyJSON bytes.Buffer + if err := json.Indent(&prettyJSON, []byte(jsonStr), "", " "); err != nil { + return []byte(""), err + } + + return prettyJSON.Bytes(), nil +} diff --git a/cmd/kubectl-testkube/commands/common/render/common.go b/cmd/kubectl-testkube/commands/common/render/common.go index 0c88caf60f5..5f6f5f0981c 100644 --- a/cmd/kubectl-testkube/commands/common/render/common.go +++ b/cmd/kubectl-testkube/commands/common/render/common.go @@ -155,7 +155,7 @@ func PrintLogs(client client.Client, info testkube.ServerInfo, execution testkub lastSource = log.Source } - if ui.Verbose { + if ui.IsVerbose() { ui.Print(log.Time.Format("2006-01-02 15:04:05") + " " + log.Content) } else { ui.Print(log.Content) diff --git a/cmd/kubectl-testkube/commands/diagnostics.go b/cmd/kubectl-testkube/commands/diagnostics.go new file mode 100644 index 00000000000..fb293754516 --- /dev/null +++ b/cmd/kubectl-testkube/commands/diagnostics.go @@ -0,0 +1,40 @@ +package commands + +import ( + "github.com/spf13/cobra" + + commands "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/diagnostics" + "github.com/kubeshop/testkube/pkg/diagnostics" + "github.com/kubeshop/testkube/pkg/ui" +) + +// NewDebugCmd creates the 'testkube debug' command +func NewDiagnosticsCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "diagnostics", + Aliases: []string{"diagnose", "diag", "di"}, + Short: "Diagnoze testkube issues with ease", + Run: NewRunDiagnosticsCmdFunc(), + } + + cmd.Flags().StringP("key-override", "k", "", "Pass License key manually (we will not try to locate it automatically)") + cmd.Flags().StringP("file-override", "f", "", "Pass License file manually (we will not try to locate it automatically)") + + cmd.AddCommand(commands.NewLicenseCheckCmd()) + cmd.AddCommand(commands.NewInstallCheckCmd()) + + return cmd +} + +func NewRunDiagnosticsCmdFunc() func(cmd *cobra.Command, args []string) { + return func(cmd *cobra.Command, args []string) { + d := diagnostics.New() + + commands.RegisterInstallValidators(cmd, d) + commands.RegisterLicenseValidators(cmd, d) + + err := d.Run() + ui.ExitOnError("Running validations", err) + ui.NL(2) + } +} diff --git a/cmd/kubectl-testkube/commands/diagnostics/install.go b/cmd/kubectl-testkube/commands/diagnostics/install.go new file mode 100644 index 00000000000..a1ffd6b5512 --- /dev/null +++ b/cmd/kubectl-testkube/commands/diagnostics/install.go @@ -0,0 +1,40 @@ +package diagnostics + +import ( + "github.com/spf13/cobra" + + "github.com/kubeshop/testkube/pkg/diagnostics" + "github.com/kubeshop/testkube/pkg/diagnostics/validators/deps" + "github.com/kubeshop/testkube/pkg/ui" +) + +func RegisterInstallValidators(_ *cobra.Command, d diagnostics.Diagnostics) { + depsGroup := d.AddValidatorGroup("install.dependencies", nil) + depsGroup.AddValidator(deps.NewKubectlDependencyValidator()) + depsGroup.AddValidator(deps.NewHelmDependencyValidator()) +} + +func NewInstallCheckCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "install", + Aliases: []string{"ins", "i"}, + Short: "Diagnose pre-installation dependencies", + Run: RunInstallCheckFunc(), + } + + cmd.Flags().StringP("key-override", "k", "", "Pass License key manually (we will not try to locate it automatically)") + cmd.Flags().StringP("file-override", "f", "", "Pass License file manually (we will not try to locate it automatically)") + + return cmd +} + +func RunInstallCheckFunc() func(cmd *cobra.Command, args []string) { + return func(cmd *cobra.Command, args []string) { + d := diagnostics.New() + RegisterInstallValidators(cmd, d) + + err := d.Run() + ui.ExitOnError("Running validations", err) + ui.NL(2) + } +} diff --git a/cmd/kubectl-testkube/commands/diagnostics/license.go b/cmd/kubectl-testkube/commands/diagnostics/license.go new file mode 100644 index 00000000000..165614c2a2b --- /dev/null +++ b/cmd/kubectl-testkube/commands/diagnostics/license.go @@ -0,0 +1,72 @@ +package diagnostics + +import ( + "github.com/spf13/cobra" + + "github.com/kubeshop/testkube/pkg/diagnostics" + "github.com/kubeshop/testkube/pkg/diagnostics/loader" + "github.com/kubeshop/testkube/pkg/diagnostics/validators/license" + "github.com/kubeshop/testkube/pkg/ui" +) + +func RegisterLicenseValidators(cmd *cobra.Command, d diagnostics.Diagnostics) { + + namespace := cmd.Flag("namespace").Value.String() + keyOverride := cmd.Flag("key-override").Value.String() + fileOverride := cmd.Flag("file-override").Value.String() + + var err error + l := loader.License{} + + if keyOverride != "" { + l.EnterpriseLicenseKey = keyOverride + } + if fileOverride != "" { + l.EnterpriseLicenseFile = fileOverride + } + + if fileOverride != "" && keyOverride != "" { + l.EnterpriseOfflineActivation = true + } + + if fileOverride == "" || keyOverride == "" { + l, err = loader.GetLicenseConfig(namespace, "") + ui.ExitOnError("loading license data", err) + } + + // License validator + licenseGroup := d.AddValidatorGroup("license.validation", l.EnterpriseLicenseKey) + if l.EnterpriseOfflineActivation { + licenseGroup.AddValidator(license.NewFileValidator()) + licenseGroup.AddValidator(license.NewOfflineLicenseKeyValidator()) + licenseGroup.AddValidator(license.NewOfflineLicenseValidator(l.EnterpriseLicenseKey, l.EnterpriseLicenseFile)) + } else { + licenseGroup.AddValidator(license.NewOnlineLicenseKeyValidator()) + licenseGroup.AddValidator(license.NewKeygenShValidator()) + } +} + +func NewLicenseCheckCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "license", + Aliases: []string{"lic", "l"}, + Short: "Diagnose license errors", + Run: RunLicenseCheckFunc(), + } + + cmd.Flags().StringP("key-override", "k", "", "Pass License key manually (we will not try to locate it automatically)") + cmd.Flags().StringP("file-override", "f", "", "Pass License file manually (we will not try to locate it automatically)") + + return cmd +} + +func RunLicenseCheckFunc() func(cmd *cobra.Command, args []string) { + return func(cmd *cobra.Command, args []string) { + d := diagnostics.New() + RegisterLicenseValidators(cmd, d) + + err := d.Run() + ui.ExitOnError("Running validations", err) + ui.NL(2) + } +} diff --git a/cmd/kubectl-testkube/commands/help.go b/cmd/kubectl-testkube/commands/help.go index 6cf7281e610..bfb93c1a67c 100644 --- a/cmd/kubectl-testkube/commands/help.go +++ b/cmd/kubectl-testkube/commands/help.go @@ -29,16 +29,17 @@ func NewHelpCmd() *cobra.Command { ui.Print(RootCmd.Short) ui.NL() ui.Print(ui.LightGray("Usage")) - ui.Printf(fmt.Sprintf("%s %s", ui.White(RootCmd.Use), ui.LightGray("[flags]"))) - ui.Printf("%s %s", ui.White(RootCmd.Use), ui.LightGray("[command]")) + ui.Printf("%s %s\n", ui.White(RootCmd.Use), ui.LightGray("[flags]")) + ui.Printf("%s %s\n", ui.White(RootCmd.Use), ui.LightGray("[command]")) ui.NL() usage := helpMessageByGroups(RootCmd) ui.Print(usage) ui.Print(ui.LightGray("Flags")) ui.Printf(RootCmd.Flags().FlagUsages()) + ui.NL() ui.Print(ui.LightGray("Use \"kubectl testkube [command] --help\" for more information about a command.")) ui.NL() - ui.Printf("%s %s", ui.LightGray("Docs & Support:"), ui.White("https://docs.testkube.io")) + ui.Printf("%s %s\n", ui.LightGray("Docs & Support:"), ui.White("https://docs.testkube.io")) ui.NL() }, } diff --git a/cmd/kubectl-testkube/commands/root.go b/cmd/kubectl-testkube/commands/root.go index a489786f29f..abd9c7e797b 100644 --- a/cmd/kubectl-testkube/commands/root.go +++ b/cmd/kubectl-testkube/commands/root.go @@ -58,6 +58,7 @@ func init() { RootCmd.AddCommand(NewConfigCmd()) RootCmd.AddCommand(NewDebugCmd()) + RootCmd.AddCommand(NewDiagnosticsCmd()) RootCmd.AddCommand(NewCreateTicketCmd()) RootCmd.AddCommand(NewAgentCmd()) diff --git a/go.mod b/go.mod index 531fe380095..f2e215d06b9 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.2 require ( github.com/99designs/gqlgen v0.17.27 github.com/Masterminds/semver v1.5.0 - github.com/adhocore/gronx v1.6.3 + github.com/adhocore/gronx v1.8.1 github.com/avast/retry-go/v4 v4.6.0 github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/cdevents/sdk-go v0.3.0 @@ -21,7 +21,7 @@ require ( github.com/fluxcd/pkg/apis/event v0.2.0 github.com/fsnotify/fsnotify v1.7.0 github.com/gabriel-vasile/mimetype v1.4.6 - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 + github.com/go-task/slim-sprig v2.20.0+incompatible github.com/gofiber/adaptor/v2 v2.1.29 github.com/gofiber/fiber/v2 v2.52.5 github.com/gofiber/websocket/v2 v2.1.1 @@ -39,10 +39,12 @@ require ( github.com/json-iterator/go v1.1.12 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kelseyhightower/envconfig v1.4.0 + github.com/keygen-sh/jsonapi-go v1.2.1 + github.com/keygen-sh/keygen-go/v3 v3.2.0 github.com/kubepug/kubepug v1.7.1 github.com/kubeshop/testkube-operator v1.17.55-0.20241118133003-70462ac10f4a - github.com/minio/minio-go/v7 v7.0.47 - github.com/montanaflynn/stats v0.6.6 + github.com/minio/minio-go/v7 v7.0.66 + github.com/montanaflynn/stats v0.7.1 github.com/moogar0880/problems v0.1.1 github.com/nats-io/nats-server/v2 v2.10.16 github.com/nats-io/nats.go v1.35.0 @@ -52,7 +54,7 @@ require ( github.com/onsi/gomega v1.31.0 github.com/otiai10/copy v1.11.0 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.18.0 + github.com/prometheus/client_golang v1.19.0 github.com/pterm/pterm v0.12.79 github.com/robfig/cron v1.2.0 github.com/savioxavier/termlink v1.4.1 @@ -63,12 +65,12 @@ require ( github.com/spf13/afero v1.11.0 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 - github.com/valyala/fasthttp v1.51.0 + github.com/valyala/fasthttp v1.52.0 github.com/vektah/gqlparser/v2 v2.5.2-0.20230422221642-25e09f9d292d github.com/wI2L/jsondiff v0.6.0 go.mongodb.org/mongo-driver v1.14.0 go.uber.org/zap v1.27.0 - golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 + golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f golang.org/x/oauth2 v0.22.0 golang.org/x/sync v0.8.0 golang.org/x/text v0.19.0 @@ -77,9 +79,9 @@ require ( google.golang.org/protobuf v1.35.1 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.28.4 - k8s.io/apimachinery v0.28.4 - k8s.io/client-go v0.28.4 + k8s.io/api v0.29.3 + k8s.io/apimachinery v0.29.3 + k8s.io/client-go v0.29.3 k8s.io/utils v0.0.0-20230726121419-3b25d923346b sigs.k8s.io/controller-runtime v0.16.3 sigs.k8s.io/kustomize/kyaml v0.15.0 @@ -90,10 +92,11 @@ require ( atomicgo.dev/keyboard v0.2.9 // indirect atomicgo.dev/schedule v0.1.0 // indirect github.com/AlecAivazis/survey/v2 v2.3.7 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/agnivade/levenshtein v1.1.1 // indirect github.com/alecthomas/chroma/v2 v2.8.0 // indirect - github.com/andybalholm/brotli v1.0.5 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -107,21 +110,21 @@ require ( github.com/cli/go-gh/v2 v2.11.0 // indirect github.com/cli/safeexec v1.0.1 // indirect github.com/cli/shurcooL-graphql v0.0.4 // indirect - github.com/containerd/console v1.0.3 // indirect + github.com/containerd/console v1.0.4 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/dlclark/regexp2 v1.4.0 // indirect + github.com/dlclark/regexp2 v1.11.0 // indirect github.com/docker/cli v27.1.1+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.11.2 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.7.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fatih/color v1.16.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-errors/errors v1.5.1 // indirect @@ -131,26 +134,30 @@ require ( github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/go-playground/validator/v10 v10.11.1 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.19.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/golang/snappy v0.0.1 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/css v1.0.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/henvic/httpretty v0.1.4 // indirect + github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/itchyny/timefmt-go v0.1.5 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/keygen-sh/go-update v1.0.0 // indirect github.com/klauspost/compress v1.17.8 // indirect - github.com/klauspost/cpuid/v2 v2.2.3 // indirect - github.com/leodido/go-urn v1.2.1 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/lithammer/fuzzysearch v1.1.8 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect @@ -158,12 +165,11 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/microcosm-cc/bluemonday v1.0.26 // indirect github.com/minio/highwayhash v1.0.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect - github.com/minio/sha256-simd v1.0.0 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -173,24 +179,26 @@ require ( github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.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/nats-io/jwt/v2 v2.5.7 // indirect github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nuid v1.0.1 // indirect + github.com/oasisprotocol/curve25519-voi v0.0.0-20230110094441-db37f07504ce // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect - github.com/package-url/packageurl-go v0.1.0 // indirect + github.com/package-url/packageurl-go v0.1.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/pquerna/cachecontrol v0.2.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rs/xid v1.4.0 // indirect + github.com/rs/xid v1.5.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899 // indirect - github.com/segmentio/backo-go v1.0.0 // indirect + github.com/segmentio/backo-go v1.0.1 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shurcooL/githubv4 v0.0.0-20240120211514-18a1ae0e79dc // indirect github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect @@ -212,7 +220,7 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect github.com/yuin/goldmark v1.5.4 // indirect github.com/yuin/goldmark-emoji v1.0.2 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect @@ -235,11 +243,11 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect - k8s.io/apiextensions-apiserver v0.28.3 // indirect - k8s.io/component-base v0.28.3 // indirect + k8s.io/apiextensions-apiserver v0.29.3 // indirect + k8s.io/component-base v0.29.3 // indirect k8s.io/klog/v2 v2.120.1 // indirect - k8s.io/kube-openapi v0.0.0-20230918164632-68afd615200d // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index cd939940559..56f08fbd156 100644 --- a/go.sum +++ b/go.sum @@ -26,14 +26,16 @@ github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/ github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= -github.com/adhocore/gronx v1.6.3 h1:bnm5vieTrY3QQPpsfB0hrAaeaHDpuZTUC2LLCVMLe9c= -github.com/adhocore/gronx v1.6.3/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= +github.com/adhocore/gronx v1.8.1 h1:F2mLTG5sB11z7vplwD4iydz3YCEjstSfYmCrdSm3t6A= +github.com/adhocore/gronx v1.8.1/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= @@ -48,8 +50,8 @@ github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPp github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= -github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -100,8 +102,9 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudevents/sdk-go/v2 v2.15.2 h1:54+I5xQEnI73RBhWHxbI1XJcqOFOVJN85vb41+8mHUc= github.com/cloudevents/sdk-go/v2 v2.15.2/go.mod h1:lL7kSWAE/V8VI4Wh0jbL2v/jvqsm6tjmaQBSvxcv4uE= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= +github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= @@ -112,7 +115,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= @@ -130,8 +132,8 @@ github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+ github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= -github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE= github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= @@ -148,16 +150,16 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 h1:90Ly+6UfUypEF6vvvW5rQIv9opIL8CbmW9FT20LDQoY= github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0/go.mod h1:V+Qd57rJe8gd4eiGzZyg4h54VLHmYVVw54iMnlAMrF8= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.11.2 h1:1onLa9DcsMYO9P+CXaL0dStDqQ2EHHXLiz+BtnqkLAU= +github.com/emicklei/go-restful/v3 v3.11.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc= -github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/fasthttp/websocket v1.5.0 h1:B4zbe3xXyvIdnqjOZrafVFklCUq5ZLo/TqCt5JA1wLE= github.com/fasthttp/websocket v1.5.0/go.mod h1:n0BlOQvJdPbTuBkZT0O5+jk/sp/1/VCzquR1BehI2F4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -167,6 +169,7 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fluxcd/pkg/apis/event v0.2.0 h1:cmAtkZfoEaNVYegI4SFM8XstdRAil3O9AoP+8fpbR34= github.com/fluxcd/pkg/apis/event v0.2.0/go.mod h1:OyzKqs90J+MK7rQaEOFMMCkALpPkfmxlkabgyY2wSFQ= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= @@ -190,17 +193,17 @@ github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= -github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +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.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= +github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig v2.20.0+incompatible h1:4Xh3bDzO29j4TWNOI+24ubc0vbVFMg2PMnXKxK54/CA= +github.com/go-task/slim-sprig v2.20.0+incompatible/go.mod h1:N/mhXZITr/EQAOErEHciKvO1bFei2Lld2Ym6h96pdy0= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= @@ -225,13 +228,14 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.5.2/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.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= @@ -240,6 +244,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo= github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -268,6 +274,10 @@ github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/henvic/httpretty v0.1.4 h1:Jo7uwIRWVFxkqOnErcoYfH90o3ddQyVrSANeS4cxYmU= @@ -276,6 +286,9 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= @@ -295,6 +308,12 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/keygen-sh/go-update v1.0.0 h1:M65sTVUHUO07tEK4l1Hq7u5D4kdEqkcgfdzUt3q3S08= +github.com/keygen-sh/go-update v1.0.0/go.mod h1:wn0UWRHLnBP5hwXtj1IdHZqWlHvIadh2Nn+becFf8Ro= +github.com/keygen-sh/jsonapi-go v1.2.1 h1:NTSIAxl2+7S5fPnKgrYwNjQSWbdKRtrFq26SD8AOkiU= +github.com/keygen-sh/jsonapi-go v1.2.1/go.mod h1:8j9vsLiKyJyDqmt8r3tYaYNmXszq2+cFhoO6QdMdAes= +github.com/keygen-sh/keygen-go/v3 v3.2.0 h1:OJqnGtY6z4ZA434kZqfNVHDmSrN5zq4l4XItcB3tECY= +github.com/keygen-sh/keygen-go/v3 v3.2.0/go.mod h1:YoFyryzXEk6XrbT3H8EUUU+JcIJkQu414TA6CvZgS/E= 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/klauspost/compress v1.14.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= @@ -302,16 +321,13 @@ github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= -github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -322,8 +338,8 @@ github.com/kubepug/kubepug v1.7.1 h1:LKhfSxS8Y5mXs50v+3Lpyec+cogErDLcV7CMUuiaisw github.com/kubepug/kubepug v1.7.1/go.mod h1:lv+HxD0oTFL7ZWjj0u6HKhMbbTIId3eG7aWIW0gyF8g= github.com/kubeshop/testkube-operator v1.17.55-0.20241118133003-70462ac10f4a h1:xget2cwwqOL+K2Op9FPbMgfzj9lSVJAzZ9p48yxuFrE= github.com/kubeshop/testkube-operator v1.17.55-0.20241118133003-70462ac10f4a/go.mod h1:P47tw1nKQFufdsZndyq2HG2MSa0zK/lU0XpRfZtEmIk= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +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/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -345,8 +361,6 @@ github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -356,10 +370,10 @@ github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.47 h1:sLiuCKGSIcn/MI6lREmTzX91DX/oRau4ia0j6e6eOSs= -github.com/minio/minio-go/v7 v7.0.47/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw= -github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= -github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0Dzw= +github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 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.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -377,8 +391,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= -github.com/montanaflynn/stats v0.6.6 h1:Duep6KMIDpY4Yo11iFsvyqJDyfzLF9+sndUKT+v64GQ= -github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/moogar0880/problems v0.1.1 h1:bktLhq8NDG/czU2ZziYNigBFksx13RaYe5AVdNmHDT4= github.com/moogar0880/problems v0.1.1/go.mod h1:5Dxrk2sD7BfBAgnOzQ1yaTiuCYdGPUh49L8Vhfky62c= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -389,6 +403,8 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= 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/nats-io/jwt/v2 v2.5.7 h1:j5lH1fUXCnJnY8SsQeB/a/z9Azgu2bYIDvtPVNdxe2c= github.com/nats-io/jwt/v2 v2.5.7/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A= github.com/nats-io/nats-server/v2 v2.10.16 h1:2jXaiydp5oB/nAx/Ytf9fdCi9QN6ItIc9eehX8kwVV0= @@ -401,14 +417,20 @@ github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230110094441-db37f07504ce h1:/pEpMk55wH0X+E5zedGEMOdLuWmV8P4+4W3+LZaM6kg= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230110094441-db37f07504ce/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.6-0.20230925090304-df64c4bbad77 h1:3bMMZ1f+GPXFQ1uNaYbO/uECWvSfqEA+ZEXn1rFAT88= github.com/olekukonko/tablewriter v0.0.6-0.20230925090304-df64c4bbad77/go.mod h1:8Hf+pH6thup1sPZPD+NLg7d6vbpsdilu9CPIeikvgMQ= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -420,9 +442,8 @@ github.com/otiai10/copy v1.11.0 h1:OKBD80J/mLBrwnzXqGtFCzprFSGioo30JcmR4APsNwc= github.com/otiai10/copy v1.11.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= -github.com/package-url/packageurl-go v0.1.0 h1:efWBc98O/dBZRg1pw2xiDzovnlMjCa9NPnfaiBduh8I= -github.com/package-url/packageurl-go v0.1.0/go.mod h1:C/ApiuWpmbpni4DIOECf6WCjFUZV7O1Fx7VAzrZHgBw= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/package-url/packageurl-go v0.1.2 h1:0H2DQt6DHd/NeRlVwW4EZ4oEI6Bn40XlNPRqegcxuo4= +github.com/package-url/packageurl-go v0.1.2/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -433,13 +454,13 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= @@ -457,24 +478,22 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= -github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE= -github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/savioxavier/termlink v1.4.1 h1:pFcd+XH8iQjL+2mB4buCDUo+CMt5kKsr8jGG+VLfYAg= github.com/savioxavier/termlink v1.4.1/go.mod h1:5T5ePUlWbxCHIwyF8/Ez1qufOoGM89RCg9NvG+3G3gc= github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899 h1:Orn7s+r1raRTBKLSc9DmbktTT04sL+vkzsbRD2Q8rOI= github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899/go.mod h1:oejLrk1Y/5zOF+c/aHtXqn3TFlzzbAgPWg8zBiAHDas= github.com/segmentio/analytics-go/v3 v3.2.1 h1:G+f90zxtc1p9G+WigVyTR0xNfOghOGs/PYAlljLOyeg= github.com/segmentio/analytics-go/v3 v3.2.1/go.mod h1:p8owAF8X+5o27jmvUognuXxdtqvSGtD0ZrfY2kcS9bE= -github.com/segmentio/backo-go v1.0.0 h1:kbOAtGJY2DqOR0jfRkYEorx/b18RgtepGtY3+Cpe6qA= -github.com/segmentio/backo-go v1.0.0/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M= +github.com/segmentio/backo-go v1.0.1 h1:68RQccglxZeyURy93ASB/2kc9QudzgIDexJ927N++y4= +github.com/segmentio/backo-go v1.0.1/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 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= @@ -541,8 +560,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.33.0/go.mod h1:KJRK/MXx0J+yd0c5hlR+s1tIHD72sniU8ZJjl97LIw4= github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= -github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= -github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= +github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= +github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= @@ -564,8 +583,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -609,16 +628,16 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 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-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= -golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= 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= @@ -632,11 +651,13 @@ golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 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= @@ -661,13 +682,16 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -676,7 +700,6 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/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-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -685,11 +708,11 @@ golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/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-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -705,6 +728,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -764,7 +788,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -788,20 +812,20 @@ gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= 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= -k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= -k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= -k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= -k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= -k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= -k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= -k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= -k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= -k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI= -k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8= +k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw= +k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80= +k8s.io/apiextensions-apiserver v0.29.3 h1:9HF+EtZaVpFjStakF4yVufnXGPRppWFEQ87qnO91YeI= +k8s.io/apiextensions-apiserver v0.29.3/go.mod h1:po0XiY5scnpJfFizNGo6puNU6Fq6D70UJY2Cb2KwAVc= +k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU= +k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU= +k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg= +k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0= +k8s.io/component-base v0.29.3 h1:Oq9/nddUxlnrCuuR2K/jp6aflVvc0uDvxMzAWxnGzAo= +k8s.io/component-base v0.29.3/go.mod h1:Yuj33XXjuOk2BAaHsIGHhCKZQAgYKhqIxIjIr2UXYio= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20230918164632-68afd615200d h1:/CFeJBjBrZvHX09rObS2+2iEEDevMWYc1v3aIYAjIYI= -k8s.io/kube-openapi v0.0.0-20230918164632-68afd615200d/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= @@ -810,7 +834,7 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMm sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/kyaml v0.15.0 h1:ynlLMAxDhrY9otSg5GYE2TcIz31XkGZ2Pkj7SdolD84= sigs.k8s.io/kustomize/kyaml v0.15.0/go.mod h1:+uMkBahdU1KNOj78Uta4rrXH+iH7wvg+nW7+GULvREA= -sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= -sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/diagnostics/diagnostics.go b/pkg/diagnostics/diagnostics.go new file mode 100644 index 00000000000..726762c4ff3 --- /dev/null +++ b/pkg/diagnostics/diagnostics.go @@ -0,0 +1,88 @@ +package diagnostics + +import ( + "errors" + "sync" + + "github.com/kubeshop/testkube/pkg/diagnostics/renderer" + "github.com/kubeshop/testkube/pkg/diagnostics/validators" +) + +const ( + DefaultValidatorGroupName = "default " +) + +var ( + ErrGroupNotFound = errors.New("group not found") +) + +func New() Diagnostics { + return Diagnostics{ + Renderer: renderer.NewCLIRenderer(), + Groups: map[string]*validators.ValidatorGroup{}, + } +} + +// Diagnostics Top level diagnostics service to organize validators in groups +type Diagnostics struct { + Renderer renderer.Renderer + Groups map[string]*validators.ValidatorGroup +} + +// Run executes all validators in all groups and renders the results +func (d Diagnostics) Run() error { + + // for now we'll make validators concurrent + for groupName, _ := range d.Groups { + ch, err := d.RunGroup(groupName) + if err != nil { + return err + } + + d.Renderer.RenderGroupStart(groupName) + for r := range ch { + d.Renderer.RenderResult(r) + if r.BreakValidationChain { + break + } + } + } + + return nil +} + +// RunGroup tries to locate group and run it +func (d Diagnostics) RunGroup(group string) (chan validators.ValidationResult, error) { + ch := make(chan validators.ValidationResult) + g, ok := d.Groups[group] + if !ok { + return ch, ErrGroupNotFound + } + + go func() { + var wg sync.WaitGroup + + defer close(ch) + + if len(g.Validators) > 0 { + for _, v := range g.Validators { + wg.Add(1) + go func(v validators.Validator) { + defer wg.Done() + ch <- v.Validate(g.Subject).WithValidator(v.Name()) + }(v) + } + wg.Wait() + } + }() + + return ch, nil +} + +func (d *Diagnostics) AddValidatorGroup(group string, subject any) *validators.ValidatorGroup { + d.Groups[group] = &validators.ValidatorGroup{ + Subject: subject, + Name: group, + } + return d.Groups[group] +} diff --git a/pkg/diagnostics/loader/license_loader.go b/pkg/diagnostics/loader/license_loader.go new file mode 100644 index 00000000000..90c8c63a901 --- /dev/null +++ b/pkg/diagnostics/loader/license_loader.go @@ -0,0 +1,77 @@ +package loader + +import ( + "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" + "github.com/kubeshop/testkube/pkg/ui" +) + +type License struct { + EnterpriseOfflineActivation bool `envconfig:"ENTERPRISE_OFFLINE_ACTIVATION" default:"false"` + EnterpriseLicenseKey string `envconfig:"ENTERPRISE_LICENSE_KEY"` + EnterpriseLicenseKeyPath string `envconfig:"ENTERPRISE_LICENSE_KEY_PATH" default:"/testkube/license.key"` + EnterpriseLicenseFile string `envconfig:"ENTERPRISE_LICENSE_FILE"` + EnterpriseLicenseFilePath string `envconfig:"ENTERPRISE_LICENSE_FILE_PATH" default:"/testkube/license.lic"` + EnterpriseLicenseFileEncryption string `envconfig:"ENTERPRISE_LICENSE_FILE_ENCRYPTION"` + EnterpriseLicenseName string `envconfig:"ENTERPRISE_LICENSE_NAME"` +} + +const DefaultSecretName = "testkube-enterprise-license" + +func GetLicenseConfig(namespace, secretName string) (l License, err error) { + if secretName == "" { + secretName = DefaultSecretName + } + + // get control plane api pod envs + envs, err := common.KubectlGetPodEnvs("-l app.kubernetes.io/name=testkube-cloud-api", namespace) + if err != nil { + return l, err + } + ui.ExitOnError("getting env variables from pods", err) + + if offlineActivation, ok := envs["ENTERPRISE_OFFLINE_ACTIVATION"]; ok && offlineActivation == "true" { + l.EnterpriseOfflineActivation = true + } + + if f, ok := envs["ENTERPRISE_LICENSE_FILE"]; ok && f != "" { + l.EnterpriseLicenseFile = f + } + + if k, ok := envs["ENTERPRISE_LICENSE_KEY_PATH"]; ok && k != "" { + l.EnterpriseLicenseKeyPath = k + } + + if k, ok := envs["ENTERPRISE_LICENSE_FILE_PATH"]; ok && k != "" { + l.EnterpriseLicenseFilePath = k + } + + if k, ok := envs["ENTERPRISE_LICENSE_KEY"]; ok && k != "" { + l.EnterpriseLicenseKey = k + } + + // try to load from secret - there is no easy way of just stream the key content + secrets, err := common.KubectlGetSecret(secretName, namespace) + ui.WarnOnError("getting secrets from pods", err) + + const ( + keySecretKeyName = "LICENSE_KEY" + fileSecretKeyName = "license.lic" + ) + + // If no direct key value provided try to figure out it from secret mapped as file + if l.EnterpriseLicenseKey == "" { + // try to load from secret - there is no easy way of just stream the key content + if k, ok := secrets[keySecretKeyName]; ok { + l.EnterpriseLicenseKey = k + } + } + + // If no direct file value provided try to figure out it from secret mapped as file + if l.EnterpriseLicenseFile == "" { + if k, ok := secrets[fileSecretKeyName]; ok { + l.EnterpriseLicenseFile = k + } + } + + return l, err +} diff --git a/pkg/diagnostics/renderer/cli.go b/pkg/diagnostics/renderer/cli.go new file mode 100644 index 00000000000..13fd548d8b6 --- /dev/null +++ b/pkg/diagnostics/renderer/cli.go @@ -0,0 +1,57 @@ +package renderer + +import ( + "strings" + + "github.com/kubeshop/testkube/pkg/diagnostics/validators" + "github.com/kubeshop/testkube/pkg/ui" +) + +var _ Renderer = CLIRenderer{} + +func NewCLIRenderer() CLIRenderer { + return CLIRenderer{} +} + +type CLIRenderer struct { +} + +func (r CLIRenderer) RenderGroupStart(message string) { + message = strings.ToUpper(strings.Replace(message, ".", " ", -1)) + lines := strings.Repeat("=", len(message)) + ui.Printf("\n%s\n%s\n\n", ui.Green(message), ui.Yellow(lines)) +} + +func (r CLIRenderer) RenderProgress(message string) { + ui.Printf("%s", message) +} + +func (r CLIRenderer) RenderResult(res validators.ValidationResult) { + + ui.Printf(" %s %s: ", ui.Green(">"), res.Validator) + + if len(res.Errors) > 0 { + ui.Printf("%s\n", ui.IconCross) + + for _, err := range res.Errors { + ui.NL() + ui.Printf(" %s %s\n", ui.IconError, err.Message) + if err.Details != "" { + ui.Printf(" %s\n", ui.LightCyan(err.Details)) + } + if len(err.Suggestions) > 0 { + ui.Info(ui.LightGray(" Consider following suggestions/fixes before proceeding: ")) + for _, s := range err.Suggestions { + ui.Printf(" * %s\n", ui.LightBlue(s)) + } + } + if err.DocsURI != "" { + ui.Printf(" For more details follow docs: [%s]\n", ui.Yellow(err.DocsURI)) + } + } + } else { + ui.Printf("%s", ui.IconCheckMark) + } + ui.NL() + +} diff --git a/pkg/diagnostics/renderer/interface.go b/pkg/diagnostics/renderer/interface.go new file mode 100644 index 00000000000..39b4e0721b6 --- /dev/null +++ b/pkg/diagnostics/renderer/interface.go @@ -0,0 +1,9 @@ +package renderer + +import "github.com/kubeshop/testkube/pkg/diagnostics/validators" + +type Renderer interface { + RenderGroupStart(group string) + RenderResult(validators.ValidationResult) + RenderProgress(message string) +} diff --git a/pkg/diagnostics/renderer/json.go b/pkg/diagnostics/renderer/json.go new file mode 100644 index 00000000000..8457e3395c3 --- /dev/null +++ b/pkg/diagnostics/renderer/json.go @@ -0,0 +1,30 @@ +package renderer + +import ( + "encoding/json" + "os" + + "github.com/kubeshop/testkube/pkg/diagnostics/validators" +) + +var _ Renderer = JSONRenderer{} + +func NewJSONRenderer() JSONRenderer { + return JSONRenderer{ + encoder: json.NewEncoder(os.Stdout), + } +} + +type JSONRenderer struct { + encoder *json.Encoder +} + +func (r JSONRenderer) RenderGroupStart(message string) { +} + +func (r JSONRenderer) RenderProgress(message string) { +} + +func (r JSONRenderer) RenderResult(res validators.ValidationResult) { + r.encoder.Encode(res) +} diff --git a/pkg/diagnostics/validators/deps/errors.go b/pkg/diagnostics/validators/deps/errors.go new file mode 100644 index 00000000000..063fff7f8c6 --- /dev/null +++ b/pkg/diagnostics/validators/deps/errors.go @@ -0,0 +1,24 @@ +package deps + +import v "github.com/kubeshop/testkube/pkg/diagnostics/validators" + +var ( + ErrKubernetesInvalidVersion = v.Err("Your Kubernetes cluster has older version than Testkube require to run correctly", v.ErrorKindFileNotFound). + WithSuggestion("Consider upgrading Kubernetes to recent version"). + WithSuggestion("Please follow your provider upgrading instructions") + ErrKubectlInvalidVersion = v.Err("kubectl has older version than required", v.ErrorKindFileNotFound). + WithSuggestion("Consider upgrading kubectl to recent version"). + WithDocsURI("https://kubernetes.io/docs/tasks/tools") + + ErrKubectlFileNotFound = v.Err("kubectl binary not found", v.ErrorKindFileNotFound). + WithSuggestion("Make sure Kubectl is correctly installed and provided in system PATH"). + WithDocsURI("https://kubernetes.io/docs/tasks/tools") + + ErrHelmFileNotFound = v.Err("helm binary not found", v.ErrorKindFileNotFound). + WithSuggestion("Make sure Helm is correctly installed and provided in system PATH"). + WithDocsURI("https://helm.sh/docs/intro/install/") + + ErrHelmInvalidVersion = v.Err("helm has older version than required", v.ErrorKindFileNotFound). + WithSuggestion("Consider upgrading helm to recent version"). + WithDocsURI("https://helm.sh/docs/intro/install/") +) diff --git a/pkg/diagnostics/validators/deps/helm_binary.go b/pkg/diagnostics/validators/deps/helm_binary.go new file mode 100644 index 00000000000..6de4bf50c77 --- /dev/null +++ b/pkg/diagnostics/validators/deps/helm_binary.go @@ -0,0 +1,26 @@ +package deps + +import ( + "errors" + + "github.com/kubeshop/testkube/pkg/diagnostics/validators" +) + +type HelmDependencyValidator struct{} + +func (v HelmDependencyValidator) Name() string { + return "helm check" +} + +func (v HelmDependencyValidator) Validate(subject any) (r validators.ValidationResult) { + + if !checkFileExists("helm") { + return r.WithStdError(errors.New("helm not found")) + } + + return r.WithValidStatus() +} + +func NewHelmDependencyValidator() HelmDependencyValidator { + return HelmDependencyValidator{} +} diff --git a/pkg/diagnostics/validators/deps/kuebctl_binary.go b/pkg/diagnostics/validators/deps/kuebctl_binary.go new file mode 100644 index 00000000000..b2741da70b8 --- /dev/null +++ b/pkg/diagnostics/validators/deps/kuebctl_binary.go @@ -0,0 +1,55 @@ +package deps + +import ( + "fmt" + + "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" + "github.com/kubeshop/testkube/pkg/diagnostics/validators" + "github.com/kubeshop/testkube/pkg/semver" +) + +func NewKubectlDependencyValidator() KubectlDependencyValidator { + return KubectlDependencyValidator{ + RequiredKubectlVersion: validators.RequiredKubectlVersion, + RequiredKubernetesVersion: validators.RequiredKubernetesVersion, + } +} + +type KubectlDependencyValidator struct { + RequiredKubectlVersion string + RequiredKubernetesVersion string +} + +func (v KubectlDependencyValidator) Name() string { + return "Kubernetes check" +} + +func (v KubectlDependencyValidator) Validate(subject any) (r validators.ValidationResult) { + + if !checkFileExists("kubectl") { + return r.WithError(ErrKubectlFileNotFound) + } + + clientVersion, kubernetesVersion, err := common.KubectlVersion() + if err != nil { + return r.WithStdError(err) + } + + ok, err := semver.Lte(v.RequiredKubectlVersion, clientVersion) + if err != nil { + return r.WithStdError(err) + } + if !ok { + return r.WithError(ErrKubectlInvalidVersion.WithDetails(fmt.Sprintf("We need at least version %s, but your is %s, please consider upgrading", v.RequiredKubectlVersion, clientVersion))) + } + + ok, err = semver.Lte(v.RequiredKubernetesVersion, kubernetesVersion) + if err != nil { + return r.WithStdError(err) + } + if !ok { + return r.WithError(ErrKubernetesInvalidVersion.WithDetails(fmt.Sprintf("We need at least version %s, but your is %s, please consider upgrading", v.RequiredKubectlVersion, kubernetesVersion))) + } + + return r.WithValidStatus() +} diff --git a/pkg/diagnostics/validators/deps/utils.go b/pkg/diagnostics/validators/deps/utils.go new file mode 100644 index 00000000000..1ff79d6623c --- /dev/null +++ b/pkg/diagnostics/validators/deps/utils.go @@ -0,0 +1,15 @@ +package deps + +import ( + "os" + "os/exec" +) + +func checkFileExists(fileName string) bool { + path, err := exec.LookPath(fileName) + if err != nil { + return false + } + _, err = os.Stat(path) + return !os.IsNotExist(err) +} diff --git a/pkg/diagnostics/validators/error.go b/pkg/diagnostics/validators/error.go new file mode 100644 index 00000000000..432641f8ac2 --- /dev/null +++ b/pkg/diagnostics/validators/error.go @@ -0,0 +1,48 @@ +package validators + +import "errors" + +func IsError(err error) bool { + return errors.Is(err, &Error{}) +} + +func Err(e string, kind ErrorKind, suggestions ...string) Error { + err := Error{Message: e, Suggestions: suggestions} + return err +} + +type ErrorKind string + +type Error struct { + Kind ErrorKind + Message string + Details string + Suggestions []string + DocsURI string +} + +func (e Error) Error() string { + s := "" + if e.Message != "" { + s += e.Message + } + if e.Details != "" { + s += " - " + e.Details + } + return s +} + +func (e Error) WithSuggestion(s string) Error { + e.Suggestions = append(e.Suggestions, s) + return e +} + +func (e Error) WithDetails(d string) Error { + e.Details = d + return e +} + +func (e Error) WithDocsURI(d string) Error { + e.DocsURI = d + return e +} diff --git a/pkg/diagnostics/validators/group.go b/pkg/diagnostics/validators/group.go new file mode 100644 index 00000000000..e2da24693b4 --- /dev/null +++ b/pkg/diagnostics/validators/group.go @@ -0,0 +1,13 @@ +package validators + +// ValidatorGroup to organize validators around given subject e.g. "License Key" +type ValidatorGroup struct { + Subject any + Name string + Validators []Validator +} + +// AddValidator adds a new validator to the group +func (vg *ValidatorGroup) AddValidator(v Validator) { + vg.Validators = append(vg.Validators, v) +} diff --git a/pkg/diagnostics/validators/interface.go b/pkg/diagnostics/validators/interface.go new file mode 100644 index 00000000000..4779f93a04b --- /dev/null +++ b/pkg/diagnostics/validators/interface.go @@ -0,0 +1,8 @@ +package validators + +// Validator interface defines the Validate method for validation logic +type Validator interface { + // Validate runs validation logic against subject + Validate(subject any) ValidationResult + Name() string +} diff --git a/pkg/diagnostics/validators/kinds.go b/pkg/diagnostics/validators/kinds.go new file mode 100644 index 00000000000..c57962b2f76 --- /dev/null +++ b/pkg/diagnostics/validators/kinds.go @@ -0,0 +1,46 @@ +package validators + +const ( + ErrorKindCustom ErrorKind = "custom" + ErrorKindFileNotFound ErrorKind = "file not found" + ErrorKindKeyNotFound ErrorKind = "key not found" + + ErrorKindInvalidFileContent ErrorKind = "invalid file content" + ErrorKindInvalidKeyContent ErrorKind = "invalid key content" + ErrorKindBadWhitespaces ErrorKind = "bad whitespaces" + + ErrorKindLicenseInvalid ErrorKind = "license invalid" + ErrorKindLicenseExpired ErrorKind = "license expired" +) + +var ( + // Suggestions map + Suggestions = map[ErrorKind][]string{ + ErrorKindKeyNotFound: { + "please provide valid file for your control plane installation", + "you can pass file as environment variable details here https://docs.testkube.io/installation", + "make sure valid environment variables are set in pods", + }, + ErrorKindFileNotFound: { + "please provide valid file for your control plane installation", + "you can pass file as environment variable details here https://docs.testkube.io/blabalbalabl", + "make sure valid environment variables are set in pods, you can use `kubectl describe pods ....`", + }, + ErrorKindInvalidKeyContent: { + "please make sure your key is in valid format", + "please make sure given key was not modified in any editor", + "check if provided value was not changed by accident", + "check if additional whitespases were not added on the beggining and the end of the key", + }, + ErrorKindInvalidFileContent: { + "please make sure your key is in valid format", + "please make sure given key was not modified in any editor", + "check if provided value was not changed by accident", + "check if additional whitespases were not added on the beggining and the end of the key", + }, + ErrorKindBadWhitespaces: { + "please make sure given key was not modified in any editor", + "check if additional whitespases were not added on the beggining and the end of the key", + }, + } +) diff --git a/pkg/diagnostics/validators/license/client.go b/pkg/diagnostics/validators/license/client.go new file mode 100644 index 00000000000..1564828bae1 --- /dev/null +++ b/pkg/diagnostics/validators/license/client.go @@ -0,0 +1,70 @@ +package license + +import ( + "bytes" + "encoding/json" + "io" + "net/http" +) + +type LicenseResponse struct { + Valid bool `json:"valid,omitempty"` + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` + License struct { + Expiry string `json:"expiry,omitempty"` + Name string `json:"name,omitempty"` + } `json:"license,omitempty"` +} + +type LicenseRequest struct { + License string `json:"license"` +} + +type Client struct { + url string +} + +const LicenseValidationURL = "https://license.testkube.io/validate" + +func NewClient() *Client { + return &Client{url: LicenseValidationURL} +} + +func (c *Client) WithURL(url string) *Client { + c.url = url + return c +} + +func (c *Client) ValidateLicense(licenseRequest LicenseRequest) (*LicenseResponse, error) { + reqBody, err := json.Marshal(licenseRequest) + if err != nil { + return nil, err + } + + resp, err := http.Post(c.url, "application/json", bytes.NewBuffer(reqBody)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var licenseResponse LicenseResponse + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode >= 400 { + return &LicenseResponse{ + Valid: false, + Message: string(b), + }, nil + } + + err = json.Unmarshal(b, &licenseResponse) + if err != nil { + return nil, err + } + + return &licenseResponse, nil +} diff --git a/pkg/diagnostics/validators/license/client_test.go b/pkg/diagnostics/validators/license/client_test.go new file mode 100644 index 00000000000..77c05e3beec --- /dev/null +++ b/pkg/diagnostics/validators/license/client_test.go @@ -0,0 +1,69 @@ +package license + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidateLicense(t *testing.T) { + // Test for successful validation + t.Run("Success", func(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + t.Errorf("Expected POST request, got %s", r.Method) + } + var reqBody LicenseRequest + err := json.NewDecoder(r.Body).Decode(&reqBody) + if err != nil || reqBody.License != "valid-license" { + http.Error(w, "Invalid request", http.StatusBadRequest) + return + } + response := LicenseResponse{Valid: true, Message: "License is valid"} + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) + // Corrected the response struct to match the expected fields + })) + defer mockServer.Close() + + client := NewClient().WithURL(mockServer.URL) + + resp, err := client.ValidateLicense(LicenseRequest{License: "valid-license"}) + assert.NoError(t, err) + assert.True(t, resp.Valid) + assert.Equal(t, "License is valid", resp.Message) + }) + + // Test for failure due to invalid request + t.Run("FailureInvalidRequest", func(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "Invalid request", http.StatusBadRequest) + })) + defer mockServer.Close() + + client := NewClient().WithURL(mockServer.URL) + + resp, err := client.ValidateLicense(LicenseRequest{License: "invalid-license"}) + assert.NoError(t, err) + assert.False(t, resp.Valid) + }) + + t.Run("RealValidation license valid", func(t *testing.T) { + client := NewClient() + + response, err := client.ValidateLicense(LicenseRequest{License: "AB24F3-405E39-C3F657-94D113-F06C13-V3"}) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + if !response.Valid { + t.Errorf("Expected license to be valid, got %v", response.Valid) + } + if response.Code != "VALID" { + t.Errorf("Expected message 'VALID', got %s", response.Code) + } + }) +} diff --git a/pkg/diagnostics/validators/license/errors.go b/pkg/diagnostics/validators/license/errors.go new file mode 100644 index 00000000000..41b2c25a89e --- /dev/null +++ b/pkg/diagnostics/validators/license/errors.go @@ -0,0 +1,56 @@ +package license + +import ( + v "github.com/kubeshop/testkube/pkg/diagnostics/validators" +) + +// Errors definitions for license based logic +var ( + ErrLicenseKeyNotFound = v.Err("license key not found", v.ErrorKindKeyNotFound). + WithSuggestion("Make sure license key was correctly provided in for the testkube-cloud-api deployment"). + WithSuggestion("You can grab deployment detail with kubectl command - `kubectl get deployment testkube-cloud-api -n testkube`, check for ENTERPRISE_LICENSE_KEY value"). + WithSuggestion("Check your Helm chart installation values") + + ErrLicenseKeyInvalidFormat = v.Err("license key invalid format", v.ErrorKindInvalidKeyContent) + + ErrOnlineLicenseKeyInvalidLength = v.Err("license key invalid length", v.ErrorKindInvalidKeyContent). + WithDetails("License key should be in form XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX-XX - 37 chars in length"). + WithSuggestion("Make sure license key is in valid format"). + WithSuggestion("Make sure there is no whitespaces on the begining and the end of the key") + + ErrWhitespacesAdded = v.Err("license key contains additional whitespaces", v.ErrorKindBadWhitespaces). + WithSuggestion("Make sure there is no whitespaces on the begining and the end of the key") + + ErrLicenseFileNotFound = v.Err("license file not found", v.ErrorKindFileNotFound). + WithSuggestion("Make sure license key was correctly provided in for the testkube-cloud-api deployment"). + WithSuggestion("You can grab deployment detail with kubectl command - `kubectl get deployment testkube-cloud-api -n testkube`, check for ENTERPRISE_LICENSE_FILE value") + + ErrKeygenShValidation = v.Err("license is invalid", v.ErrorKindInvalidKeyContent) + + ErrKeygenShValidationExpired = v.Err("license is expired", v.ErrorKindLicenseExpired). + WithDetails("Looks like your testkube license has expired"). + WithSuggestion("Please contact testkube team [https://testkube.io/contact] to check with your license") + + ErrOfflineLicenseKeyInvalidPrefix = v.Err("license key has invalid prefix", v.ErrorKindInvalidKeyContent). + WithDetails("License key should start with 'key/' string"). + WithSuggestion("Make sure license key is in valid format"). + WithSuggestion("Make sure you're NOT using offline keys for air-gapped (offline) installations"). + WithSuggestion("Make sure there is no whitespaces on the begining and the end of the key") + + ErrOfflineLicensePublicKeyMissing = v.Err("public key is missing", v.ErrorKindLicenseInvalid) + ErrOfflineLicenseInvalid = v.Err("offline license is invalid", v.ErrorKindLicenseInvalid) + ErrOfflineLicenseVerificationInvalid = v.Err("offline license verification error", v.ErrorKindLicenseInvalid) + ErrOfflineLicenseCertificateInvalid = v.Err("offline license certificate error", v.ErrorKindLicenseInvalid) + ErrOfflineLicenseDecodingError = v.Err("offline license decoding error", v.ErrorKindLicenseInvalid) + ErrOfflineLicenseLicenseFileIsNotGenuine = v.Err("license file is not genuine", v.ErrorKindLicenseInvalid) + ErrOfflineLicenseClockTamperingDetected = v.Err("system clock tampering detected", v.ErrorKindLicenseInvalid) + ErrOfflineLicenseFileExpired = v.Err("license file is expired", v.ErrorKindLicenseExpired). + WithDetails("Looks like your testkube license has expired"). + WithSuggestion("Please contact testkube team [https://testkube.io/contact] to check with your license") + + ErrOfflineLicenseDatasetIsMissing = v.Err("license dataset missing", v.ErrorKindLicenseInvalid) + + ErrOfflineLicenseExpired = v.Err("license is expired", v.ErrorKindLicenseExpired). + WithDetails("Looks like your testkube license has expired"). + WithSuggestion("Please contact testkube team [https://testkube.io/contact] to check with your license") +) diff --git a/pkg/diagnostics/validators/license/file_validator.go b/pkg/diagnostics/validators/license/file_validator.go new file mode 100644 index 00000000000..0b6017db029 --- /dev/null +++ b/pkg/diagnostics/validators/license/file_validator.go @@ -0,0 +1,42 @@ +package license + +import ( + "strings" + + "github.com/kubeshop/testkube/pkg/diagnostics/validators" +) + +func NewFileValidator() FileValidator { + return FileValidator{} +} + +type FileValidator struct { +} + +func (v FileValidator) Name() string { + return "License file check" +} + +// Validate validates a given license file for format / length correctness without calling external services +func (v FileValidator) Validate(subject any) (r validators.ValidationResult) { + r = r.WithValidator("License file check") + // get file + file, ok := subject.(string) + if !ok { + return r.WithError(ErrLicenseKeyInvalidFormat) + } + + if file == "" { + return r.WithError(ErrLicenseFileNotFound) + } + + // check if file doesn't contain invalid spaces + cleaned := strings.TrimSpace(file) + if file != cleaned { + return r.WithError(ErrWhitespacesAdded) + } + + // TODO use checks for file format validation + + return r.WithValidStatus() +} diff --git a/pkg/diagnostics/validators/license/keygensh_validator.go b/pkg/diagnostics/validators/license/keygensh_validator.go new file mode 100644 index 00000000000..9e17ac42baf --- /dev/null +++ b/pkg/diagnostics/validators/license/keygensh_validator.go @@ -0,0 +1,63 @@ +package license + +import ( + "fmt" + + "github.com/kubeshop/testkube/pkg/diagnostics/validators" +) + +var ( + + // Errors + ErrInvalidLicenseFormat = validators.ValidationResult{ + Status: validators.StatusInvalid, + Errors: []validators.Error{ + ErrLicenseKeyInvalidFormat, + }, + } +) + +func NewKeygenShValidator() KeygenShValidator { + return KeygenShValidator{ + Client: NewClient(), + } +} + +type KeygenShValidator struct { + Client *Client +} + +func (v KeygenShValidator) Name() string { + return "License key correctness online check" +} + +func (v KeygenShValidator) Validate(subject any) (r validators.ValidationResult) { + // get key + key, ok := subject.(string) + if !ok { + return r.WithError(ErrLicenseKeyInvalidFormat) + } + + // validate + resp, err := v.Client.ValidateLicense(LicenseRequest{License: key}) + if err != nil { + return r.WithStdError(err) + } + + return mapResponseToValidatonResult(r, resp) + +} + +func mapResponseToValidatonResult(r validators.ValidationResult, resp *LicenseResponse) validators.ValidationResult { + if resp.Valid { + return r.WithValidStatus() + } + + switch resp.Code { + case "EXPIRED": + return r.WithError(ErrKeygenShValidationExpired. + WithDetails(fmt.Sprintf("Looks like your license '%s' has expired at '%s'", resp.License.Name, resp.License.Expiry))) + } + + return r.WithError(ErrKeygenShValidation.WithDetails(resp.Message + resp.Code)) +} diff --git a/pkg/diagnostics/validators/license/offline_key_validator.go b/pkg/diagnostics/validators/license/offline_key_validator.go new file mode 100644 index 00000000000..85851b6b9cf --- /dev/null +++ b/pkg/diagnostics/validators/license/offline_key_validator.go @@ -0,0 +1,39 @@ +package license + +import ( + "strings" + + "github.com/kubeshop/testkube/pkg/diagnostics/validators" +) + +func NewOfflineLicenseKeyValidator() OfflineLicenseKeyValidator { + return OfflineLicenseKeyValidator{} +} + +type OfflineLicenseKeyValidator struct { +} + +func (v OfflineLicenseKeyValidator) Name() string { + return "Offline license key check" +} + +// Validate validates a given license key for format / length correctness without calling external services +func (v OfflineLicenseKeyValidator) Validate(subject any) (r validators.ValidationResult) { + // get key + key, ok := subject.(string) + if !ok { + return r.WithError(ErrLicenseKeyInvalidFormat).WithBreak() + } + + if key == "" { + return r.WithError(ErrLicenseKeyNotFound).WithBreak() + } + + // key can be in enrypted format + if !strings.HasPrefix(key, "key/") { + return r.WithError(ErrOfflineLicenseKeyInvalidPrefix) + + } + + return r.WithValidStatus() +} diff --git a/pkg/diagnostics/validators/license/offline_license_validator.go b/pkg/diagnostics/validators/license/offline_license_validator.go new file mode 100644 index 00000000000..a61fe4c4863 --- /dev/null +++ b/pkg/diagnostics/validators/license/offline_license_validator.go @@ -0,0 +1,167 @@ +package license + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "os" + "strings" + "time" + + "github.com/keygen-sh/jsonapi-go" + "github.com/keygen-sh/keygen-go/v3" + + "github.com/kubeshop/testkube/pkg/diagnostics/validators" +) + +// KeygenOfflinePublicKey will be set through build process when changed +var KeygenOfflinePublicKey = "" + +type License struct { + License *keygen.License + Entitlements keygen.Entitlements + IsOffline bool `json:"isOffline"` +} + +func NewOfflineLicenseValidator(key, file string) OfflineLicenseValidator { + return OfflineLicenseValidator{ + LicenseKey: key, + LicenseFile: file, + } +} + +type OfflineLicenseValidator struct { + LicenseFile string + LicenseKey string +} + +func (v OfflineLicenseValidator) Name() string { + return "Offline license validation/signing check" +} + +// Validate validates a given license key for format / length correctness without calling external services +func (v OfflineLicenseValidator) Validate(_ any) (r validators.ValidationResult) { + l, err := v.ValidateOfflineLicenseCert(v.LicenseKey, v.LicenseFile) + if l == nil { + return r.WithError(err) + } + + if l.License.Expiry.Before(time.Now()) { + return r.WithError(ErrOfflineLicenseExpired. + WithDetails(fmt.Sprintf("Your license file expired in %s, please renew your license", l.License.Expiry))) + } + + left := l.License.Expiry.Sub(time.Now()) + return r.WithValidStatus().WithAdditionalInfo(fmt.Sprintf("license is still valid %d days", int(left.Hours())/24)) +} + +func (v *OfflineLicenseValidator) ValidateOfflineLicenseCert(key string, file string) (l *License, e validators.Error) { + if KeygenOfflinePublicKey == "" { + key, ok := os.LookupEnv("KEYGEN_PUBLIC_KEY") + if ok { + KeygenOfflinePublicKey = key + } + } + keygen.PublicKey = KeygenOfflinePublicKey + keygen.LicenseKey = key + + // Verify the license file's signature + lic := &keygen.LicenseFile{Certificate: file} + err := lic.Verify() + switch { + case errors.Is(err, keygen.ErrPublicKeyMissing): + return nil, ErrOfflineLicensePublicKeyMissing + case errors.Is(err, keygen.ErrLicenseFileNotGenuine): + return nil, ErrOfflineLicenseLicenseFileIsNotGenuine + case err != nil: + return nil, ErrOfflineLicenseVerificationInvalid.WithDetails(err.Error()) + } + + cert, err := certificate(lic) + if err != nil { + return nil, ErrOfflineLicenseCertificateInvalid.WithDetails(err.Error()) + } + + var dataset *keygen.LicenseFileDataset + switch { + case strings.HasPrefix(cert.Alg, "aes-256"): + // The license key is used as the key for the symmetric encryption. + dataset, err = lic.Decrypt(key) + case strings.HasPrefix(cert.Alg, "base64"): + dataset, err = decode(cert) + } + + switch { + case errors.Is(err, keygen.ErrSystemClockUnsynced): + return nil, ErrOfflineLicenseClockTamperingDetected + case errors.Is(err, keygen.ErrLicenseFileExpired): + return nil, ErrOfflineLicenseFileExpired + case err != nil: + return nil, ErrOfflineLicenseDecodingError.WithDetails(err.Error()) + } + + if dataset == nil { + return nil, ErrOfflineLicenseDatasetIsMissing + } + + return &License{ + License: &dataset.License, + Entitlements: dataset.Entitlements, + IsOffline: true, + }, e +} + +func decode(cert *keygenCertificate) (*keygen.LicenseFileDataset, error) { + if cert.Alg != "base64+ed25519" { + return nil, keygen.ErrLicenseFileNotSupported + } + + // continue here decode with base64 and json parse properly + data, err := base64.StdEncoding.DecodeString(cert.Enc) + if err != nil { + return nil, &keygen.LicenseFileError{Err: err} + } + + // Unmarshal + dataset := &keygen.LicenseFileDataset{} + + if _, err := jsonapi.Unmarshal(data, dataset); err != nil { + return nil, err + } + + if dataset.TTL != 0 && time.Now().After(dataset.Expiry) { + return dataset, keygen.ErrLicenseFileExpired + } + + return dataset, nil +} + +type keygenCertificate struct { + Enc string `json:"enc"` + Sig string `json:"sig"` + Alg string `json:"alg"` +} + +func certificate(lic *keygen.LicenseFile) (*keygenCertificate, error) { + payload := strings.TrimSpace(lic.Certificate) + + // Remove header and footer + payload = strings.TrimPrefix(payload, "-----BEGIN LICENSE FILE-----") + payload = strings.TrimSuffix(payload, "-----END LICENSE FILE-----") + payload = strings.TrimSpace(payload) + + // Decode + dec, err := base64.StdEncoding.DecodeString(payload) + if err != nil { + return nil, &keygen.LicenseFileError{Err: err} + } + + // Unmarshal + var cert *keygenCertificate + if err := json.Unmarshal(dec, &cert); err != nil { + return nil, &keygen.LicenseFileError{Err: err} + } + + return cert, nil +} diff --git a/pkg/diagnostics/validators/license/offline_license_validator_test.go b/pkg/diagnostics/validators/license/offline_license_validator_test.go new file mode 100644 index 00000000000..a4f6f523c5a --- /dev/null +++ b/pkg/diagnostics/validators/license/offline_license_validator_test.go @@ -0,0 +1,54 @@ +package license + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/kubeshop/testkube/pkg/diagnostics/validators" +) + +// Test_Licenses basic test when licenses files are provided +func Test_Licenses(t *testing.T) { + + var workingLicense = os.Getenv("TEST_WORKING_LICENSE") + var exampleWorkingFile = os.Getenv("TEST_VALID_LICENSE_FILE") + var fileTTLExpired = os.Getenv("TEST_INVALID_LICENSE_FILE_TTL") + var licenseExpired = os.Getenv("TEST_INVALID_LICENSE_FILE_EXPIRED") + + if workingLicense == "" { + t.Skip("working license env var not provided skipping test") + } + + t.Run("valid key valid file", func(t *testing.T) { + if exampleWorkingFile == "" { + t.Skip("env vars not provided skipping test") + } + v := NewOfflineLicenseValidator(workingLicense, exampleWorkingFile) + r := v.Validate("") + assert.Equal(t, r.Status, validators.StatusValid) + }) + + t.Run("valid key file ttl expired", func(t *testing.T) { + if fileTTLExpired == "" { + t.Skip("env vars not provided skipping test") + } + v := NewOfflineLicenseValidator(workingLicense, fileTTLExpired) + r := v.Validate("") + + assert.Equal(t, r.Status, validators.StatusInvalid) + assert.Equal(t, r.Errors[0].Error(), ErrOfflineLicenseFileExpired.Error()) + }) + + t.Run("valid key license expired file", func(t *testing.T) { + if licenseExpired == "" { + t.Skip("env vars not provided skipping test") + } + v := NewOfflineLicenseValidator(workingLicense, licenseExpired) + r := v.Validate("") + + assert.Equal(t, r.Status, validators.StatusInvalid) + assert.Equal(t, r.Errors[0].Error(), ErrOfflineLicenseExpired.Error()) + }) +} diff --git a/pkg/diagnostics/validators/license/online_key_validator.go b/pkg/diagnostics/validators/license/online_key_validator.go new file mode 100644 index 00000000000..4f263509e26 --- /dev/null +++ b/pkg/diagnostics/validators/license/online_key_validator.go @@ -0,0 +1,47 @@ +package license + +import ( + "fmt" + "regexp" + + "github.com/kubeshop/testkube/pkg/diagnostics/validators" +) + +func NewOnlineLicenseKeyValidator() OnlineLicenseKeyValidator { + return OnlineLicenseKeyValidator{} +} + +type OnlineLicenseKeyValidator struct { +} + +func (v OnlineLicenseKeyValidator) Name() string { + return "License key format check" +} + +// Validate validates a given license key for format / length correctness without calling external services +func (v OnlineLicenseKeyValidator) Validate(subject any) validators.ValidationResult { + r := validators.NewResult() + + // get key + key, ok := subject.(string) + if !ok { + return r.WithError(ErrLicenseKeyInvalidFormat) + } + + if key == "" { + return r.WithError(ErrLicenseKeyNotFound) + } + + // Check if the license key is the correct length and validate + if len(key) != 37 { + return r.WithError(ErrOnlineLicenseKeyInvalidLength.WithDetails(fmt.Sprintf("Passed license key length is %d and should be 37", len(key)))) + } + + // Check if the license key matches the expected format + match, _ := regexp.MatchString(`^([A-Z0-9_]{6}-){5}[^-]{2}$`, key) + if !match { + return r.WithError(ErrOnlineLicenseKeyInvalidLength) + } + + return r.WithValidStatus() +} diff --git a/pkg/diagnostics/validators/license/validator_test.go b/pkg/diagnostics/validators/license/validator_test.go new file mode 100644 index 00000000000..1da49e97abd --- /dev/null +++ b/pkg/diagnostics/validators/license/validator_test.go @@ -0,0 +1,14 @@ +package license + +import ( + "regexp" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRegexp(t *testing.T) { + key := "8D9E74-444441-DEF387-31EE50-5F2A2E-V3" + match, _ := regexp.MatchString(`^([^-]{6}-){5}[^-]{2}$`, key) + assert.True(t, match) +} diff --git a/pkg/diagnostics/validators/mock/invalid.go b/pkg/diagnostics/validators/mock/invalid.go new file mode 100644 index 00000000000..339b150d7a6 --- /dev/null +++ b/pkg/diagnostics/validators/mock/invalid.go @@ -0,0 +1,11 @@ +package mock + +import v "github.com/kubeshop/testkube/pkg/diagnostics/validators" + +type AlwaysInvalidValidator struct { + Name string +} + +func (val AlwaysInvalidValidator) Validate(subject any) v.ValidationResult { + return v.NewResult().WithError(v.Err("Some error", v.ErrorKindCustom)).WithValidator("Always invalid " + val.Name) +} diff --git a/pkg/diagnostics/validators/mock/invalid_multi.go b/pkg/diagnostics/validators/mock/invalid_multi.go new file mode 100644 index 00000000000..8c08698a805 --- /dev/null +++ b/pkg/diagnostics/validators/mock/invalid_multi.go @@ -0,0 +1,15 @@ +package mock + +import v "github.com/kubeshop/testkube/pkg/diagnostics/validators" + +type AlwaysInvalidMultiValidator struct { + Name string +} + +func (val AlwaysInvalidMultiValidator) Validate(subject any) v.ValidationResult { + return v.NewResult(). + WithValidator("Always invalid " + val.Name). + WithError(v.Err("err1", v.ErrorKindCustom).WithDetails("some error occured")). + WithError(v.Err("err2", v.ErrorKindCustom).WithDetails("some error occured")). + WithError(v.Err("err3", v.ErrorKindCustom).WithDocsURI("https://docs.testkube.io/")) +} diff --git a/pkg/diagnostics/validators/mock/valid.go b/pkg/diagnostics/validators/mock/valid.go new file mode 100644 index 00000000000..cd3d7a07aae --- /dev/null +++ b/pkg/diagnostics/validators/mock/valid.go @@ -0,0 +1,11 @@ +package mock + +import v "github.com/kubeshop/testkube/pkg/diagnostics/validators" + +type AlwaysValidValidator struct { + Name string +} + +func (val AlwaysValidValidator) Validate(subject any) v.ValidationResult { + return v.NewResult().WithValidStatus().WithValidator("Always valid " + val.Name) +} diff --git a/pkg/diagnostics/validators/requirements.go b/pkg/diagnostics/validators/requirements.go new file mode 100644 index 00000000000..0d3c055dd42 --- /dev/null +++ b/pkg/diagnostics/validators/requirements.go @@ -0,0 +1,8 @@ +package validators + +const ( + // Minimal required versions + RequiredHelmVersion = "3.0.0" + RequiredKubectlVersion = "1.0.0" + RequiredKubernetesVersion = "1.0.0" +) diff --git a/pkg/diagnostics/validators/result.go b/pkg/diagnostics/validators/result.go new file mode 100644 index 00000000000..5677f63b55e --- /dev/null +++ b/pkg/diagnostics/validators/result.go @@ -0,0 +1,87 @@ +package validators + +type Status string + +const ( + StatusValid Status = "valid" + StatusInvalid Status = "invalid" +) + +func NewResult() ValidationResult { + return ValidationResult{ + Status: StatusInvalid, + } +} + +func NewErrorResponse(err error) ValidationResult { + return ValidationResult{ + Status: StatusInvalid, + Errors: []Error{ + { + Message: err.Error(), + Suggestions: []string{ + "got unexpected error, please contact Testkube team", + }, + }, + }, + } +} + +func NewValidResponse() ValidationResult { + return ValidationResult{ + Status: StatusValid, + } +} + +// ValidationResult represents the result of a validation operation +type ValidationResult struct { + BreakValidationChain bool + + Validator string + Status Status + + // Errors + Errors []Error + + // Logs + Logs map[string]string + + AdditionalInfo string +} + +func (r ValidationResult) WithValidator(v string) ValidationResult { + r.Validator = v + return r +} + +func (r ValidationResult) WithBreak() ValidationResult { + r.BreakValidationChain = true + return r +} + +func (r ValidationResult) WithValidStatus() ValidationResult { + r.Status = StatusValid + return r +} + +func (r ValidationResult) WithInvalidStatus() ValidationResult { + r.Status = StatusValid + return r +} + +func (r ValidationResult) WithAdditionalInfo(i string) ValidationResult { + r.AdditionalInfo = i + return r +} + +func (r ValidationResult) WithError(err Error) ValidationResult { + r.Status = StatusInvalid + r.Errors = append(r.Errors, err) + return r +} + +func (r ValidationResult) WithStdError(err error) ValidationResult { + r.Status = StatusInvalid + r.Errors = append(r.Errors, Error{Kind: ErrorKindCustom, Message: err.Error()}) + return r +} diff --git a/pkg/ui/printers.go b/pkg/ui/printers.go index 362c2a80c2b..bf086196392 100644 --- a/pkg/ui/printers.go +++ b/pkg/ui/printers.go @@ -106,7 +106,6 @@ func (ui *UI) Print(message string, subMessages ...string) { func (ui *UI) Printf(format string, data ...any) { fmt.Fprintf(ui.Writer, format, data...) - fmt.Fprintln(ui.Writer) } func (ui *UI) PrintDot() {