From 3f45ece9df04ac175ca1c42f19c9670da545677c Mon Sep 17 00:00:00 2001 From: Justin Taylor-Barrick <46463088+jbarrick-mesosphere@users.noreply.github.com> Date: Tue, 30 Jul 2019 17:41:06 -0700 Subject: [PATCH] Implement CLI and E2E testing using the KUDO test harness. (#656) * Implement CLI and E2E testing using the KUDO test harness. Fixes #368 (support CLI commands) Fixes #369 (support install kudo frameworks) Fixes #520 (panic when executing test harness) This adds a `kubectl` setting to the KUDO test harness TestSuite and TestStep configurations to support running kubectl commands as a part of tests. * Log files that are ignored by test harness. * factor DoKubectl -> testutils.RunKubectlCommands * Clean up kubectl argument parsing. * Fix broken logging and log stderr instead of writing it to an error. * make cli-test -> make cli-fast * make cli-test -> make cli-fast --- Makefile | 11 +- go.mod | 1 + go.sum | 3 +- pkg/apis/kudo/v1alpha1/test_types.go | 5 + .../kudo/v1alpha1/zz_generated.deepcopy.go | 15 ++ pkg/test/case.go | 52 +++--- pkg/test/case_test.go | 6 +- pkg/test/harness.go | 47 ++++- pkg/test/step.go | 12 +- pkg/test/test_data/cli-test/00-assert.yaml | 4 + .../test_data/cli-test/00-create-pod.yaml | 4 + .../test_data/cli-test/test_data/pod.yaml | 9 + pkg/test/test_data/with-overrides/README.md | 0 pkg/test/utils/kubernetes.go | 159 +++++++++++++++++ pkg/test/utils/kubernetes_test.go | 168 ++++++++++++++++++ test/integration/cli-install/00-assert.yaml | 20 +++ test/integration/cli-install/00-install.yaml | 4 + test/integration/cli-install/01-assert.yaml | 22 +++ test/integration/cli-install/01-upgrade.yaml | 11 ++ .../cli-install-operator/operator.yaml | 20 +++ .../cli-install-operator/params.yaml | 6 + .../templates/deployment.yaml | 12 ++ 22 files changed, 552 insertions(+), 39 deletions(-) create mode 100644 pkg/test/test_data/cli-test/00-assert.yaml create mode 100644 pkg/test/test_data/cli-test/00-create-pod.yaml create mode 100644 pkg/test/test_data/cli-test/test_data/pod.yaml create mode 100644 pkg/test/test_data/with-overrides/README.md create mode 100644 test/integration/cli-install/00-assert.yaml create mode 100644 test/integration/cli-install/00-install.yaml create mode 100644 test/integration/cli-install/01-assert.yaml create mode 100644 test/integration/cli-install/01-upgrade.yaml create mode 100644 test/integration/cli-install/cli-install-operator/operator.yaml create mode 100644 test/integration/cli-install/cli-install-operator/params.yaml create mode 100644 test/integration/cli-install/cli-install-operator/templates/deployment.yaml diff --git a/Makefile b/Makefile index 80d9a8ed4..b5ae17325 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ test: .PHONY: integration-test # Run integration tests -integration-test: +integration-test: cli-fast go test -tags integration ./pkg/... ./cmd/... -v -mod=readonly -coverprofile cover-integration.out go run ./cmd/kubectl-kudo test @@ -117,11 +117,14 @@ generate: generate-clean: rm -rf hack/code-gen +.PHONY: cli-fast +# Build CLI but don't lint or run code generation first. +cli-fast: + go build -ldflags "${LDFLAGS}" -o bin/${CLI} cmd/kubectl-kudo/main.go + .PHONY: cli # Build CLI -cli: prebuild - # developer convince for platform they are running - go build -ldflags "${LDFLAGS}" -o bin/${CLI} cmd/kubectl-kudo/main.go +cli: prebuild cli-fast .PHONY: cli-clean # Clean CLI build diff --git a/go.mod b/go.mod index adf24cbec..fe1b43f4f 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/gobuffalo/envy v1.6.15 // indirect github.com/google/go-github v17.0.0+incompatible github.com/google/go-querystring v1.0.0 // indirect + github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf github.com/gophercloud/gophercloud v0.2.0 // indirect github.com/hashicorp/golang-lru v0.5.1 // indirect github.com/huandu/xstrings v1.2.0 // indirect diff --git a/go.sum b/go.sum index bb3412500..008c73800 100644 --- a/go.sum +++ b/go.sum @@ -112,7 +112,6 @@ github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9k github.com/gobuffalo/envy v1.6.15 h1:OsV5vOpHYUpP7ZLS6sem1y40/lNX1BZj+ynMiRi21lQ= github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -144,6 +143,8 @@ github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= +github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= diff --git a/pkg/apis/kudo/v1alpha1/test_types.go b/pkg/apis/kudo/v1alpha1/test_types.go index a73b75c6a..c09638b0a 100644 --- a/pkg/apis/kudo/v1alpha1/test_types.go +++ b/pkg/apis/kudo/v1alpha1/test_types.go @@ -40,6 +40,8 @@ type TestSuite struct { Parallel int `json:"parallel"` // The directory to output artifacts to (current working directory if not specified). ArtifactsDir string `json:"artifactsDir"` + // Kubectl commands to run before running any tests. + Kubectl []string `json:"kubectl"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -58,6 +60,9 @@ type TestStep struct { // Indicates that this is a unit test - safe to run without a real Kubernetes cluster. UnitTest bool `json:"unitTest"` + // Kubectl commands to run at the start of the test + Kubectl []string `json:"kubectl"` + // Allowed environment labels // Disallowed environment labels } diff --git a/pkg/apis/kudo/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/kudo/v1alpha1/zz_generated.deepcopy.go index db11d7e71..828df20b4 100644 --- a/pkg/apis/kudo/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/kudo/v1alpha1/zz_generated.deepcopy.go @@ -1347,6 +1347,11 @@ func (in *TestStep) DeepCopyInto(out *TestStep) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Kubectl != nil { + in, out := &in.Kubectl, &out.Kubectl + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -1373,11 +1378,21 @@ func (in *TestSuite) DeepCopyInto(out *TestSuite) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.ManifestDirs != nil { + in, out := &in.ManifestDirs, &out.ManifestDirs + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.TestDirs != nil { in, out := &in.TestDirs, &out.TestDirs *out = make([]string, len(*in)) copy(*out, *in) } + if in.Kubectl != nil { + in, out := &in.Kubectl, &out.Kubectl + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/test/case.go b/pkg/test/case.go index dfe2199a2..294d2f116 100644 --- a/pkg/test/case.go +++ b/pkg/test/case.go @@ -74,40 +74,36 @@ func (t *Case) CreateNamespace(namespace string) error { }) } -// TestCaseFactory creates a new Go test that runs a set of test steps. -func (t *Case) TestCaseFactory() func(*testing.T) { - return func(test *testing.T) { - t.Logger = testutils.NewTestLogger(test, t.Name) +// Run runs a test case including all of its steps. +func (t *Case) Run(test *testing.T) { + test.Parallel() - test.Parallel() + ns := fmt.Sprintf("kudo-test-%s", petname.Generate(2, "-")) - ns := fmt.Sprintf("kudo-test-%s", petname.Generate(2, "-")) + if err := t.CreateNamespace(ns); err != nil { + test.Fatal(err) + } - if err := t.CreateNamespace(ns); err != nil { - test.Fatal(err) - } + if !t.SkipDelete { + defer t.DeleteNamespace(ns) + } + + for _, testStep := range t.Steps { + testStep.Client = t.Client + testStep.DiscoveryClient = t.DiscoveryClient + testStep.Logger = t.Logger.WithPrefix(testStep.String()) if !t.SkipDelete { - defer t.DeleteNamespace(ns) + defer testStep.Clean(ns) } - for _, testStep := range t.Steps { - testStep.Client = t.Client - testStep.DiscoveryClient = t.DiscoveryClient - testStep.Logger = t.Logger.WithPrefix(testStep.String()) - - if !t.SkipDelete { - defer testStep.Clean(ns) + if errs := testStep.Run(ns); len(errs) > 0 { + for _, err := range errs { + test.Error(err) } - if errs := testStep.Run(ns); len(errs) > 0 { - for _, err := range errs { - test.Error(err) - } - - test.Error(fmt.Errorf("failed in step %s", testStep.String())) - break - } + test.Error(fmt.Errorf("failed in step %s", testStep.String())) + break } } } @@ -125,6 +121,11 @@ func (t *Case) CollectTestStepFiles() (map[int64][]string, error) { for _, file := range files { matches := testStepRegex.FindStringSubmatch(file.Name()) + if len(matches) < 2 { + t.Logger.Log("Ignoring", file.Name(), "as it does not match file name regexp:", testStepRegex.String()) + continue + } + index, err := strconv.ParseInt(matches[1], 10, 32) if err != nil { return nil, err @@ -168,6 +169,7 @@ func (t *Case) LoadTestSteps() error { testStep := &Step{ Timeout: t.Timeout, Index: int(index), + Dir: t.Dir, Asserts: []runtime.Object{}, Apply: []runtime.Object{}, Errors: []runtime.Object{}, diff --git a/pkg/test/case_test.go b/pkg/test/case_test.go index a6bc809b8..f61e0ad16 100644 --- a/pkg/test/case_test.go +++ b/pkg/test/case_test.go @@ -222,7 +222,7 @@ func TestLoadTestSteps(t *testing.T) { }, } { t.Run(tt.path, func(t *testing.T) { - test := &Case{Dir: tt.path} + test := &Case{Dir: tt.path, Logger: testutils.NewTestLogger(t, tt.path)} err := test.LoadTestSteps() assert.Nil(t, err) @@ -234,10 +234,12 @@ func TestLoadTestSteps(t *testing.T) { assert.Equal(t, len(tt.testSteps), len(testStepsVal)) for index := range tt.testSteps { + tt.testSteps[index].Dir = tt.path assert.Equal(t, tt.testSteps[index].Apply, testStepsVal[index].Apply) assert.Equal(t, tt.testSteps[index].Asserts, testStepsVal[index].Asserts) assert.Equal(t, tt.testSteps[index].Errors, testStepsVal[index].Errors) assert.Equal(t, tt.testSteps[index].Step, testStepsVal[index].Step) + assert.Equal(t, tt.testSteps[index].Dir, testStepsVal[index].Dir) assert.Equal(t, tt.testSteps[index], testStepsVal[index]) } }) @@ -283,7 +285,7 @@ func TestCollectTestStepFiles(t *testing.T) { }, } { t.Run(tt.path, func(t *testing.T) { - test := &Case{Dir: tt.path} + test := &Case{Dir: tt.path, Logger: testutils.NewTestLogger(t, tt.path)} testStepFiles, err := test.CollectTestStepFiles() assert.Nil(t, err) assert.Equal(t, tt.expected, testStepFiles) diff --git a/pkg/test/harness.go b/pkg/test/harness.go index 0aa672fac..edf775030 100644 --- a/pkg/test/harness.go +++ b/pkg/test/harness.go @@ -5,6 +5,7 @@ import ( "fmt" "io/ioutil" "math/rand" + "os" "path/filepath" "sync" "testing" @@ -30,6 +31,7 @@ type Harness struct { TestSuite kudo.TestSuite T *testing.T + logger testutils.Logger managerStopCh chan struct{} config *rest.Config client client.Client @@ -71,6 +73,15 @@ func (h *Harness) LoadTests(dir string) ([]*Case, error) { return tests, nil } +// GetLogger returns an initialized test logger. +func (h *Harness) GetLogger() testutils.Logger { + if h.logger == nil { + h.logger = testutils.NewTestLogger(h.T, "") + } + + return h.logger +} + // GetTimeout returns the configured timeout for the test suite. func (h *Harness) GetTimeout() int { timeout := 30 @@ -162,7 +173,18 @@ func (h *Harness) Config() (*rest.Config, error) { h.config, err = config.GetConfig() } - return h.config, err + if err != nil { + return h.config, err + } + + f, err := os.Create("kubeconfig") + if err != nil { + return h.config, err + } + + defer f.Close() + + return h.config, testutils.Kubeconfig(h.config, f) } // RunKUDO starts the KUDO controllers and installs the required CRDs. @@ -249,11 +271,15 @@ func (h *Harness) RunTests() { test.Client = h.Client test.DiscoveryClient = h.DiscoveryClient - if err := test.LoadTestSteps(); err != nil { - t.Fatal(err) - } + t.Run(test.Name, func(t *testing.T) { + test.Logger = testutils.NewTestLogger(t, test.Name) - t.Run(test.Name, test.TestCaseFactory()) + if err := test.LoadTestSteps(); err != nil { + t.Fatal(err) + } + + test.Run(t) + }) } }) } @@ -299,6 +325,10 @@ func (h *Harness) Run() { } } + if err := testutils.RunKubectlCommands(h.GetLogger(), "default", h.TestSuite.Kubectl, ""); err != nil { + h.T.Fatal(err) + } + if h.TestSuite.StartKUDO { if err := h.RunKUDO(); err != nil { h.T.Fatal(err) @@ -326,7 +356,12 @@ func (h *Harness) Stop() { } if h.TestSuite.SkipClusterDelete || h.TestSuite.SkipDelete { - // TODO: figure out how to write the Kubernetes client configuration to disk. + cwd, _ := os.Getwd() + kubeconfig := filepath.Join(cwd, "kubeconfig") + + h.T.Log("skipping cluster tear down") + h.T.Log(fmt.Sprintf("to connect to the cluster, run: export KUBECONFIG=\"%s\"", kubeconfig)) + return } diff --git a/pkg/test/step.go b/pkg/test/step.go index dd1e50c0e..51356f7bb 100644 --- a/pkg/test/step.go +++ b/pkg/test/step.go @@ -26,6 +26,8 @@ type Step struct { Name string Index int + Dir string + Step *kudo.TestStep Assert *kudo.TestAssert @@ -299,7 +301,15 @@ func (s *Step) Run(namespace string) []error { return []error{err} } - testErrors := s.Create(namespace) + testErrors := []error{} + + if s.Step != nil { + if errors := testutils.RunKubectlCommands(s.Logger, namespace, s.Step.Kubectl, s.Dir); errors != nil { + testErrors = append(testErrors, errors...) + } + } + + testErrors = append(testErrors, s.Create(namespace)...) if len(testErrors) != 0 { return testErrors diff --git a/pkg/test/test_data/cli-test/00-assert.yaml b/pkg/test/test_data/cli-test/00-assert.yaml new file mode 100644 index 000000000..8314c5c5d --- /dev/null +++ b/pkg/test/test_data/cli-test/00-assert.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Pod +metadata: + name: cli-test-pod diff --git a/pkg/test/test_data/cli-test/00-create-pod.yaml b/pkg/test/test_data/cli-test/00-create-pod.yaml new file mode 100644 index 000000000..6a5841ffb --- /dev/null +++ b/pkg/test/test_data/cli-test/00-create-pod.yaml @@ -0,0 +1,4 @@ +apiVersion: kudo.dev/v1alpha1 +kind: TestStep +kubectl: +- apply -f ./test_data/pod.yaml diff --git a/pkg/test/test_data/cli-test/test_data/pod.yaml b/pkg/test/test_data/cli-test/test_data/pod.yaml new file mode 100644 index 000000000..5aeef1a4a --- /dev/null +++ b/pkg/test/test_data/cli-test/test_data/pod.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Pod +metadata: + name: cli-test-pod +spec: + containers: + - name: cli-test + image: alpine + restartPolicy: Never diff --git a/pkg/test/test_data/with-overrides/README.md b/pkg/test/test_data/with-overrides/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/test/utils/kubernetes.go b/pkg/test/utils/kubernetes.go index dbb49352d..5a5ae2c9e 100644 --- a/pkg/test/utils/kubernetes.go +++ b/pkg/test/utils/kubernetes.go @@ -11,11 +11,13 @@ import ( "io" "log" "os" + "os/exec" "path/filepath" "strings" "sync" "time" + "github.com/google/shlex" "github.com/kudobuilder/kudo/pkg/apis" kudo "github.com/kudobuilder/kudo/pkg/apis/kudo/v1alpha1" "github.com/pkg/errors" @@ -27,6 +29,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/json" @@ -42,6 +45,7 @@ import ( "k8s.io/client-go/rest" "k8s.io/client-go/restmapper" coretesting "k8s.io/client-go/testing" + api "k8s.io/client-go/tools/clientcmd/api/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" kindConfig "sigs.k8s.io/kind/pkg/cluster/config" @@ -781,3 +785,158 @@ func StartTestEnvironment() (env TestEnvironment, err error) { env.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(env.Config) return } + +// GetKubectlArgs parses a kubectl command line string into its arguments and appends a namespace if it is not already set. +func GetKubectlArgs(args string, namespace string) ([]string, error) { + argSlice := []string{} + + argSplit, err := shlex.Split(args) + if err != nil { + return nil, err + } + + fs := pflag.NewFlagSet("", pflag.ContinueOnError) + fs.ParseErrorsWhitelist.UnknownFlags = true + + namespaceParsed := fs.StringP("namespace", "n", "", "") + if err := fs.Parse(argSplit); err != nil { + return nil, err + } + + if argSplit[0] != "kubectl" { + argSlice = append(argSlice, "kubectl") + } + + argSlice = append(argSlice, argSplit...) + + if *namespaceParsed == "" { + argSlice = append(argSlice, "--namespace", namespace) + } + + return argSlice, nil +} + +// Kubectl runs a kubectl command (or plugin) with args. +// args gets split on spaces (respecting quoted strings). +func Kubectl(ctx context.Context, namespace string, args string, cwd string, stdout io.Writer, stderr io.Writer) error { + actualDir, err := os.Getwd() + if err != nil { + return err + } + + argSlice, err := GetKubectlArgs(args, namespace) + if err != nil { + return err + } + + cmd := exec.Command("kubectl") + cmd.Args = argSlice + cmd.Dir = cwd + cmd.Stdout = stdout + cmd.Stderr = stderr + cmd.Env = []string{ + fmt.Sprintf("KUBECONFIG=%s/kubeconfig", actualDir), + fmt.Sprintf("PATH=%s/bin/:%s", actualDir, os.Getenv("PATH")), + } + + return cmd.Run() +} + +// RunKubectlCommands runs a set of kubectl commands, returning any errors. +func RunKubectlCommands(logger Logger, namespace string, commands []string, workdir string) []error { + errs := []error{} + + if commands == nil { + return nil + } + + for _, cmd := range commands { + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + + logger.Log("Running kubectl:", cmd) + + err := Kubectl(context.TODO(), namespace, cmd, workdir, stdout, stderr) + if err != nil { + errs = append(errs, err) + } + + logger.Log(stderr.String()) + logger.Log(stdout.String()) + } + + if len(errs) == 0 { + return nil + } + + return errs +} + +// Kubeconfig converts a rest.Config into a YAML kubeconfig and writes it to w +func Kubeconfig(cfg *rest.Config, w io.Writer) error { + var authProvider *api.AuthProviderConfig + var execConfig *api.ExecConfig + + if cfg.AuthProvider != nil { + authProvider = &api.AuthProviderConfig{ + Name: cfg.AuthProvider.Name, + Config: cfg.AuthProvider.Config, + } + } + + if cfg.ExecProvider != nil { + execConfig = &api.ExecConfig{ + Command: cfg.ExecProvider.Command, + Args: cfg.ExecProvider.Args, + APIVersion: cfg.ExecProvider.APIVersion, + Env: []api.ExecEnvVar{}, + } + + for _, envVar := range cfg.ExecProvider.Env { + execConfig.Env = append(execConfig.Env, api.ExecEnvVar{ + Name: envVar.Name, + Value: envVar.Value, + }) + } + } + + return json.NewYAMLSerializer(json.DefaultMetaFactory, nil, nil).Encode(&api.Config{ + CurrentContext: "cluster", + Clusters: []api.NamedCluster{ + { + Name: "cluster", + Cluster: api.Cluster{ + Server: cfg.Host, + CertificateAuthorityData: cfg.TLSClientConfig.CAData, + InsecureSkipTLSVerify: cfg.TLSClientConfig.Insecure, + }, + }, + }, + Contexts: []api.NamedContext{ + { + Name: "cluster", + Context: api.Context{ + Cluster: "cluster", + AuthInfo: "user", + }, + }, + }, + AuthInfos: []api.NamedAuthInfo{ + { + Name: "user", + AuthInfo: api.AuthInfo{ + ClientCertificateData: cfg.TLSClientConfig.CertData, + ClientKeyData: cfg.TLSClientConfig.KeyData, + Token: cfg.BearerToken, + Username: cfg.Username, + Password: cfg.Password, + Impersonate: cfg.Impersonate.UserName, + ImpersonateGroups: cfg.Impersonate.Groups, + ImpersonateUserExtra: cfg.Impersonate.Extra, + AuthProvider: authProvider, + Exec: execConfig, + }, + }, + }, + }, w) +} diff --git a/pkg/test/utils/kubernetes_test.go b/pkg/test/utils/kubernetes_test.go index b32c10aca..ad99d79ab 100644 --- a/pkg/test/utils/kubernetes_test.go +++ b/pkg/test/utils/kubernetes_test.go @@ -234,3 +234,171 @@ metadata: assert.True(t, MatchesKind(objs[1], crd, pod)) assert.False(t, MatchesKind(objs[1], svc, pod)) } + +func TestGetKubectlArgs(t *testing.T) { + for _, test := range []struct { + testName string + namespace string + args string + expected []string + }{ + { + testName: "namespace long, combined already set at end is not modified", + namespace: "default", + args: "kudo test --namespace=test-canary", + expected: []string{ + "kubectl", "kudo", "test", "--namespace=test-canary", + }, + }, + { + testName: "namespace long already set at end is not modified", + namespace: "default", + args: "kudo test --namespace test-canary", + expected: []string{ + "kubectl", "kudo", "test", "--namespace", "test-canary", + }, + }, + { + testName: "namespace short, combined already set at end is not modified", + namespace: "default", + args: "kudo test -n=test-canary", + expected: []string{ + "kubectl", "kudo", "test", "-n=test-canary", + }, + }, + { + testName: "namespace short already set at end is not modified", + namespace: "default", + args: "kudo test -n test-canary", + expected: []string{ + "kubectl", "kudo", "test", "-n", "test-canary", + }, + }, + { + testName: "namespace long, combined already set at beginning is not modified", + namespace: "default", + args: "--namespace=test-canary kudo test", + expected: []string{ + "kubectl", "--namespace=test-canary", "kudo", "test", + }, + }, + { + testName: "namespace long already set at beginning is not modified", + namespace: "default", + args: "--namespace test-canary kudo test", + expected: []string{ + "kubectl", "--namespace", "test-canary", "kudo", "test", + }, + }, + { + testName: "namespace short, combined already set at beginning is not modified", + namespace: "default", + args: "-n=test-canary kudo test", + expected: []string{ + "kubectl", "-n=test-canary", "kudo", "test", + }, + }, + { + testName: "namespace short already set at beginning is not modified", + namespace: "default", + args: "-n test-canary kudo test", + expected: []string{ + "kubectl", "-n", "test-canary", "kudo", "test", + }, + }, + { + testName: "namespace long, combined already set in middle is not modified", + namespace: "default", + args: "kudo --namespace=test-canary test", + expected: []string{ + "kubectl", "kudo", "--namespace=test-canary", "test", + }, + }, + { + testName: "namespace long already set in middle is not modified", + namespace: "default", + args: "kudo --namespace test-canary test", + expected: []string{ + "kubectl", "kudo", "--namespace", "test-canary", "test", + }, + }, + { + testName: "namespace short, combined already set in middle is not modified", + namespace: "default", + args: "kudo -n=test-canary test", + expected: []string{ + "kubectl", "kudo", "-n=test-canary", "test", + }, + }, + { + testName: "namespace short already set in middle is not modified", + namespace: "default", + args: "kudo -n test-canary test", + expected: []string{ + "kubectl", "kudo", "-n", "test-canary", "test", + }, + }, + { + testName: "namespace not set is appended", + namespace: "default", + args: "kudo test", + expected: []string{ + "kubectl", "kudo", "test", "--namespace", "default", + }, + }, + { + testName: "unknown arguments do not break parsing with namespace is not set", + namespace: "default", + args: "kudo test --config kudo-test.yaml", + expected: []string{ + "kubectl", "kudo", "test", "--config", "kudo-test.yaml", "--namespace", "default", + }, + }, + { + testName: "unknown arguments do not break parsing if namespace is set at beginning", + namespace: "default", + args: "--namespace=test-canary kudo test --config kudo-test.yaml", + expected: []string{ + "kubectl", "--namespace=test-canary", "kudo", "test", "--config", "kudo-test.yaml", + }, + }, + { + testName: "unknown arguments do not break parsing if namespace is set at middle", + namespace: "default", + args: "kudo --namespace=test-canary test --config kudo-test.yaml", + expected: []string{ + "kubectl", "kudo", "--namespace=test-canary", "test", "--config", "kudo-test.yaml", + }, + }, + { + testName: "unknown arguments do not break parsing if namespace is set at end", + namespace: "default", + args: "kudo test --config kudo-test.yaml --namespace=test-canary", + expected: []string{ + "kubectl", "kudo", "test", "--config", "kudo-test.yaml", "--namespace=test-canary", + }, + }, + { + testName: "quotes are respected when parsing", + namespace: "default", + args: "kudo \"test quoted\"", + expected: []string{ + "kubectl", "kudo", "test quoted", "--namespace", "default", + }, + }, + { + testName: "kubectl is not pre-pended if it is already present", + namespace: "default", + args: "kubectl kudo test", + expected: []string{ + "kubectl", "kudo", "test", "--namespace", "default", + }, + }, + } { + t.Run(test.testName, func(t *testing.T) { + args, err := GetKubectlArgs(test.args, test.namespace) + assert.Nil(t, err) + assert.Equal(t, test.expected, args) + }) + } +} diff --git a/test/integration/cli-install/00-assert.yaml b/test/integration/cli-install/00-assert.yaml new file mode 100644 index 000000000..31035c523 --- /dev/null +++ b/test/integration/cli-install/00-assert.yaml @@ -0,0 +1,20 @@ +apiVersion: kudo.dev/v1alpha1 +kind: Instance +metadata: + labels: + kudo.dev/operator: cli-install-operator +spec: + operatorVersion: + name: cli-install-operator-0.1.0 +status: + status: COMPLETE + activePlan: + apiVersion: kudo.dev/v1alpha1 + kind: PlanExecution +--- +apiVersion: v1 +kind: Service +metadata: + labels: + memory: "1Gi" + cpu: "0.25" diff --git a/test/integration/cli-install/00-install.yaml b/test/integration/cli-install/00-install.yaml new file mode 100644 index 000000000..0cac620d6 --- /dev/null +++ b/test/integration/cli-install/00-install.yaml @@ -0,0 +1,4 @@ +apiVersion: kudo.dev/v1alpha1 +kind: TestStep +kubectl: +- kudo install --instance cli-install ./cli-install-operator diff --git a/test/integration/cli-install/01-assert.yaml b/test/integration/cli-install/01-assert.yaml new file mode 100644 index 000000000..f55499cc8 --- /dev/null +++ b/test/integration/cli-install/01-assert.yaml @@ -0,0 +1,22 @@ +apiVersion: kudo.dev/v1alpha1 +kind: Instance +metadata: + labels: + kudo.dev/operator: cli-install-operator + name: cli-install +spec: + operatorVersion: + name: cli-install-operator-0.1.0 +status: + status: COMPLETE + activePlan: + apiVersion: kudo.dev/v1alpha1 + kind: PlanExecution +--- +apiVersion: v1 +kind: Service +metadata: + labels: + memory: "2Gi" + cpu: "0.25" + name: cli-install-cli-install-operator diff --git a/test/integration/cli-install/01-upgrade.yaml b/test/integration/cli-install/01-upgrade.yaml new file mode 100644 index 000000000..42f6a22f2 --- /dev/null +++ b/test/integration/cli-install/01-upgrade.yaml @@ -0,0 +1,11 @@ +apiVersion: kudo.dev/v1alpha1 +kind: Instance +metadata: + name: cli-install + labels: + kudo.dev/operator: cli-install-operator +spec: + operatorVersion: + name: cli-install-operator-0.1.0 + parameters: + memory: "2Gi" diff --git a/test/integration/cli-install/cli-install-operator/operator.yaml b/test/integration/cli-install/cli-install-operator/operator.yaml new file mode 100644 index 000000000..6b1449dd0 --- /dev/null +++ b/test/integration/cli-install/cli-install-operator/operator.yaml @@ -0,0 +1,20 @@ +name: "cli-install-operator" +version: "0.1.0" +kudoVersion: 0.3.0 +kubernetesVersion: 1.5.1 +maintainers: + - Justin Taylor-Barrick +tasks: + infra: + resources: + - deployment.yaml +plans: + deploy: + strategy: serial + phases: + - name: infra + strategy: parallel + steps: + - name: everything + tasks: + - infra diff --git a/test/integration/cli-install/cli-install-operator/params.yaml b/test/integration/cli-install/cli-install-operator/params.yaml new file mode 100644 index 000000000..e3d2b2884 --- /dev/null +++ b/test/integration/cli-install/cli-install-operator/params.yaml @@ -0,0 +1,6 @@ +memory: + description: Amount of memory to provide to Zookeeper pods + default: "1Gi" +cpus: + description: Amount of cpu to provide to Zookeeper pods + default: "0.25" diff --git a/test/integration/cli-install/cli-install-operator/templates/deployment.yaml b/test/integration/cli-install/cli-install-operator/templates/deployment.yaml new file mode 100644 index 000000000..ef9c1afcb --- /dev/null +++ b/test/integration/cli-install/cli-install-operator/templates/deployment.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .OperatorName }} + namespace: {{ .Namespace }} + labels: + memory: "{{ .Params.memory }}" + cpu: "{{ .Params.cpus }}" +spec: + ports: + - port: 8080 + name: {{ .Name }}