diff --git a/.goreleaser.yml b/.goreleaser.yml index 89e863b..c7a823e 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,4 +1,5 @@ project_name: status +version: 2 release: github: owner: bergerx @@ -6,6 +7,7 @@ release: before: hooks: - go generate ./... + - go mod tidy builds: - id: status goos: diff --git a/Makefile b/Makefile index b4cc083..78be965 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ vet: .PHONY: staticcheck staticcheck: - go run honnef.co/go/tools/cmd/staticcheck@v0.4.7 ./... + go run honnef.co/go/tools/cmd/staticcheck@v0.5.1 ./... .PHONY: clean clean: diff --git a/cmd/main.go b/cmd/main.go index 85983c8..d109b30 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -81,14 +81,17 @@ func RootCmd() *cobra.Command { Long: longCmdMessage, Example: examplesMessage, PreRun: func(cmd *cobra.Command, args []string) { - _ = viper.BindPFlags(cmd.Flags()) + viper.AutomaticEnv() + err := viper.BindPFlags(cmd.Flags()) + if err != nil { + cmd.PrintErr("error binding flags", err) + } }, SilenceUsage: true, Version: version, } initColorCobra(cmd) configFlags := initFlags(cmd) - cobra.OnInitialize(viper.AutomaticEnv) viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) f := cmdutil.NewFactory(configFlags) cmd.RunE = func(cmd *cobra.Command, args []string) error { @@ -99,7 +102,8 @@ func RootCmd() *cobra.Command { }) cmdutil.CheckErr(complete(f)) cmdutil.CheckErr(validate()) - if b, _ := cmd.Flags().GetBool("time-hack-ago"); b { + if b, _ := cmd.Flags().GetBool("test-hack"); b { + viper.Set("test-hack", true) plugin.SetDurationRound(func(_ interface{}) string { return "1m" }) } ioStreams := genericiooptions.IOStreams{In: cmd.InOrStdin(), Out: cmd.OutOrStdout(), ErrOut: cmd.ErrOrStderr()} @@ -147,11 +151,13 @@ func initColorCobra(cmd *cobra.Command) { } func hideNoisyFlags(flags *pflag.FlagSet) { - flagsToHide := []string{"add_dir_header", "as-uid", "alsologtostderr", "as", "as-group", "cache-dir", + flagsToHide := []string{ + "add_dir_header", "as-uid", "alsologtostderr", "as", "as-group", "cache-dir", "certificate-authority", "client-certificate", "client-key", "cluster", "context", "insecure-skip-tls-verify", "kubeconfig", "log_backtrace_at", "log_dir", "log_file", "log_file_max_size", "logtostderr", "one_output", "password", "request-timeout", "server", "skip_headers", "skip_log_headers", "stderrthreshold", - "tls-server-name", "token", "user", "username", "vmodule", "time-hack-ago"} + "tls-server-name", "token", "user", "username", "vmodule", "test-hack", + } for _, flagName := range flagsToHide { flags.Lookup(flagName).Hidden = true } @@ -201,8 +207,8 @@ func addRenderFlags(flags *pflag.FlagSet) { "Show all available flags.") flags.String("color", "auto", "One of 'auto', 'never' or 'always'.") - flags.Bool("time-hack-ago", false, - "always report 1m for any time duration") + flags.Bool("test-hack", false, + "helper flag for tests, e.g. always report 1m for any time duration, 1.1.1.1 for IPs, etc.") } func isBoolConfigExplicitlySetToTrue(key string) bool { diff --git a/cmd/main_test.go b/cmd/main_test.go index cf94d23..4495aa7 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -2,40 +2,68 @@ package main import ( "bytes" + "context" "os" "os/exec" "path" "path/filepath" + "regexp" "strings" "testing" "github.com/spf13/viper" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" "k8s.io/klog/v2" "github.com/bergerx/kubectl-status/pkg/plugin" ) type cmdTest struct { - name string - args []string - stdoutRegex string // Regex - stdoutEqual string // Regex - stderrRegex string // Regex - stderrEqual string // Regex - wantErr string // Contains + name string + args []string + stdoutRegex string // Regex + stdoutRegexPath string // Regex match against file contents under test folder + stdoutEqual string // Exact + stdoutEqualPath string // Exact match with file contents under test folder + stderrRegex string // Regex + stderrEqual string // Exact + wantErr string // Contains } -func (c cmdTest) assert(t *testing.T) { +func nodeNameModifier(stdout string) string { + return string(regexp.MustCompile(`Node/[a-z0-9-]+`).ReplaceAll([]byte(stdout), []byte(`Node/minikube`))) +} + +func (c cmdTest) assert(t *testing.T, stdoutModifier func(string) string) { t.Helper() + t.Logf("running cmdTest assert: %s", c) stdout, stderr, err := executeCMD(t, c.args) + if stdoutModifier != nil { + stdout = nodeNameModifier(stdout) + } switch { - case c.stdoutRegex == "" && c.stdoutEqual == "": + case c.stdoutRegex == "" && c.stdoutEqual == "" && c.stdoutRegexPath == "" && c.stdoutEqualPath == "": assert.Empty(t, stdout) case c.stdoutRegex != "": assert.Regexp(t, c.stdoutRegex, stdout) case c.stdoutEqual != "": assert.Equal(t, c.stdoutEqual, stdout) + case c.stdoutEqualPath != "": + outFile := path.Join("..", "tests", c.stdoutEqualPath) + out, err := os.ReadFile(outFile) + assert.NoErrorf(t, err, "failed to read test artifact file: %s", outFile) + assert.Equal(t, string(out), stdout) + case c.stdoutRegexPath != "": + outFile := path.Join("..", "tests", c.stdoutRegexPath) + regexBytes, err := os.ReadFile(outFile) + assert.NoErrorf(t, err, "failed to read test artifact file: %s", outFile) + regex := `(?ms)` + string(regexBytes) + assert.Regexp(t, regex, stdout) } switch { case c.stderrRegex == "" && c.stderrEqual == "": @@ -51,7 +79,7 @@ func (c cmdTest) assert(t *testing.T) { } func TestRootCmdWithoutACluster(t *testing.T) { - _ = os.Setenv("KUBECONFIG", "/dev/null") + t.Setenv("KUBECONFIG", "/dev/null") defer plugin.SetDurationRound(func(_ interface{}) string { return "1m" })() tests := []cmdTest{ { @@ -61,7 +89,7 @@ func TestRootCmdWithoutACluster(t *testing.T) { { name: "pods against a non-configured client should print an error", args: []string{"pods"}, - stderrRegex: `The connection to the server localhost:8080 was refused`, + stderrRegex: `the server rejected our request for an unknown reason`, }, { name: "missing file should fail", @@ -71,7 +99,7 @@ func TestRootCmdWithoutACluster(t *testing.T) { { name: "file without local should fail", args: []string{"-f", "../tests/artifacts/deployment-healthy.yaml"}, - stderrRegex: `dial tcp \[::1\]:8080: connect: connection refused\n$`, + stderrRegex: `the server rejected our request for an unknown reason\n$`, }, { name: "file with local should succeed", @@ -102,19 +130,14 @@ $`, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.assert(t) + tt.assert(t, nil) }) } } func TestE2EAgainstVanillaMinikube(t *testing.T) { - if os.Getenv("RUN_E2E_TESTS") != "true" { - t.Skip("Skipping e2e test") - } - if os.Getenv("ASSUME_MINIKUBE_IS_CONFIGURED") != "true" { - defer startMinikube(t, "kubectl-status-e2e")() - } - defer plugin.SetDurationRound(func(_ interface{}) string { return "1m" })() + e2eMinikubeTest(t) + testHack(t) klog.InitFlags(nil) t.Log("starting tests...") tests := []cmdTest{ @@ -145,27 +168,44 @@ func TestE2EAgainstVanillaMinikube(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.assert(t) + viperTestHack(t) + tt.assert(t, nil) }) } } -func TestAllArtifacts(t *testing.T) { - defer plugin.SetDurationRound(func(_ interface{}) string { return "1m" })() - _ = os.Setenv("KUBECONFIG", "/dev/null") - viper.Set("test", true) +func testHack(t *testing.T) { + t.Helper() + durationRevert := plugin.SetDurationRound(func(_ interface{}) string { return "1m" }) + t.Cleanup(func() { + durationRevert() + }) +} + +func viperTestHack(t *testing.T) { + t.Helper() + viper.Reset() + viper.Set("test-hack", true) + t.Cleanup(func() { + viper.Reset() + }) +} + +func TestAllArtifactsLocal(t *testing.T) { + t.Setenv("KUBECONFIG", "/dev/null") + testHack(t) + viperTestHack(t) artifacts, err := filepath.Glob("../tests/artifacts/*.yaml") assert.NoError(t, err) for _, artifact := range artifacts { - t.Run(strings.Replace(artifact, "../", "", -1), func(t *testing.T) { - outFile := strings.Replace(artifact, ".yaml", ".out", -1) - out, err := os.ReadFile(outFile) - assert.NoError(t, err) + name := strings.Replace(artifact, "../tests/", "", 1) + name = strings.Replace(name, ".yaml", "", 1) + t.Run(name, func(t *testing.T) { test := cmdTest{ - args: []string{"-f", artifact, "--local", "--shallow"}, - stdoutEqual: string(out), + args: []string{"-f", artifact, "--local", "--shallow", "--v", "255"}, + stdoutEqualPath: name + ".out", } - test.assert(t) // to update the out files check /tests/artifacts/README.md + test.assert(t, nil) // to update the out files check /tests/artifacts/README.md }) } } @@ -183,25 +223,139 @@ func executeCMD(t *testing.T, args []string) (string, string, error) { return stdout.String(), stderr.String(), err } -func startMinikube(t *testing.T, clusterName string) (deleteMinikube func()) { +func startMinikube(t *testing.T) { t.Helper() - t.Log("Creating temp folder for minikube.kubeconfig...") + clusterName := t.Name() + t.Logf("Creating temp folder for minikube.kubeconfig for minikube %s ...", clusterName) dir, err := os.MkdirTemp("", clusterName) - assert.NoError(t, err) + require.NoError(t, err) kubeconfig := path.Join(dir, "minikube.kubeconfig") t.Setenv("KUBECONFIG", kubeconfig) - t.Log("Starting Minikube cluster...") + t.Logf("Starting Minikube cluster %s with %s ...", clusterName, kubeconfig) startMinikube := exec.Command("minikube", "start", "-p", clusterName) - assert.NoError(t, startMinikube.Run()) - return func() { + require.NoError(t, startMinikube.Run()) + t.Cleanup(func() { cmd := exec.Command("minikube", "delete", "-p", clusterName) - t.Log("Deleting Minikube cluster...") + t.Logf("Deleting Minikube cluster %s...", clusterName) if err := cmd.Run(); err != nil { t.Log("Error deleting Minikube cluster:", err) } - t.Log("Deleting temp folder of minikube.kubeconfig...") + t.Logf("Deleting temp folder for minikube %s: %s ...", clusterName, dir) if err := os.RemoveAll(dir); err != nil { t.Log("Error deleting temp folder of minikube.kubeconfig:", err) } + }) +} + +func e2eMinikubeTest(t *testing.T) { + t.Helper() + if os.Getenv("RUN_E2E_TESTS") != "true" { + t.Skip("Skipping e2e test as RUN_E2E_TESTS is not set to true") + } + if os.Getenv("ASSUME_MINIKUBE_IS_CONFIGURED") == "true" { + t.Logf("assuming current kubeconfig context is pointng a minikube to run e2e tests") + } else { + startMinikube(t) + } +} + +func TestE2EDynamicManifests(t *testing.T) { + e2eMinikubeTest(t) + testHack(t) + kubeconfigPath := os.Getenv("KUBECONFIG") + if kubeconfigPath == "" { + homeDir := os.Getenv("HOME") + kubeconfigPath = filepath.Join(homeDir, ".kube", "config") } + config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) + if err != nil { + t.Fatal(err) + } + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + t.Fatal(err) + } + t.Run("owners should be included with deep", func(t *testing.T) { + viperTestHack(t) + owner := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "owner", + Namespace: "default", + }, + } + owner, err := clientset.CoreV1().Secrets("default").Create(context.TODO(), owner, metav1.CreateOptions{}) + defer clientset.CoreV1().Secrets("default").Delete(context.TODO(), "owner", metav1.DeleteOptions{}) + require.NoError(t, err) + uid := owner.GetUID() + t.Logf("owner secret is created, uid is %s", uid) + // Create the child secret with owner reference + child := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "child", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Secret", + Name: "owner", + UID: uid, + }, + }, + }, + } + _, err = clientset.CoreV1().Secrets("default").Create(context.TODO(), child, metav1.CreateOptions{}) + t.Log("child secret is created") + defer clientset.CoreV1().Secrets("default").Delete(context.TODO(), "child", metav1.DeleteOptions{}) + require.NoError(t, err) + + test := cmdTest{ + args: []string{"secret/child", "--deep", "--v", "7"}, + stdoutRegex: `(?ms) +Secret\/child -n default, created 1m ago by Secret/owner + Current: Resource is always ready + Known\/recorded manage events: + 1m ago Updated by [^ ]+ \(metadata, type\) + Owners: + Secret\/owner -n default, created 1m ago + Current: Resource is always ready + Known\/recorded manage events: + 1m ago Updated by [^ ]+ \(type\) +`, + } + test.assert(t, nil) // to update the out files check /tests/artifacts/README.md + }) + t.Run("sts-with-ingress", func(t *testing.T) { + viperTestHack(t) + // using sts here as the pod name is predictable in that case, not true for deployments and ds + applyManifest(t, "e2e-artifacts/sts-with-ingress.yaml") + waitFor(t, "sts/sts-with-ingress", "jsonpath={.status.readyReplicas}=1") + cmdTest{ + args: []string{"pod/sts-with-ingress-0", "--include-events=false", "--v", "5"}, + stdoutEqualPath: "e2e-artifacts/sts-with-ingress.pod.out", + }.assert(t, nodeNameModifier) + }) +} + +func applyManifest(t *testing.T, filepath string) { + t.Helper() + filepath = path.Join("..", "tests", filepath) + cmd := exec.Command("kubectl", "apply", "-f", filepath) + output, err := cmd.CombinedOutput() + t.Cleanup(func() { + t.Logf("deleting manifest %s", filepath) + cmd := exec.Command("kubectl", "delete", "-f", filepath) + output, err := cmd.CombinedOutput() + assert.NoError(t, err) + t.Logf("manifest deleted %s: %s", filepath, string(output)) + }) + require.NoError(t, err) + t.Logf("applied manifest %s: %s", filepath, string(output)) +} + +func waitFor(t *testing.T, resource, forParam string) { + t.Helper() + cmd := exec.Command("kubectl", "wait", "--for", forParam, resource) + output, err := cmd.CombinedOutput() + t.Logf("wait result for %s: %s", resource, string(output)) + require.NoError(t, err) } diff --git a/go.mod b/go.mod index 61a439e..557dda6 100644 --- a/go.mod +++ b/go.mod @@ -13,12 +13,13 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 - k8s.io/api v0.30.3 - k8s.io/apimachinery v0.30.3 - k8s.io/cli-runtime v0.30.3 - k8s.io/client-go v0.30.3 + golang.org/x/sys v0.22.0 + k8s.io/api v0.31.2 + k8s.io/apimachinery v0.31.2 + k8s.io/cli-runtime v0.31.2 + k8s.io/client-go v0.31.2 k8s.io/klog/v2 v2.130.1 - k8s.io/kubectl v0.30.3 + k8s.io/kubectl v0.31.2 sigs.k8s.io/cli-utils v0.37.2 ) @@ -27,16 +28,16 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fatih/camelcase v1.0.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/fvbommel/sortorder v1.1.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-errors/errors v1.4.2 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.4 // indirect @@ -64,8 +65,8 @@ require ( github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/spdystream v0.2.0 // indirect - github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect + github.com/moby/spdystream v0.4.0 // indirect + github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect @@ -80,30 +81,29 @@ require ( github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.25.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect golang.org/x/term v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.34.2 // indirect - gopkg.in/evanphx/json-patch.v5 v5.6.0 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/component-base v0.30.3 // indirect + k8s.io/component-base v0.31.2 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/kustomize/api v0.15.0 // indirect + sigs.k8s.io/kustomize/api v0.17.2 // indirect sigs.k8s.io/kustomize/kyaml v0.17.1 // 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 5aa473a..d347034 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0 github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= @@ -32,8 +34,6 @@ github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxER github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/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/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= @@ -45,12 +45,12 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= -github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= @@ -60,7 +60,6 @@ github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogB github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-sprout/sprout v0.4.1 h1:grvsR21YepGs64EFoIXg4g+5OzIZFwmsw5Y88Wod9sI= github.com/go-sprout/sprout v0.4.1/go.mod h1:jRgO0n+24zLgiPAg/6rMaeq2oEnBSGlZiHUoK3hnQc4= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -75,8 +74,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= @@ -89,20 +86,18 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/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.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= @@ -131,6 +126,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -151,10 +148,10 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= +github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -180,8 +177,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -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/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 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/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= @@ -218,19 +215,21 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -241,7 +240,6 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -250,19 +248,16 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -271,26 +266,19 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/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-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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -303,7 +291,6 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -312,8 +299,6 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= @@ -328,15 +313,13 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/evanphx/json-patch.v5 v5.6.0 h1:BMT6KIwBD9CaU91PJCZIe46bDmBWa9ynTQgJIOpfQBk= -gopkg.in/evanphx/json-patch.v5 v5.6.0/go.mod h1:/kvTRh1TVm5wuM6OkHxqXtE/1nUZZpihg29RtuIyfvk= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= @@ -350,30 +333,30 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.30.3 h1:ImHwK9DCsPA9uoU3rVh4QHAHHK5dTSv1nxJUapx8hoQ= -k8s.io/api v0.30.3/go.mod h1:GPc8jlzoe5JG3pb0KJCSLX5oAFIW3/qNJITlDj8BH04= -k8s.io/apimachinery v0.30.3 h1:q1laaWCmrszyQuSQCfNB8cFgCuDAoPszKY4ucAjDwHc= -k8s.io/apimachinery v0.30.3/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= -k8s.io/cli-runtime v0.30.3 h1:aG69oRzJuP2Q4o8dm+f5WJIX4ZBEwrvdID0+MXyUY6k= -k8s.io/cli-runtime v0.30.3/go.mod h1:hwrrRdd9P84CXSKzhHxrOivAR9BRnkMt0OeP5mj7X30= -k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k= -k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U= -k8s.io/component-base v0.30.3 h1:Ci0UqKWf4oiwy8hr1+E3dsnliKnkMLZMVbWzeorlk7s= -k8s.io/component-base v0.30.3/go.mod h1:C1SshT3rGPCuNtBs14RmVD2xW0EhRSeLvBh7AGk1quA= +k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= +k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= +k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= +k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/cli-runtime v0.31.2 h1:7FQt4C4Xnqx8V1GJqymInK0FFsoC+fAZtbLqgXYVOLQ= +k8s.io/cli-runtime v0.31.2/go.mod h1:XROyicf+G7rQ6FQJMbeDV9jqxzkWXTYD6Uxd15noe0Q= +k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= +k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs= +k8s.io/component-base v0.31.2 h1:Z1J1LIaC0AV+nzcPRFqfK09af6bZ4D1nAOpWsy9owlA= +k8s.io/component-base v0.31.2/go.mod h1:9PeyyFN/drHjtJZMCTkSpQJS3U9OXORnHQqMLDz0sUQ= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/kubectl v0.30.3 h1:YIBBvMdTW0xcDpmrOBzcpUVsn+zOgjMYIu7kAq+yqiI= -k8s.io/kubectl v0.30.3/go.mod h1:IcR0I9RN2+zzTRUa1BzZCm4oM0NLOawE6RzlDvd1Fpo= -k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= -k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kubectl v0.31.2 h1:gTxbvRkMBwvTSAlobiTVqsH6S8Aa1aGyBcu5xYLsn8M= +k8s.io/kubectl v0.31.2/go.mod h1:EyASYVU6PY+032RrTh5ahtSOMgoDRIux9V1JLKtG5xM= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/cli-utils v0.37.2 h1:GOfKw5RV2HDQZDJlru5KkfLO1tbxqMoyn1IYUxqBpNg= sigs.k8s.io/cli-utils v0.37.2/go.mod h1:V+IZZr4UoGj7gMJXklWBg6t5xbdThFBcpj4MrZuCYco= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.15.0 h1:6Ca88kEOBVotHDw+y2IsIMYtg9Pvv7MKpW9JMyF/OH4= -sigs.k8s.io/kustomize/api v0.15.0/go.mod h1:p19kb+E14gN7zcIBR/nhByJDAfUa7N8mp6ZdH/mMXbg= +sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= +sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0= sigs.k8s.io/kustomize/kyaml v0.17.1 h1:TnxYQxFXzbmNG6gOINgGWQt09GghzgTP6mIurOgrLCQ= sigs.k8s.io/kustomize/kyaml v0.17.1/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/pkg/input/input.go b/pkg/input/input.go index c7961a4..048e0e3 100644 --- a/pkg/input/input.go +++ b/pkg/input/input.go @@ -1,23 +1,79 @@ package input import ( + "context" + "fmt" + "sort" + "strings" + "github.com/spf13/viper" + corev1 "k8s.io/api/core/v1" + netv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/json" "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/klog/v2" + "k8s.io/kubectl/pkg/cmd/events" "k8s.io/kubectl/pkg/cmd/util" ) -type ResourceRepo struct { - f util.Factory +// Object is the JSON compatible map[string]interface{} mostly used through unstructured.Unstructured. +type Object map[string]interface{} + +func (u Object) creationTimestamp() string { + m, ok := u["metadata"].(map[string]string) + if !ok { + return "" + } + return m["creationTimestamp"] +} + +func (u Object) Unstructured() *unstructured.Unstructured { + return &unstructured.Unstructured{Object: u} +} + +type Objects []Object + +func (u Objects) Len() int { + return len(u) +} + +func (u Objects) Less(i, j int) bool { + return u[i].creationTimestamp() < u[j].creationTimestamp() } -func NewResourceRepo(factory util.Factory) ResourceRepo { +func (u Objects) Swap(i, j int) { + u[i], u[j] = u[j], u[i] +} - return ResourceRepo{ - f: factory, +func NewResourceRepo(factory util.Factory) (*ResourceRepo, error) { + dynamicClient, err := factory.DynamicClient() + if err != nil { + return nil, err } + kubernetesClientSet, err := factory.KubernetesClientSet() + if err != nil { + return nil, err + } + return &ResourceRepo{ + f: factory, + dynamicClient: dynamicClient, + kubernetesClientSet: kubernetesClientSet, + }, nil +} + +type ResourceRepo struct { + f util.Factory + dynamicClient dynamic.Interface + kubernetesClientSet *kubernetes.Clientset } func (r *ResourceRepo) newBaseBuilder() *resource.Builder { @@ -57,22 +113,233 @@ func (r *ResourceRepo) CLIQueryResults(args []string) *resource.Result { return builder.Do() } -func (r *ResourceRepo) ResourceInfos(namespace string, args []string, labelSelector string) ([]*resource.Info, error) { +func (r *ResourceRepo) Objects(namespace string, args []string, labelSelector string) (Objects, error) { builder := r.newBaseBuilder(). NamespaceParam(namespace). ResourceTypeOrNameArgs(true, args...). LabelSelectorParam(labelSelector) - return builder.Do().Infos() + infos, err := builder.Do().Infos() + unstructuredObjects := Objects{} + for _, info := range infos { + unstructuredObj, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(info.Object) // TODO: handle error + unstructuredObjects = append(unstructuredObjects, unstructuredObj) + } + sort.Sort(unstructuredObjects) + return unstructuredObjects, err +} + +func (r *ResourceRepo) Owners(obj Object) (out Objects, err error) { + uobj := obj.Unstructured() + namespace := uobj.GetNamespace() + owners := uobj.GetOwnerReferences() + if len(owners) == 0 { + klog.V(4).InfoS("KubeGetOwners Object has no owners", "r", r) + return nil, fmt.Errorf("Object has no owners: %s", obj) + } + for _, owner := range owners { + gv, err := schema.ParseGroupVersion(owner.APIVersion) + var kindVersionGroup string + if err != nil { + klog.V(3).InfoS("repo.Owners failed parsing apiVersion", "apiVersion", owner.APIVersion) + kindVersionGroup = owner.Kind + object, err := r.FirstObject(namespace, []string{kindVersionGroup}, owner.Name) + if err != nil { + klog.V(3).InfoS("repo.Owners failed to get owner using Kind", "apiVersion", owner.APIVersion) + continue + } + out = append(out, object) + continue + } + if gv.Group == "" && gv.Version != "v1" { + kindVersionGroup = fmt.Sprintf("%s.%s", owner.Kind, gv.Version) + klog.V(5).InfoS("repo.Owners", "kindVersionGroup", kindVersionGroup, "gv", gv) + ownerWithVersion, err := r.FirstObject(namespace, []string{kindVersionGroup, owner.Name}, "") + if err != nil { + klog.V(3).InfoS("repo.Owners failed to get owner using kind+version", "apiVersion", owner.APIVersion) + continue + } + if ownerWithVersion == nil { + // it's likely the ownerReference.apiVersion field doesn't have the group prefix, so we'll try without the version + ownerWithVersion, err = r.FirstObject(namespace, []string{owner.Kind, owner.Name}, "") + if err != nil { + klog.V(3).InfoS("repo.Owners failed to get owner using kind+version", "apiVersion", owner.APIVersion) + continue + } + } + out = append(out, ownerWithVersion) + continue + } + kindVersionGroup = fmt.Sprintf("%s.%s.%s", owner.Kind, gv.Version, gv.Group) + klog.V(5).InfoS("repo.Owners", "kindVersionGroup", kindVersionGroup) + object, err := r.FirstObject(namespace, []string{kindVersionGroup, owner.Name}, "") + if err != nil { + klog.V(3).InfoS("repo.Owners failed to get owner using kind+version+group", "apiVersion", owner.APIVersion) + continue + } + out = append(out, object) + } + return out, nil +} + +func (r *ResourceRepo) FirstObject(namespace string, args []string, labelSelector string) (Object, error) { + objects, err := r.Objects(namespace, args, labelSelector) + if err != nil { + return nil, err + } + if len(objects) == 0 { + return nil, fmt.Errorf("no objects found in namespace %s for '%s'", namespace, strings.Join(args, " ")) + } + return objects[0], err +} + +func (r *ResourceRepo) ObjectEvents(u *unstructured.Unstructured) (*corev1.EventList, error) { + eventList, err := r.kubernetesClientSet.CoreV1().Events(u.GetNamespace()).Search(scheme.Scheme, u) + if err != nil { + klog.V(3).ErrorS(err, "error getting events", "r", r) + return nil, err + } + sort.Sort(events.SortableEvents(eventList.Items)) + return eventList, nil } -func (r *ResourceRepo) ToRESTMapper() (meta.RESTMapper, error) { - return r.f.ToRESTMapper() +func (r *ResourceRepo) DynamicObject(gvr schema.GroupVersionResource, namespace string, name string) (Object, error) { + u, err := r.dynamicClient.Resource(gvr).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + object, err := runtime.DefaultUnstructuredConverter.ToUnstructured(u.Object) + if err != nil { + return nil, err + } + return object, nil } -func (r *ResourceRepo) KubernetesClientSet() (*kubernetes.Clientset, error) { - return r.f.KubernetesClientSet() +func (r *ResourceRepo) DynamicObjects(gvr schema.GroupVersionResource, namespace string) (Objects, error) { + unstructuredList, err := r.dynamicClient.Resource(gvr).Namespace(namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, err + } + var objects Objects + for _, unstructuredObj := range unstructuredList.Items { + unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(unstructuredObj.Object) + if err != nil { + return nil, err + } + objects = append(objects, unstructuredObj) + } + return objects, nil } -func (r *ResourceRepo) DynamicClient() (dynamic.Interface, error) { - return r.f.DynamicClient() +func (r *ResourceRepo) GVRFor(resourceOrKindArg string) (schema.GroupVersionResource, error) { + mapping, err := r.mappingFor(resourceOrKindArg) + if err != nil { + return schema.GroupVersionResource{}, err + } + return mapping.Resource, nil +} + +// This is a modified copy of resource.Builder's mappingFor method. +func (r *ResourceRepo) mappingFor(resourceOrKindArg string) (*meta.RESTMapping, error) { + fullySpecifiedGVR, groupResource := schema.ParseResourceArg(resourceOrKindArg) + gvk := schema.GroupVersionKind{} + restMapper, err := r.f.ToRESTMapper() + if err != nil { + return nil, err + } + + if fullySpecifiedGVR != nil { + gvk, _ = restMapper.KindFor(*fullySpecifiedGVR) + } + if gvk.Empty() { + gvk, _ = restMapper.KindFor(groupResource.WithVersion("")) + } + if !gvk.Empty() { + return restMapper.RESTMapping(gvk.GroupKind(), gvk.Version) + } + + fullySpecifiedGVK, groupKind := schema.ParseKindArg(resourceOrKindArg) + if fullySpecifiedGVK == nil { + gvk := groupKind.WithVersion("") + fullySpecifiedGVK = &gvk + } + + if !fullySpecifiedGVK.Empty() { + if mapping, err := restMapper.RESTMapping(fullySpecifiedGVK.GroupKind(), fullySpecifiedGVK.Version); err == nil { + return mapping, nil + } + } + + mapping, err := restMapper.RESTMapping(groupKind, gvk.Version) + if err != nil { + // if we error out here, it is because we could not match a resource or a kind + // for the given argument. To maintain consistency with previous behavior, + // announce that a resource type could not be found. + // if the error is _not_ a *meta.NoKindMatchError, then we had trouble doing discovery, + // so we should return the original error since it may help a user diagnose what is actually wrong + if meta.IsNoMatchError(err) { + return nil, fmt.Errorf("the server doesn't have a resource type %q", groupResource.Resource) + } + return nil, err + } + + return mapping, nil +} + +func (r *ResourceRepo) Ingresses(namespace string) (*netv1.IngressList, error) { + return r.kubernetesClientSet.NetworkingV1().Ingresses(namespace).List(context.TODO(), metav1.ListOptions{}) +} + +func (r *ResourceRepo) Services(namespace string) (*corev1.ServiceList, error) { + return r.kubernetesClientSet.CoreV1().Services(namespace).List(context.TODO(), metav1.ListOptions{}) +} + +func (r *ResourceRepo) Service(namespace, name string) (*corev1.Service, error) { + return r.kubernetesClientSet.CoreV1().Services(namespace).Get(context.TODO(), name, metav1.GetOptions{}) +} + +func (r *ResourceRepo) Endpoints(namespace string) (*corev1.EndpointsList, error) { + return r.kubernetesClientSet.CoreV1().Endpoints(namespace).List(context.TODO(), metav1.ListOptions{}) +} + +// KubeGetNodeStatsSummary returns this structure +// > kubectl get --raw /api/v1/nodes/{nodeName}/proxy/stats/summary +// The endpoint that this function uses will be disabled soon: https://github.com/kubernetes/kubernetes/issues/68522 +func (r *ResourceRepo) KubeGetNodeStatsSummary(nodeName string) (Object, error) { + getBytes, err := r.kubernetesClientSet.CoreV1().RESTClient().Get(). + Resource("nodes"). + SubResource("proxy"). + Name(nodeName). + Suffix("stats/summary"). + DoRaw(context.TODO()) + if err != nil { + return nil, err + } + nodeStatsSummary := make(Object) + err = json.Unmarshal(getBytes, &nodeStatsSummary) + return nodeStatsSummary, err +} + +func (r *ResourceRepo) NonTerminatedPodsOnTheNode(nodeName string) (Objects, error) { + fieldSelector, err := fields.ParseSelector("spec.nodeName=" + nodeName + + ",status.phase!=" + string(corev1.PodSucceeded) + + ",status.phase!=" + string(corev1.PodFailed)) + if err != nil { + klog.V(3).ErrorS(err, "Failed creating fieldSelector for non-terminated Pods on Node", + "r", r, "nodeName", nodeName) + return nil, err + } + nodeNonTerminatedPodsList, err := r.kubernetesClientSet.CoreV1(). + Pods(""). // Search in all namespaces + List(context.TODO(), metav1.ListOptions{FieldSelector: fieldSelector.String()}) + if err != nil { + klog.V(3).ErrorS(err, "Failed getting non-terminated Pods for Node", + "r", r, "nodeName", nodeName) + return nil, err + } + pods := Objects{} + for _, pod := range nodeNonTerminatedPodsList.Items { + unstructuredPod, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(&pod) + pods = append(pods, unstructuredPod) + } + return pods, nil } diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index 43a8c59..6bd0717 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -17,10 +17,9 @@ import ( "k8s.io/klog/v2" "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/util/interrupt" -) -//go:linkname signame runtime.signame -func signame(sig uint32) string + "github.com/bergerx/kubectl-status/pkg/input" +) func errorPrintf(wr io.Writer, format string, a ...interface{}) { _, _ = color.New(color.BgRed, color.FgHiWhite).Printf(format, a...) @@ -34,18 +33,23 @@ func Run(f util.Factory, streams genericiooptions.IOStreams, args []string) erro } else if viper.Get("color") == "never" { color.NoColor = true } - engine, err := newRenderEngine(f, streams) + repo, err := input.NewResourceRepo(f) + if err != nil { + klog.V(2).ErrorS(err, "Error creating repo") + return err + } + engine, err := newRenderEngine(streams) if err != nil { klog.V(2).ErrorS(err, "Error creating engine") return err } klog.V(5).InfoS("Created engine", "engine", engine) - results := engine.repo.CLIQueryResults(args) + results := repo.CLIQueryResults(args) count := 0 err = results.Visit(func(resourceInfo *resource.Info, err error) error { count += 1 klog.V(5).InfoS("Processing resource", "item", count, "resource", resourceInfo) - processObj(resourceInfo.Object, engine) + processObj(resourceInfo.Object, engine, repo) return err }) klog.V(5).InfoS("Processed matching resources", "count", count) @@ -58,12 +62,12 @@ func Run(f util.Factory, streams genericiooptions.IOStreams, args []string) erro return fmt.Errorf("no resources found") } if viper.GetBool("watch") { - return runWatch(results, engine) + return runWatch(results, engine, repo) } return nil } -func runWatch(results *resource.Result, engine *renderEngine) error { +func runWatch(results *resource.Result, engine *renderEngine, repo *input.ResourceRepo) error { color.HiYellow("\nPrinted all existing resource statuses, starting to watch. Switching to shallow mode during watch!\n\n") viper.Set("shallow", true) viper.Set("watching", true) @@ -90,7 +94,7 @@ func runWatch(results *resource.Result, engine *renderEngine) error { _ = intr.Run(func() error { _, err := watchtools.UntilWithoutRetry(ctx, w, func(e watch.Event) (bool, error) { klog.V(5).InfoS("Processing watch event", "e", e) - processObj(e.Object, engine) + processObj(e.Object, engine, repo) return false, nil }) klog.V(1).ErrorS(err, "Watch failed", "obj", obj) @@ -99,7 +103,7 @@ func runWatch(results *resource.Result, engine *renderEngine) error { return nil } -func processObj(obj runtime.Object, engine *renderEngine) { +func processObj(obj runtime.Object, engine *renderEngine, repo *input.ResourceRepo) { streams := engine.ioStreams _, _ = fmt.Fprintf(streams.Out, "\n") out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) @@ -107,7 +111,7 @@ func processObj(obj runtime.Object, engine *renderEngine) { errorPrintf(streams.ErrOut, "Failed to decode obj=%s: %s", obj, err) return } - r := newRenderableObject(out, *engine) + r := newRenderableObject(out, engine, repo) err = r.render(streams.Out) if err != nil { _, _ = fmt.Fprintf(streams.ErrOut, "\n") diff --git a/pkg/plugin/render_engine.go b/pkg/plugin/render_engine.go index 0e66f48..b02c823 100644 --- a/pkg/plugin/render_engine.go +++ b/pkg/plugin/render_engine.go @@ -2,20 +2,13 @@ package plugin import ( "embed" - "fmt" "os" "path/filepath" "text/template" "github.com/go-sprout/sprout" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/cli-runtime/pkg/genericiooptions" - "k8s.io/cli-runtime/pkg/resource" "k8s.io/klog/v2" - "k8s.io/kubectl/pkg/cmd/util" - - "github.com/bergerx/kubectl-status/pkg/input" ) //go:embed templates @@ -25,21 +18,18 @@ var templatesFS embed.FS // Also holds the parsed templates. type renderEngine struct { ioStreams genericiooptions.IOStreams - repo input.ResourceRepo template.Template } -func newRenderEngine(f util.Factory, streams genericiooptions.IOStreams) (*renderEngine, error) { - klog.V(5).InfoS("Creating new render engine instance...", "f", f) +func newRenderEngine(streams genericiooptions.IOStreams) (*renderEngine, error) { + klog.V(5).InfoS("Creating new render engine instance...") tmpl, err := getTemplate() - repo := input.NewResourceRepo(f) if err != nil { klog.V(3).ErrorS(err, "Error parsing templates") return nil, err } return &renderEngine{ streams, - repo, *tmpl, }, nil } @@ -84,55 +74,3 @@ func findTemplateName(tmpl template.Template, kind string) string { } return kind } - -// This is a modified copy of resource.Builder's mappingFor method. -func (e *renderEngine) mappingFor(resourceOrKindArg string) (*meta.RESTMapping, error) { - fullySpecifiedGVR, groupResource := schema.ParseResourceArg(resourceOrKindArg) - gvk := schema.GroupVersionKind{} - restMapper, err := e.repo.ToRESTMapper() - if err != nil { - return nil, err - } - - if fullySpecifiedGVR != nil { - gvk, _ = restMapper.KindFor(*fullySpecifiedGVR) - } - if gvk.Empty() { - gvk, _ = restMapper.KindFor(groupResource.WithVersion("")) - } - if !gvk.Empty() { - return restMapper.RESTMapping(gvk.GroupKind(), gvk.Version) - } - - fullySpecifiedGVK, groupKind := schema.ParseKindArg(resourceOrKindArg) - if fullySpecifiedGVK == nil { - gvk := groupKind.WithVersion("") - fullySpecifiedGVK = &gvk - } - - if !fullySpecifiedGVK.Empty() { - if mapping, err := restMapper.RESTMapping(fullySpecifiedGVK.GroupKind(), fullySpecifiedGVK.Version); err == nil { - return mapping, nil - } - } - - mapping, err := restMapper.RESTMapping(groupKind, gvk.Version) - if err != nil { - // if we error out here, it is because we could not match a resource or a kind - // for the given argument. To maintain consistency with previous behavior, - // announce that a resource type could not be found. - // if the error is _not_ a *meta.NoKindMatchError, then we had trouble doing discovery, - // so we should return the original error since it may help a user diagnose what is actually wrong - if meta.IsNoMatchError(err) { - return nil, fmt.Errorf("the server doesn't have a resource type %q", groupResource.Resource) - } - return nil, err - } - - return mapping, nil -} - -func (e renderEngine) getResourceQueryInfos(namespace string, args []string) ([]*resource.Info, error) { - klog.V(5).InfoS("getResourceQueryInfos", "namespace", namespace, "args", args) - return e.repo.ResourceInfos(namespace, args, "") -} diff --git a/pkg/plugin/renderable.go b/pkg/plugin/renderable.go index 380df27..0aaca90 100644 --- a/pkg/plugin/renderable.go +++ b/pkg/plugin/renderable.go @@ -11,12 +11,15 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status" + + "github.com/bergerx/kubectl-status/pkg/input" ) -func newRenderableObject(obj map[string]interface{}, engine renderEngine) RenderableObject { +func newRenderableObject(obj map[string]interface{}, engine *renderEngine, repo *input.ResourceRepo) RenderableObject { r := RenderableObject{ Unstructured: unstructured.Unstructured{Object: obj}, - engine: &engine, + engine: engine, + repo: repo, Config: viper.GetViper(), } return r @@ -28,6 +31,7 @@ func newRenderableObject(obj map[string]interface{}, engine renderEngine) Render type RenderableObject struct { unstructured.Unstructured engine *renderEngine + repo *input.ResourceRepo Config *viper.Viper } @@ -41,7 +45,7 @@ func (r RenderableObject) KStatus() *kstatus.Result { } func (r RenderableObject) newRenderableObject(obj map[string]interface{}) RenderableObject { - return newRenderableObject(obj, *r.engine) + return newRenderableObject(obj, r.engine, r.repo) } func (r RenderableObject) String() string { @@ -141,7 +145,7 @@ func (r RenderableObject) renderTemplate(templateName string, data interface{}) func (r RenderableObject) executeTemplate(wr io.Writer, name string, data any) error { target, ok := data.(RenderableObject) - if ok && target.Kind() == name && renderedUIDs.checkAdd(target.GetUID()) && !viper.GetBool("watching") && !viper.GetBool("test") { + if ok && target.Kind() == name && renderedUIDs.checkAdd(target.GetUID()) && !viper.GetBool("watching") && !viper.GetBool("test-hack") { klog.V(3).InfoS("skip rendering of the RenderableObject as its already rendered", "r", r, "templateName", name) _, _ = color.New(color.FgWhite).Fprintf(wr, "%s is already printed", target.String()) diff --git a/pkg/plugin/template_functions_dynamic.go b/pkg/plugin/template_functions_dynamic.go index 50dadc7..7fba2fd 100644 --- a/pkg/plugin/template_functions_dynamic.go +++ b/pkg/plugin/template_functions_dynamic.go @@ -3,10 +3,8 @@ package plugin import ( - "context" "encoding/json" "fmt" - "sort" "strings" "time" @@ -16,16 +14,12 @@ import ( netv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/cli-runtime/pkg/resource" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes/scheme" "k8s.io/klog/v2" - "k8s.io/kubectl/pkg/cmd/events" - "k8s.io/kubectl/pkg/cmd/get" "k8s.io/kubectl/pkg/polymorphichelpers" + + "github.com/bergerx/kubectl-status/pkg/input" ) func (r RenderableObject) KubeGet(namespace string, args ...string) (out []RenderableObject) { @@ -33,33 +27,21 @@ func (r RenderableObject) KubeGet(namespace string, args ...string) (out []Rende return } klog.V(5).InfoS("processing KubeGet", "r", r, "namespace", namespace, "args", args) - resourceInfos, err := r.engine.getResourceQueryInfos(namespace, args) + objects, err := r.repo.Objects(namespace, args, "") if err != nil { klog.V(3).ErrorS(err, "ignoring resource error", "r", r, "namespace", namespace, "args", args) } - return r.getCreationTimestampSortedRenderableObjects(resourceInfos) + return r.objectsToRenderableObjects(objects) } -func (r RenderableObject) getCreationTimestampSortedRenderableObjects(resourceInfos []*resource.Info) []RenderableObject { - var out []RenderableObject - for _, obj := range sortedResourceInfosToRuntimeObjects(resourceInfos) { - unstructuredObj, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) - nr := r.newRenderableObject(unstructuredObj) - klog.V(5).InfoS("KubeGet matched object", "object", nr) +func (r RenderableObject) objectsToRenderableObjects(objects input.Objects) (out []RenderableObject) { + for _, obj := range objects { + nr := r.newRenderableObject(obj) out = append(out, nr) } return out } -func sortedResourceInfosToRuntimeObjects(resourceInfos []*resource.Info) []runtime.Object { - runtimeObjList := make([]runtime.Object, len(resourceInfos)) - for i := range resourceInfos { - runtimeObjList[i] = resourceInfos[i].Object - } - _ = get.NewRuntimeSorter(runtimeObjList, ".metadata.creationTimestamp").Sort() - return runtimeObjList -} - // KubeGetFirst returns a new RenderableObject with a nil Object when no object found. func (r RenderableObject) KubeGetFirst(namespace string, args ...string) RenderableObject { nr := r.newRenderableObject(nil) @@ -67,20 +49,12 @@ func (r RenderableObject) KubeGetFirst(namespace string, args ...string) Rendera return nr } klog.V(5).InfoS("called template method KubeGetFirst", - "r", r, "namespace", namespace, "args", args) - resourceInfos, err := r.engine.getResourceQueryInfos(namespace, args) + "namespace", namespace, "args", args) + var err error + nr.Object, err = r.repo.FirstObject(namespace, args, "") if err != nil { - klog.V(3).ErrorS(err, "getResourceQueryInfos failed", - "r", r, "namespace", namespace, "args", args) - return nr - } - if len(resourceInfos) >= 1 { - first := resourceInfos[0] - unstructuredObj, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(first.Object) - nr.Object = unstructuredObj - } else { - klog.V(3).InfoS("KubeGetFirst returning empty", - "r", r, "namespace", namespace, "args", args) + klog.V(3).ErrorS(err, "KubeGetFirst failed", + "namespace", namespace, "args", args) } return nr } @@ -99,13 +73,13 @@ func (r RenderableObject) KubeGetByLabelsMap(namespace, resourceType string, lab labelPairs = append(labelPairs, fmt.Sprintf("%s=%s", k, v)) } selector := strings.Join(labelPairs, ",") - resourceInfos, err := r.engine.repo.ResourceInfos(namespace, []string{resourceType}, selector) + unstructuredObjects, err := r.repo.Objects(namespace, []string{resourceType}, selector) if err != nil { klog.V(3).ErrorS(err, "error querying labels", "r", r, "namespace", namespace, "labels", labels) return } - return r.getCreationTimestampSortedRenderableObjects(resourceInfos) + return r.objectsToRenderableObjects(unstructuredObjects) } func (r RenderableObject) KubeGetEvents() RenderableObject { @@ -114,83 +88,27 @@ func (r RenderableObject) KubeGetEvents() RenderableObject { return nr } klog.V(5).InfoS("called KubeGetEvents", "r", r) - clientSet, _ := r.engine.repo.KubernetesClientSet() - eventList, err := clientSet.CoreV1().Events(r.GetNamespace()).Search(scheme.Scheme, &r) + eventList, err := r.repo.ObjectEvents(&r.Unstructured) if err != nil { klog.V(3).ErrorS(err, "error getting events", "r", r) return nr } - sort.Sort(events.SortableEvents(eventList.Items)) unstructuredEvents, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(&eventList) - nr.Object = unstructuredEvents + nr.Object = unstructuredEvents // TODO: convert input.Events to return Objects return nr } -// KubeGetResourcesOwnedOf is meant to be called from templates. -// It returns a RenderableObject list for all resources which have provided kind or resource type with the current -// object listed in the ownerReferences. -func (r RenderableObject) KubeGetResourcesOwnedOf(resourceOrKind string) (out []RenderableObject) { - if viper.GetBool("shallow") { - return - } - klog.V(5).InfoS("called template method KubeGetResourcesOwnedOf", "r", r) - restMapper, _ := r.engine.mappingFor(resourceOrKind) - dynamicInterface, _ := r.engine.repo.DynamicClient() - controllerRevisions, _ := dynamicInterface. - Resource(restMapper.Resource). - Namespace(r.GetNamespace()). - List(context.TODO(), metav1.ListOptions{}) - for _, controllerRevision := range controllerRevisions.Items { - if doesOwnerMatch(r.Unstructured, controllerRevision) { - out = append(out, r.newRenderableObject(controllerRevision.Object)) - } - } - return -} - -func doesOwnerMatch(owner, owned unstructured.Unstructured) bool { - for _, ownerReference := range owner.GetOwnerReferences() { - if ownerReference.UID == owned.GetUID() { - return true - } - } - return false -} - // KubeGetOwners returns the list of objects which are listed in the Owner references of an object. func (r RenderableObject) KubeGetOwners() (out []RenderableObject) { if viper.GetBool("shallow") { return } klog.V(5).InfoS("KubeGetOwners called KubeGetOwners", "r", r) - owners := r.GetOwnerReferences() - if len(owners) == 0 { - klog.V(4).InfoS("KubeGetOwners Object has no owners", "r", r) - return - } - for _, owner := range owners { - gv, err := schema.ParseGroupVersion(owner.APIVersion) - var kindVersionGroup string - if err != nil { - klog.V(3).InfoS("KubeGetOwners failed parsing apiVersion", "apiVersion", owner.APIVersion) - kindVersionGroup = owner.Kind - out = append(out, r.KubeGetFirst(r.Namespace(), kindVersionGroup, owner.Name)) - } else if gv.Group == "" && gv.Version != "v1" { - kindVersionGroup = fmt.Sprintf("%s.%s", owner.Kind, gv.Version) - klog.V(5).InfoS("KubeGetOwners", "kindVersionGroup", kindVersionGroup, "gv", gv) - ownerWithVersion := r.KubeGetFirst(r.Namespace(), kindVersionGroup, owner.Name) - if ownerWithVersion.Object == nil { - // its likely the ownerReference.apiVersion field doesn't have the group prefix, so we'll try without the version - ownerWithVersion = r.KubeGetFirst(r.Namespace(), owner.Kind, owner.Name) - } - out = append(out, ownerWithVersion) - } else { - kindVersionGroup = fmt.Sprintf("%s.%s.%s", owner.Kind, gv.Version, gv.Group) - klog.V(5).InfoS("KubeGetOwners", "kindVersionGroup", kindVersionGroup) - out = append(out, r.KubeGetFirst(r.Namespace(), kindVersionGroup, owner.Name)) - } + owners, err := r.repo.Owners(r.Object) + if err != nil { + klog.V(3).ErrorS(err, "error getting owners", "r", r) } - return out + return r.objectsToRenderableObjects(owners) } func (r RenderableObject) KubeGetIngressesMatchingService(namespace, svcName string) (out []RenderableObject) { @@ -199,9 +117,7 @@ func (r RenderableObject) KubeGetIngressesMatchingService(namespace, svcName str } klog.V(5).InfoS("called KubeGetIngressesMatchingService", "r", r, "namespace", namespace, "svcName", svcName) - clientSet, _ := r.engine.repo.KubernetesClientSet() - // The old v1beta1 Ingress which will no longer served as of v1.22. Not implementing it. - ingresses, err := clientSet.NetworkingV1().Ingresses(namespace).List(context.TODO(), metav1.ListOptions{}) + ingresses, err := r.repo.Ingresses(namespace) if err != nil { klog.V(3).ErrorS(err, "error listing ingresses", "r", r, "namespace", namespace) return @@ -240,8 +156,7 @@ func (r RenderableObject) KubeGetServicesMatchingLabels(namespace string, labels castedLabels[k] = v.(string) } klog.V(5).InfoS("casted labels values into string", "r", r, "castedLabels", castedLabels) - clientSet, _ := r.engine.repo.KubernetesClientSet() - svcs, err := clientSet.CoreV1().Services(r.Namespace()).List(context.TODO(), metav1.ListOptions{}) + svcs, err := r.repo.Services(r.Namespace()) if err != nil { klog.V(3).ErrorS(err, "error listing services", "r", r, "namespace", namespace) return out @@ -255,14 +170,14 @@ func (r RenderableObject) KubeGetServicesMatchingLabels(namespace string, labels } return out } + func (r RenderableObject) KubeGetServicesMatchingPod(namespace, podName string) (out []RenderableObject) { out = make([]RenderableObject, 0) if viper.GetBool("shallow") { return } klog.V(5).InfoS("called KubeGetServicesMatchingPod", "r", r, "namespace", namespace, "podName", podName) - clientSet, _ := r.engine.repo.KubernetesClientSet() - endpoints, err := clientSet.CoreV1().Endpoints(r.Namespace()).List(context.TODO(), metav1.ListOptions{}) + endpoints, err := r.repo.Endpoints(r.Namespace()) if err != nil { klog.V(3).ErrorS(err, "error listing endpoints", "r", r, "namespace", namespace) return @@ -277,7 +192,7 @@ func (r RenderableObject) KubeGetServicesMatchingPod(namespace, podName string) } } if matched { - svc, err := clientSet.CoreV1().Services(r.Namespace()).Get(context.TODO(), ep.Name, metav1.GetOptions{}) + svc, err := r.repo.Service(r.Namespace(), ep.Name) if err != nil { klog.V(3).ErrorS(err, "error getting matching service", "r", r, "namespace", namespace, "name", ep.Name) continue @@ -315,7 +230,7 @@ func (r RenderableObject) KubeGetNodeStatsSummary(nodeName string) map[string]in return nil } klog.V(5).InfoS("called KubeGetNodeStatsSummary", "r", r, "node", nodeName) - nodeStatsSummary, err := r.kubeGetNodeStatsSummary(nodeName) + nodeStatsSummary, err := r.repo.KubeGetNodeStatsSummary(nodeName) if err != nil { klog.V(3).ErrorS(err, "failed to get node stats summary", "r", r, "node", nodeName) return nil @@ -323,68 +238,19 @@ func (r RenderableObject) KubeGetNodeStatsSummary(nodeName string) map[string]in return nodeStatsSummary } -// kubeGetNodeStatsSummary returns this structure -// -// > kubectl get --raw /api/v1/nodes/{nodeName}/proxy/stats/summary -// -// The endpoint that this function uses will be disabled soon: https://github.com/kubernetes/kubernetes/issues/68522 -func (r RenderableObject) kubeGetNodeStatsSummary(nodeName string) (map[string]interface{}, error) { - clientSet, err := r.engine.repo.KubernetesClientSet() - if err != nil { - return nil, err - } - getBytes, err := clientSet.CoreV1().RESTClient().Get(). - Resource("nodes"). - SubResource("proxy"). - Name(nodeName). - Suffix("stats/summary"). - DoRaw(context.TODO()) - if err != nil { - return nil, err - } - nodeStatsSummary := make(map[string]interface{}) - err = json.Unmarshal(getBytes, &nodeStatsSummary) - return nodeStatsSummary, err -} - // KubeGetNonTerminatedPodsOnNode returns details of all pods which are not in terminal status func (r RenderableObject) KubeGetNonTerminatedPodsOnNode(nodeName string) (podList []RenderableObject) { if viper.GetBool("shallow") { return } klog.V(5).InfoS("called KubeGetNonTerminatedPodsOnNode", "r", r, "node", nodeName) - podList, err := r.kubeGetNonTerminatedPodsOnTheNode(nodeName) - if err != nil { - klog.V(3).ErrorS(err, "kubeGetNonTerminatedPodsOnTheNode failed", - "r", r, "nodeName", nodeName) - } - return -} - -func (r RenderableObject) kubeGetNonTerminatedPodsOnTheNode(nodeName string) (podList []RenderableObject, err error) { - clientSet, _ := r.engine.repo.KubernetesClientSet() - fieldSelector, err := fields.ParseSelector("spec.nodeName=" + nodeName + - ",status.phase!=" + string(corev1.PodSucceeded) + - ",status.phase!=" + string(corev1.PodFailed)) - if err != nil { - klog.V(3).ErrorS(err, "Failed creating fieldSelector for non-terminated Pods on Node", - "r", r, "nodeName", nodeName) - return - } - nodeNonTerminatedPodsList, err := clientSet.CoreV1(). - Pods(""). // Search in all namespaces - List(context.TODO(), metav1.ListOptions{FieldSelector: fieldSelector.String()}) + pods, err := r.repo.NonTerminatedPodsOnTheNode(nodeName) if err != nil { klog.V(3).ErrorS(err, "Failed getting non-terminated Pods for Node", "r", r, "nodeName", nodeName) return } - for _, pod := range nodeNonTerminatedPodsList.Items { - unstructuredPod, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(&pod) - nr := r.newRenderableObject(unstructuredPod) - podList = append(podList, nr) - } - return podList, nil + return r.objectsToRenderableObjects(pods) } // KubeGetUnifiedDiffString generates a unified diff between given 2 resources and ignores several keys which are @@ -405,22 +271,16 @@ func (r RenderableObject) KubeGetUnifiedDiffString(resourceOrKind, namespace, na } func (r RenderableObject) kubeGetUnifiedDiffString(resourceOrKind, namespace, nameA, nameB string) (string, error) { - controllerRevisionMapping, err := r.engine.mappingFor(resourceOrKind) + gvr, err := r.repo.GVRFor(resourceOrKind) if err != nil { klog.V(3).ErrorS(err, "failed to get mapping", "resourceOrKind", resourceOrKind) return "", err } - gvr := controllerRevisionMapping.Resource - dynamicClient, err := r.engine.repo.DynamicClient() - if err != nil { - klog.V(3).ErrorS(err, "failed to get dynamic client") - return "", err - } - aKind, aBytes, aTime, err := getObjectDetailsForDiff(dynamicClient, gvr, namespace, nameA) + aKind, aBytes, aTime, err := r.getObjectDetailsForDiff(gvr, namespace, nameA) if err != nil { return "", err } - bKind, bBytes, bTime, err := getObjectDetailsForDiff(dynamicClient, gvr, namespace, nameB) + bKind, bBytes, bTime, err := r.getObjectDetailsForDiff(gvr, namespace, nameB) if err != nil { return "", err } @@ -436,12 +296,13 @@ func (r RenderableObject) kubeGetUnifiedDiffString(resourceOrKind, namespace, na return difflib.GetUnifiedDiffString(diff) } -func getObjectDetailsForDiff(dynamicClient dynamic.Interface, gvr schema.GroupVersionResource, namespace string, name string) (string, []byte, time.Time, error) { - obj, err := dynamicClient.Resource(gvr).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{}) +func (r RenderableObject) getObjectDetailsForDiff(gvr schema.GroupVersionResource, namespace string, name string) (string, []byte, time.Time, error) { + object, err := r.repo.DynamicObject(gvr, namespace, name) if err != nil { - klog.V(3).ErrorS(err, "failed to query object") + klog.V(2).ErrorS(err, "failed to query object", "gvr", gvr, "namespace", namespace, "name", name) return "", nil, time.Time{}, err } + obj := object.Unstructured() creationTime := obj.GetCreationTimestamp().Time removeFieldsThatCreateDiffNoise(obj) objBytes, err := json.MarshalIndent(obj, "", " ") diff --git a/pkg/plugin/template_functions_static.go b/pkg/plugin/template_functions_static.go index 148209e..c6bcc65 100644 --- a/pkg/plugin/template_functions_static.go +++ b/pkg/plugin/template_functions_static.go @@ -6,6 +6,7 @@ import ( "regexp" "sort" "strings" + "syscall" "text/template" "time" @@ -13,6 +14,8 @@ import ( "github.com/fatih/color" "github.com/go-sprout/sprout" "github.com/spf13/cast" + "github.com/spf13/viper" + "golang.org/x/sys/unix" resource2 "k8s.io/apimachinery/pkg/api/resource" "k8s.io/klog/v2" ) @@ -60,9 +63,17 @@ func funcMap() template.FuncMap { "addFloat64": addFloat64, "subFloat64": subFloat64, "divFloat64": divFloat64, + "ip": ip, } } +func ip(ip string) string { + if viper.GetBool("test-hack") { + return "1.1.1.1" + } + return ip +} + func addFloat64(i ...interface{}) float64 { var a float64 = 0 for _, b := range i { @@ -272,7 +283,7 @@ func isStatusConditionHealthy(condition map[string]interface{}) bool { } func signalName(signal int64) string { - return signame(uint32(signal)) + return unix.SignalName(syscall.Signal(signal)) } func redIf(cond interface{}, str string) string { diff --git a/pkg/plugin/templates/Pod.tmpl b/pkg/plugin/templates/Pod.tmpl index 2adaed5..f229326 100644 --- a/pkg/plugin/templates/Pod.tmpl +++ b/pkg/plugin/templates/Pod.tmpl @@ -107,9 +107,9 @@ {{- define "pod_volume_stats" }} {{- /* pod_volume_stats returns a single liner representing all the known volume details */ -}} {{- $bytesUsagePercentOfCapacity := percent (.usedBytes | float64) (.capacityBytes | float64) }} - {{- .usedBytes | humanizeSI "B" }}/{{ .capacityBytes | humanizeSI "B" }}({{- $bytesUsagePercentOfCapacity | colorPercent "%.0f%%" }}) used, {{ .availableBytes | humanizeSI "B" }} free, {{""}} + {{- .usedBytes | float64 | humanizeSI "B" }}/{{ .capacityBytes | float64 | humanizeSI "B" }}({{- $bytesUsagePercentOfCapacity | colorPercent "%.0f%%" }}) used, {{ .availableBytes | float64 | humanizeSI "B" }} free, {{""}} {{- $inodesUsagePercentOfCapacity := percent (.inodesUsed | float64) (.inodes | float64) }} - {{- .inodesUsed | humanizeSI "" }}/{{ .inodes | humanizeSI "" }}({{- $inodesUsagePercentOfCapacity | colorPercent "%.0f%%" }}) inodes used, {{ .inodesFree | humanizeSI "" }} free. + {{- .inodesUsed | float64 | humanizeSI "" }}/{{ .inodes | float64 | humanizeSI "" }}({{- $inodesUsagePercentOfCapacity | colorPercent "%.0f%%" }}) inodes used, {{ .inodesFree | float64 | humanizeSI "" }} free. {{- end }} {{- define "matching_services" }} diff --git a/pkg/plugin/templates/Service.tmpl b/pkg/plugin/templates/Service.tmpl index 71ab3b4..5f3e39f 100644 --- a/pkg/plugin/templates/Service.tmpl +++ b/pkg/plugin/templates/Service.tmpl @@ -60,7 +60,7 @@ * address: either Endpoints.subsets.addresses[N] or Endpoints.subsets.notReadyAddresses * ports: Endpoints.subsets.ports */ -}} - {{- $ip := .address.ip }} + {{- $ip := .address.ip | ip }} {{- $hasTargetRef := not (not .address.targetRef) }} {{- if .address.targetRef }} {{- .address.targetRef.kind | bold }}/{{ .address.targetRef.name }} diff --git a/pkg/plugin/templates_common_test.go b/pkg/plugin/templates_common_test.go index 3eba1fa..be56b8b 100644 --- a/pkg/plugin/templates_common_test.go +++ b/pkg/plugin/templates_common_test.go @@ -4,16 +4,24 @@ import ( "strings" "testing" - "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericiooptions" - "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/client-go/rest/fake" + cmdtesting "k8s.io/kubectl/pkg/cmd/testing" + + "github.com/bergerx/kubectl-status/pkg/input" ) func checkTemplate(t *testing.T, templateName string, obj map[string]interface{}, shouldContain string, useRenderable bool) { + t.Helper() tmpl, _ := getTemplate() - e, _ := newRenderEngine(util.NewFactory(genericclioptions.NewTestConfigFlags()), genericiooptions.NewTestIOStreamsDiscard()) + f := cmdtesting.NewTestFactory().WithNamespace("test") + f.Client = &fake.RESTClient{} + f.UnstructuredClient = f.Client + t.Cleanup(func() { f.Cleanup() }) + repo, _ := input.NewResourceRepo(f) + e, _ := newRenderEngine(genericiooptions.NewTestIOStreamsDiscard()) e.Template = *tmpl - r := newRenderableObject(obj, *e) + r := newRenderableObject(obj, e, repo) var objToPassTemplate interface{} if useRenderable { objToPassTemplate = r @@ -79,6 +87,7 @@ func TestObservedGenerationSummaryTemplate(t *testing.T) { }) } } + func TestSuspendTemplate(t *testing.T) { tests := []struct { name string @@ -115,6 +124,7 @@ func TestSuspendTemplate(t *testing.T) { }) } } + func TestOwnersTemplate(t *testing.T) { tests := []struct { name string diff --git a/staticcheck.conf b/staticcheck.conf new file mode 100644 index 0000000..5f0a348 --- /dev/null +++ b/staticcheck.conf @@ -0,0 +1 @@ +checks = ["all", "-ST1000"] diff --git a/tests/artifacts/README.md b/tests/artifacts/README.md index c75b64b..17ef37d 100644 --- a/tests/artifacts/README.md +++ b/tests/artifacts/README.md @@ -11,6 +11,21 @@ cd ../.. for yaml in ./tests/artifacts/*.yaml; do out=$(echo ${yaml} | sed 's/.yaml/.out/') echo "${yaml} --> ${out}" - go run ./cmd --time-hack-ago -f ${yaml} --local --shallow > ${out} + go run ./cmd --test-hack -f ${yaml} --local --shallow > ${out} done ``` + +# Adding a new case + +First generate the artifact file using a command like this: + +```bash +cmd="" # the command line parameters for generating the new manifest, e.g.: -n default node,service +file="" # filename for the new artifact file to be stored, e.g. node-and-service + +cd ../.. +kubectl get -o yaml ${cmd} > tests/artifacts/${file}.yaml +go run ./cmd --test-hack ${cmd} --shallow > tests/artifacts/${file}.out +make test +git add tests/artifacts/${file}.yaml tests/artifacts/${file}.out +``` diff --git a/tests/artifacts/node-and-service.out b/tests/artifacts/node-and-service.out new file mode 100644 index 0000000..b87aaa4 --- /dev/null +++ b/tests/artifacts/node-and-service.out @@ -0,0 +1,16 @@ + +Node/minikube, created 1m ago + linux Ubuntu 22.04.4 LTS (amd64), kernel 6.6.41-1-MANJARO, kubelet v1.30.0, kube-proxy v1.30.0 + images 8 + Current: Resource is Ready + MemoryPressure KubeletHasSufficientMemory, kubelet has sufficient memory available for 1m + DiskPressure KubeletHasNoDiskPressure, kubelet has no disk pressure for 1m + PIDPressure KubeletHasSufficientPID, kubelet has sufficient PID available for 1m + Ready KubeletReady, kubelet is posting ready status for 1m + addresses: InternalIP=192.168.49.2 Hostname=minikube + allocatable: pods:110, cpu:16, mem:33.3GB, ephemeral-storage:997.3GB + capacity: pods:110, cpu:16, mem:33.3GB, ephemeral-storage:997.3GB + +Service/kubernetes -n default, created 1m ago + Current: Service is ready + Missing Endpoint: Service has no matching endpoint. diff --git a/tests/artifacts/node-and-service.yaml b/tests/artifacts/node-and-service.yaml new file mode 100644 index 0000000..10d55bf --- /dev/null +++ b/tests/artifacts/node-and-service.yaml @@ -0,0 +1,153 @@ +apiVersion: v1 +items: +- apiVersion: v1 + kind: Node + metadata: + annotations: + kubeadm.alpha.kubernetes.io/cri-socket: unix:///var/run/cri-dockerd.sock + node.alpha.kubernetes.io/ttl: "0" + volumes.kubernetes.io/controller-managed-attach-detach: "true" + creationTimestamp: "2024-08-06T08:36:21Z" + labels: + beta.kubernetes.io/arch: amd64 + beta.kubernetes.io/os: linux + kubernetes.io/arch: amd64 + kubernetes.io/hostname: minikube + kubernetes.io/os: linux + minikube.k8s.io/commit: 5883c09216182566a63dff4c326a6fc9ed2982ff-dirty + minikube.k8s.io/name: minikube + minikube.k8s.io/primary: "true" + minikube.k8s.io/updated_at: 2024_08_06T09_36_23_0700 + minikube.k8s.io/version: v1.33.1 + node-role.kubernetes.io/control-plane: "" + node.kubernetes.io/exclude-from-external-load-balancers: "" + name: minikube + resourceVersion: "8978" + uid: 8319502f-5189-46fc-a54f-184f79a1cff5 + spec: + podCIDR: 10.244.0.0/24 + podCIDRs: + - 10.244.0.0/24 + status: + addresses: + - address: 192.168.49.2 + type: InternalIP + - address: minikube + type: Hostname + allocatable: + cpu: "16" + ephemeral-storage: 973983276Ki + hugepages-1Gi: "0" + hugepages-2Mi: "0" + memory: 32587744Ki + pods: "110" + capacity: + cpu: "16" + ephemeral-storage: 973983276Ki + hugepages-1Gi: "0" + hugepages-2Mi: "0" + memory: 32587744Ki + pods: "110" + conditions: + - lastHeartbeatTime: "2024-08-07T07:45:09Z" + lastTransitionTime: "2024-08-06T08:36:20Z" + message: kubelet has sufficient memory available + reason: KubeletHasSufficientMemory + status: "False" + type: MemoryPressure + - lastHeartbeatTime: "2024-08-07T07:45:09Z" + lastTransitionTime: "2024-08-06T08:36:20Z" + message: kubelet has no disk pressure + reason: KubeletHasNoDiskPressure + status: "False" + type: DiskPressure + - lastHeartbeatTime: "2024-08-07T07:45:09Z" + lastTransitionTime: "2024-08-06T08:36:20Z" + message: kubelet has sufficient PID available + reason: KubeletHasSufficientPID + status: "False" + type: PIDPressure + - lastHeartbeatTime: "2024-08-07T07:45:09Z" + lastTransitionTime: "2024-08-06T08:36:21Z" + message: kubelet is posting ready status + reason: KubeletReady + status: "True" + type: Ready + daemonEndpoints: + kubeletEndpoint: + Port: 10250 + images: + - names: + - registry.k8s.io/etcd@sha256:44a8e24dcbba3470ee1fee21d5e88d128c936e9b55d4bc51fbef8086f8ed123b + - registry.k8s.io/etcd:3.5.12-0 + sizeBytes: 149347661 + - names: + - registry.k8s.io/kube-apiserver@sha256:6b8e197b2d39c321189a475ac755a77896e34b56729425590fbc99f3a96468a3 + - registry.k8s.io/kube-apiserver:v1.30.0 + sizeBytes: 116552324 + - names: + - registry.k8s.io/kube-controller-manager@sha256:5f52f00f17d5784b5ca004dffca59710fa1a9eec8d54cebdf9433a1d134150fe + - registry.k8s.io/kube-controller-manager:v1.30.0 + sizeBytes: 111113187 + - names: + - registry.k8s.io/kube-proxy@sha256:ec532ff47eaf39822387e51ec73f1f2502eb74658c6303319db88d2c380d0210 + - registry.k8s.io/kube-proxy:v1.30.0 + sizeBytes: 84675401 + - names: + - registry.k8s.io/kube-scheduler@sha256:2353c3a1803229970fcb571cffc9b2f120372350e01c7381b4b650c4a02b9d67 + - registry.k8s.io/kube-scheduler:v1.30.0 + sizeBytes: 61969366 + - names: + - registry.k8s.io/coredns/coredns@sha256:1eeb4c7316bacb1d4c8ead65571cd92dd21e27359f0d4917f1a5822a73b75db1 + - registry.k8s.io/coredns/coredns:v1.11.1 + sizeBytes: 59820619 + - names: + - gcr.io/k8s-minikube/storage-provisioner@sha256:18eb69d1418e854ad5a19e399310e52808a8321e4c441c1dddad8977a0d7a944 + - gcr.io/k8s-minikube/storage-provisioner:v5 + sizeBytes: 31465472 + - names: + - registry.k8s.io/pause@sha256:7031c1b283388d2c2e09b57badb803c05ebed362dc88d84b480cc47f72a21097 + - registry.k8s.io/pause:3.9 + sizeBytes: 743952 + nodeInfo: + architecture: amd64 + bootID: 347b6358-8163-4429-b24d-c5b7841e4b41 + containerRuntimeVersion: docker://26.1.1 + kernelVersion: 6.6.41-1-MANJARO + kubeProxyVersion: v1.30.0 + kubeletVersion: v1.30.0 + machineID: d6f70d223fef420cbd26ccac4bd0412b + operatingSystem: linux + osImage: Ubuntu 22.04.4 LTS + systemUUID: d91167d3-7a3a-499e-aaaa-6c952fc10a74 +- apiVersion: v1 + kind: Service + metadata: + creationTimestamp: "2024-08-06T08:36:22Z" + labels: + component: apiserver + provider: kubernetes + name: kubernetes + namespace: default + resourceVersion: "198" + uid: ef2ff9cf-09e9-4bd6-ba01-3daf3e8886c6 + spec: + clusterIP: 10.96.0.1 + clusterIPs: + - 10.96.0.1 + internalTrafficPolicy: Cluster + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - name: https + port: 443 + protocol: TCP + targetPort: 8443 + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +kind: List +metadata: + resourceVersion: "" diff --git a/tests/artifacts/pod-deleted-due-to-missing-container.out b/tests/artifacts/pod-deleted-due-to-missing-container.out index 6dd8a7c..024e835 100644 --- a/tests/artifacts/pod-deleted-due-to-missing-container.out +++ b/tests/artifacts/pod-deleted-due-to-missing-container.out @@ -5,5 +5,5 @@ Pod/prometheus-operator-5c5784bc5f-4h65z -n prometheus, created 1m ago by Replic Ready PodFailed for 1m ContainersReady PodFailed for 1m Containers: - kube-prometheus-stack (quay.io/prometheus-operator/prometheus-operator:v0.58.0) Terminated as ContainerStatusUnknown with "The container could not be located when the pod was terminated" exit with 137 (SIGKILL: kill), restarted 1 times - previously: Terminated as ContainerStatusUnknown with "The container could not be located when the pod was deleted. The container used to be Running" exit with 137 (SIGKILL: kill) + kube-prometheus-stack (quay.io/prometheus-operator/prometheus-operator:v0.58.0) Terminated as ContainerStatusUnknown with "The container could not be located when the pod was terminated" exit with 137 (SIGKILL), restarted 1 times + previously: Terminated as ContainerStatusUnknown with "The container could not be located when the pod was deleted. The container used to be Running" exit with 137 (SIGKILL) diff --git a/tests/e2e-artifacts/sts-with-ingress.pod.out b/tests/e2e-artifacts/sts-with-ingress.pod.out new file mode 100644 index 0000000..0821365 --- /dev/null +++ b/tests/e2e-artifacts/sts-with-ingress.pod.out @@ -0,0 +1,22 @@ + +Pod/sts-with-ingress-0 -n default, created 1m ago by StatefulSet/sts-with-ingress Running BestEffort + Current: Pod is Ready + Managed by sts-with-ingress application + PodScheduled -> Initialized -> ContainersReady -> Ready for 1m + Containers: + sts-with-ingress (registry.k8s.io/pause:3.9) Running for 1m and Ready + Known/recorded manage events: + 1m ago Updated by kube-controller-manager (metadata, spec) + 1m ago Updated by kubelet (status) + Services matching this pod: + Service/sts-with-ingress -n default, created 1m ago, last endpoint change was 1m ago + Current: Service is ready + Ready: Pod/sts-with-ingress-0 -n default on Node/minikube, 1.1.1.1:80/TCP + Known/recorded manage events: + 1m ago Updated by kubectl-client-side-apply (metadata, spec) + Ingresses matching this Service: + Ingress/sts-with-ingress -n default, created 1m ago, gen:1 + Current: Resource is current + Service/sts-with-ingress:80 has 1 endpoints. + Known/recorded manage events: + 1m ago Updated by kubectl-client-side-apply (metadata, spec) diff --git a/tests/e2e-artifacts/sts-with-ingress.yaml b/tests/e2e-artifacts/sts-with-ingress.yaml new file mode 100644 index 0000000..095517e --- /dev/null +++ b/tests/e2e-artifacts/sts-with-ingress.yaml @@ -0,0 +1,46 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: sts-with-ingress +spec: + replicas: 1 + selector: + matchLabels: + app: sts-with-ingress + template: + metadata: + labels: + app: sts-with-ingress + spec: + containers: + - name: sts-with-ingress + image: registry.k8s.io/pause:3.9 +--- +apiVersion: v1 +kind: Service +metadata: + name: sts-with-ingress +spec: + selector: + app: sts-with-ingress + ports: + - protocol: TCP + port: 80 + targetPort: 80 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: sts-with-ingress +spec: + rules: + - host: sts-with-ingress.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: sts-with-ingress + port: + number: 80