diff --git a/e2e/README.md b/e2e/README.md new file mode 100644 index 00000000..9f115ef2 --- /dev/null +++ b/e2e/README.md @@ -0,0 +1,9 @@ +# E2e Testing + +This e2e test suite leverages the [Kubernetes e2e-framework](https://github.com/kubernetes-sigs/e2e-framework) project for writing and running e2e tests for the spin operator project. + +To run tests: + +```console +go test ./e2e -v +``` diff --git a/e2e/crd_installed_test.go b/e2e/crd_installed_test.go new file mode 100644 index 00000000..7a86e5e3 --- /dev/null +++ b/e2e/crd_installed_test.go @@ -0,0 +1,49 @@ +package e2e + +import ( + "context" + "testing" + + apiextensionsV1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" +) + +func TestCRDInstalled(t *testing.T) { + crdInstalledFeature := features.New("crd installed"). + Assess("spinapp crd installed", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + client := cfg.Client() + if err := apiextensionsV1.AddToScheme(client.Resources().GetScheme()); err != nil { + t.Fatalf("failed to register the v1 API extension types with Kuberenets scheme: %s", err) + } + name := "spinapps.core.spinoperator.dev" + var crd apiextensionsV1.CustomResourceDefinition + if err := client.Resources().Get(ctx, name, "", &crd); err != nil { + t.Fatalf("SpinApp CRD not found: %s", err) + } + + if crd.Spec.Group != "core.spinoperator.dev" { + t.Fatalf("SpinApp CRD has unexpected group: %s", crd.Spec.Group) + } + return ctx + + }). + Assess("spinappexecutor crd installed", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + client := cfg.Client() + if err := apiextensionsV1.AddToScheme(client.Resources().GetScheme()); err != nil { + t.Fatalf("failed to register the v1 API extension types with Kuberenets scheme: %s", err) + } + + name := "spinappexecutors.core.spinoperator.dev" + var crd apiextensionsV1.CustomResourceDefinition + if err := client.Resources().Get(ctx, name, "", &crd); err != nil { + t.Fatalf("SpinApp CRD not found: %s", err) + } + + if crd.Spec.Group != "core.spinoperator.dev" { + t.Fatalf("SpinAppExecutor CRD has unexpected group: %s", crd.Spec.Group) + } + return ctx + }).Feature() + testEnv.Test(t, crdInstalledFeature) +} diff --git a/e2e/default_test.go b/e2e/default_test.go new file mode 100644 index 00000000..c7100e6a --- /dev/null +++ b/e2e/default_test.go @@ -0,0 +1,138 @@ +package e2e + +import ( + "context" + "testing" + "time" + + v1 "k8s.io/api/core/v1" + nodev1 "k8s.io/api/node/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/e2e-framework/klient" + "sigs.k8s.io/e2e-framework/klient/k8s" + "sigs.k8s.io/e2e-framework/klient/wait" + "sigs.k8s.io/e2e-framework/klient/wait/conditions" + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + spinapps_v1 "github.com/spinkube/spin-operator/api/v1" +) + +var runtimeClassName = "wasmtime-spin-v2" + +// TestDefaultSetup is a test that checks that the minimal setup works +// +// with the containerd wasm shim runtime as the default runtime. +func TestDefaultSetup(t *testing.T) { + var client klient.Client + + helloWorldImage := "ghcr.io/deislabs/containerd-wasm-shims/examples/spin-rust-hello:latest" + testSpinAppName := "test-spinapp" + + defaultTest := features.New("default and most minimal setup"). + Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + + client = cfg.Client() + + if err := spinapps_v1.AddToScheme(client.Resources(testNamespace).GetScheme()); err != nil { + t.Fatalf("failed to register the spinapps_v1 types with Kuberenets scheme: %s", err) + } + + runtimeClass := &nodev1.RuntimeClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: runtimeClassName, + }, + Handler: "spin", + } + + if err := client.Resources().Create(ctx, runtimeClass); err != nil { + t.Fatalf("Failed to create runtimeclass: %s", err) + } + + return ctx + }). + Assess("spin app custom resource is created", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + testSpinApp := newSpinAppCR(testSpinAppName, helloWorldImage) + + if err := client.Resources().Create(ctx, newContainerdShimExecutor(testNamespace)); err != nil { + t.Fatalf("Failed to create spinappexecutor: %s", err) + } + + if err := client.Resources().Create(ctx, testSpinApp); err != nil { + t.Fatalf("Failed to create spinapp: %s", err) + } + // wait for spinapp to be created + if err := wait.For( + conditions.New(client.Resources()).ResourceMatch(testSpinApp, func(object k8s.Object) bool { + return true + }), + wait.WithTimeout(3*time.Minute), + wait.WithInterval(30*time.Second), + ); err != nil { + t.Fatal(err) + } + + return ctx + }). + Assess("spin app deployment and service are available", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + + // wait for deployment to be ready + if err := wait.For( + conditions.New(client.Resources()).DeploymentAvailable(testSpinAppName, testNamespace), + wait.WithTimeout(3*time.Minute), + wait.WithInterval(30*time.Second), + ); err != nil { + t.Fatal(err) + } + + svc := &v1.ServiceList{ + Items: []v1.Service{ + {ObjectMeta: metav1.ObjectMeta{Name: testSpinAppName, Namespace: testNamespace}}, + }, + } + + if err := wait.For( + conditions.New(client.Resources()).ResourcesFound(svc), + wait.WithTimeout(3*time.Minute), + wait.WithInterval(30*time.Second), + ); err != nil { + t.Fatal(err) + } + return ctx + }).Feature() + testEnv.Test(t, defaultTest) +} + +func newSpinAppCR(name, image string) *spinapps_v1.SpinApp { + var testSpinApp = &spinapps_v1.SpinApp{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: testNamespace, + }, + Spec: spinapps_v1.SpinAppSpec{ + Replicas: 1, + Image: image, + Executor: "containerd-shim-spin", + }, + } + + return testSpinApp + +} + +func newContainerdShimExecutor(namespace string) *spinapps_v1.SpinAppExecutor { + var testSpinAppExecutor = &spinapps_v1.SpinAppExecutor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "containerd-shim-spin", + Namespace: namespace, + }, + Spec: spinapps_v1.SpinAppExecutorSpec{ + CreateDeployment: true, + DeploymentConfig: &spinapps_v1.ExecutorDeploymentConfig{ + RuntimeClassName: runtimeClassName, + }, + }, + } + + return testSpinAppExecutor +} diff --git a/e2e/k3d_provider.go b/e2e/k3d_provider.go new file mode 100644 index 00000000..a979e78a --- /dev/null +++ b/e2e/k3d_provider.go @@ -0,0 +1,124 @@ +package e2e + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "strings" + + "k8s.io/client-go/rest" + "k8s.io/klog/v2" + "sigs.k8s.io/e2e-framework/klient/conf" + "sigs.k8s.io/e2e-framework/support/utils" +) + +const k3dImage = "ghcr.io/deislabs/containerd-wasm-shims/examples/k3d:v0.11.0" + +var k3dBin = "k3d" + +type Cluster struct { + name string + kubecfgFile string + restConfig *rest.Config +} + +func (c *Cluster) Create(context.Context, string) (string, error) { + if err := findOrInstallK3d(); err != nil { + panic(err) + } + + if _, ok := clusterExists(c.name); ok { + klog.V(4).Info("Skipping k3d Cluster creation. Cluster already created ", c.name) + return c.GetKubeconfig() + } + + command := fmt.Sprintf("%s cluster create %s --image %s -p '8081:80@loadbalancer' --agents 2 --wait", k3dBin, c.name, k3dImage) + klog.V(4).Info("Launching:", command) + p := utils.RunCommand(command) + if p.Err() != nil { + outBytes, err := io.ReadAll(p.Out()) + if err != nil { + klog.ErrorS(err, "failed to read data from the k3d create process output due to an error") + } + return "", fmt.Errorf("k3d: failed to create cluster %q: %w: %s: %s", c.name, p.Err(), p.Result(), string(outBytes)) + } + + clusters, ok := clusterExists(c.name) + if !ok { + return "", fmt.Errorf("k3d Cluster.Create: cluster %v still not in 'cluster list' after creation: %v", c.name, clusters) + } + klog.V(4).Info("k3d cluster available: ", clusters) + + kConfig, err := c.GetKubeconfig() + if err != nil { + return "", err + } + return kConfig, c.initKubernetesAccessClients() +} + +func (c *Cluster) Destroy() error { + + p := utils.RunCommand(fmt.Sprintf(`%s cluster delete %s`, k3dBin, c.name)) + if p.Err() != nil { + return fmt.Errorf("%s cluster delete: %w", k3dBin, p.Err()) + } + + return nil +} + +func (c *Cluster) GetKubeconfig() (string, error) { + kubecfg := fmt.Sprintf("%s-kubecfg", c.name) + + p := utils.RunCommand(fmt.Sprintf(`%s kubeconfig get %s`, k3dBin, c.name)) + + if p.Err() != nil { + return "", fmt.Errorf("k3d get kubeconfig: %w", p.Err()) + } + + var stdout bytes.Buffer + if _, err := stdout.ReadFrom(p.Out()); err != nil { + return "", fmt.Errorf("k3d kubeconfig stdout bytes: %w", err) + } + + file, err := os.CreateTemp("", fmt.Sprintf("k3d-cluster-%s", kubecfg)) + if err != nil { + return "", fmt.Errorf("k3d kubeconfig file: %w", err) + } + defer file.Close() + + c.kubecfgFile = file.Name() + + if n, err := io.Copy(file, &stdout); n == 0 || err != nil { + return "", fmt.Errorf("k3d kubecfg file: bytes copied: %d: %w]", n, err) + } + + return file.Name(), nil +} + +func (c *Cluster) initKubernetesAccessClients() error { + cfg, err := conf.New(c.kubecfgFile) + if err != nil { + return err + } + c.restConfig = cfg + + return nil +} + +func findOrInstallK3d() error { + _, err := utils.FindOrInstallGoBasedProvider(k3dBin, k3dBin, "github.com/k3d-io/k3d", "v5.6.0") + return err +} + +func clusterExists(name string) (string, bool) { + clusters := utils.FetchCommandOutput(fmt.Sprintf("%s cluster list --no-headers", k3dBin)) + for _, c := range strings.Split(clusters, "\n") { + cl := strings.Split(c, " ")[0] + if cl == name { + return clusters, true + } + } + return clusters, false +} diff --git a/e2e/main_test.go b/e2e/main_test.go new file mode 100644 index 00000000..418c1c65 --- /dev/null +++ b/e2e/main_test.go @@ -0,0 +1,98 @@ +package e2e + +import ( + "context" + "os" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/klient/wait" + "sigs.k8s.io/e2e-framework/klient/wait/conditions" + "sigs.k8s.io/e2e-framework/pkg/env" + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/envfuncs" + "sigs.k8s.io/e2e-framework/support/utils" +) + +var ( + testEnv env.Environment + testNamespace string + spinOperatorDeploymentName = "spin-operator-controller-manager" + spinOperatorNamespace = "spin-operator" + cluster = &Cluster{} +) + +func TestMain(m *testing.M) { + cfg, _ := envconf.NewFromFlags() + testEnv = env.NewWithConfig(cfg) + testNamespace = envconf.RandomName("my-ns", 10) + cluster.name = envconf.RandomName("crdtest-", 16) + + testEnv.Setup( + + func(ctx context.Context, e *envconf.Config) (context.Context, error) { + if _, err := cluster.Create(ctx, cluster.name); err != nil { + return ctx, err + } + e.WithKubeconfigFile(cluster.kubecfgFile) + + return ctx, nil + }, + + envfuncs.CreateNamespace(testNamespace), + + // build and load spin operator image into cluster + func(ctx context.Context, _ *envconf.Config) (context.Context, error) { + if p := utils.RunCommand(`bash -c "cd .. && IMG=ghcr.io/spinkube/spin-operator:dev make docker-build"`); p.Err() != nil { + return ctx, p.Err() + } + + if p := utils.RunCommand(("k3d image import -c " + cluster.name + " ghcr.io/spinkube/spin-operator:dev")); p.Err() != nil { + return ctx, p.Err() + } + return ctx, nil + }, + + // install spin operator and pre-reqs + func(ctx context.Context, _ *envconf.Config) (context.Context, error) { + // install crds + if p := utils.RunCommand(`bash -c "cd .. && make install"`); p.Err() != nil { + return ctx, p.Err() + } + + // install cert-manager + if p := utils.RunCommand("kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.2/cert-manager.yaml"); p.Err() != nil { + return ctx, p.Err() + } + // wait for cert-manager to be ready + if p := utils.RunCommand("kubectl wait --for=condition=Available --timeout=300s deployment/cert-manager-webhook -n cert-manager"); p.Err() != nil { + return ctx, p.Err() + } + + if p := utils.RunCommand(`bash -c "cd .. && IMG=ghcr.io/spinkube/spin-operator:dev make deploy"`); p.Err() != nil { + return ctx, p.Err() + } + + // wait for the controller deployment to be ready + client := cfg.Client() + if err := wait.For( + conditions.New(client.Resources()). + DeploymentAvailable(spinOperatorDeploymentName, spinOperatorNamespace), + wait.WithTimeout(3*time.Minute), + wait.WithInterval(10*time.Second), + ); err != nil { + return ctx, err + } + + return ctx, nil + }, + ) + + testEnv.Finish( + func(ctx context.Context, _ *envconf.Config) (context.Context, error) { + return ctx, cluster.Destroy() + }, + ) + + os.Exit(testEnv.Run(m)) +} diff --git a/go.mod b/go.mod index 12a981aa..7a3ecc66 100644 --- a/go.mod +++ b/go.mod @@ -9,9 +9,12 @@ require ( github.com/prometheus/common v0.48.0 github.com/stretchr/testify v1.8.4 k8s.io/api v0.29.2 + k8s.io/apiextensions-apiserver v0.29.0 k8s.io/apimachinery v0.29.2 k8s.io/client-go v0.29.2 + k8s.io/klog/v2 v2.110.1 sigs.k8s.io/controller-runtime v0.17.2 + sigs.k8s.io/e2e-framework v0.3.0 ) require ( @@ -32,19 +35,23 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/imdario/mergo v0.3.6 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/imdario/mergo v0.3.15 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/moby/spdystream v0.2.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/vladimirvivien/gexe v0.2.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect @@ -60,9 +67,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.29.0 // indirect k8s.io/component-base v0.29.0 // indirect - k8s.io/klog/v2 v2.110.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/go.sum b/go.sum index 6e76f970..a4bb4fc0 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +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/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -48,8 +50,11 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJY github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +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/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -65,6 +70,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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +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/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= @@ -72,6 +79,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY= github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= @@ -101,6 +110,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/vladimirvivien/gexe v0.2.0 h1:nbdAQ6vbZ+ZNsolCgSVb9Fno60kzSuvtzVh6Ytqi/xY= +github.com/vladimirvivien/gexe v0.2.0/go.mod h1:LHQL00w/7gDUKIak24n801ABp8C+ni6eBht9vGVst8w= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -189,6 +200,8 @@ k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSn k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.17.2 h1:FwHwD1CTUemg0pW2otk7/U5/i5m2ymzvOXdbeGOUvw0= sigs.k8s.io/controller-runtime v0.17.2/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= +sigs.k8s.io/e2e-framework v0.3.0 h1:eqQALBtPCth8+ulTs6lcPK7ytV5rZSSHJzQHZph4O7U= +sigs.k8s.io/e2e-framework v0.3.0/go.mod h1:C+ef37/D90Dc7Xq1jQnNbJYscrUGpxrWog9bx2KIa+c= 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/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=