From 611bac67cf6dc8b58130b9cb43486d4ddda1b387 Mon Sep 17 00:00:00 2001 From: Inteon <42113979+inteon@users.noreply.github.com> Date: Thu, 8 Jul 2021 16:28:38 +0200 Subject: [PATCH 1/2] Add basic test cases Signed-off-by: Inteon <42113979+inteon@users.noreply.github.com> --- cmd/ctl/pkg/install/install.go | 2 +- test/integration/ctl/BUILD.bazel | 20 ++- test/integration/ctl/ctl_install.go | 144 ++++++++++++++++++ .../ctl/install_framework/BUILD.bazel | 28 ++++ .../ctl/install_framework/framework.go | 109 +++++++++++++ 5 files changed, 300 insertions(+), 3 deletions(-) create mode 100644 test/integration/ctl/ctl_install.go create mode 100644 test/integration/ctl/install_framework/BUILD.bazel create mode 100644 test/integration/ctl/install_framework/framework.go diff --git a/cmd/ctl/pkg/install/install.go b/cmd/ctl/pkg/install/install.go index ee661c0e7..b2cd56062 100644 --- a/cmd/ctl/pkg/install/install.go +++ b/cmd/ctl/pkg/install/install.go @@ -230,7 +230,7 @@ func (o *InstallOptions) runInstall(ctx context.Context) (*release.Release, erro // Install chart o.client.DryRun = false // Apply DryRun cli flags o.client.ClientOnly = false // Perform install against cluster - o.client.Atomic = true // If part of the install fails, also undo other installed resources + o.client.Atomic = o.client.Wait // If part of the install fails, also undo other installed resources chartValues[installCRDsFlagName] = false // Do not render CRDs, as this might cause problems when uninstalling using helm return o.client.Run(chart, chartValues) diff --git a/test/integration/ctl/BUILD.bazel b/test/integration/ctl/BUILD.bazel index 7902fcea2..b8e3d76db 100644 --- a/test/integration/ctl/BUILD.bazel +++ b/test/integration/ctl/BUILD.bazel @@ -1,4 +1,4 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_test") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_test( name = "go_default_test", @@ -9,6 +9,7 @@ go_test( "ctl_status_certificate_test.go", ], data = glob(["testdata/**"]), + embed = [":go_default_library"], deps = [ "//cmd/ctl/pkg/convert:go_default_library", "//cmd/ctl/pkg/create/certificaterequest:go_default_library", @@ -42,7 +43,22 @@ filegroup( filegroup( name = "all-srcs", - srcs = [":package-srcs"], + srcs = [ + ":package-srcs", + "//test/integration/ctl/install_framework:all-srcs", + ], tags = ["automanaged"], visibility = ["//visibility:public"], ) + +go_library( + name = "go_default_library", + srcs = ["ctl_install.go"], + importpath = "github.com/jetstack/cert-manager/test/integration/ctl", + visibility = ["//visibility:public"], + deps = [ + "//cmd/ctl/cmd:go_default_library", + "//test/integration/ctl/install_framework:go_default_library", + "@com_github_sergi_go_diff//diffmatchpatch:go_default_library", + ], +) diff --git a/test/integration/ctl/ctl_install.go b/test/integration/ctl/ctl_install.go new file mode 100644 index 000000000..887b3db3b --- /dev/null +++ b/test/integration/ctl/ctl_install.go @@ -0,0 +1,144 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ctl + +import ( + "bytes" + "context" + "fmt" + "regexp" + "strings" + "testing" + "time" + + "github.com/sergi/go-diff/diffmatchpatch" + + "github.com/jetstack/cert-manager/cmd/ctl/cmd" + "github.com/jetstack/cert-manager/test/integration/ctl/install_framework" +) + +func TestCtlInstall(t *testing.T) { + tests := map[string]struct { + prerun bool + preInputArgs []string + preExpErr bool + preExpOutput string + + inputArgs []string + expErr bool + expOutput string + }{ + "install cert-manager": { + inputArgs: []string{}, + expErr: false, + expOutput: `STATUS: deployed`, + }, + "install cert-manager (already installed)": { + prerun: true, + preInputArgs: []string{}, + preExpErr: false, + preExpOutput: `STATUS: deployed`, + + inputArgs: []string{}, + expErr: true, + expOutput: `^Found existing installed cert-manager CRDs! Cannot continue with installation.$`, + }, + "install cert-manager (already installed, in other namespace)": { + prerun: true, + preInputArgs: []string{"--namespace=test"}, + preExpErr: false, + preExpOutput: `STATUS: deployed`, + + inputArgs: []string{}, + expErr: true, + expOutput: `^Found existing installed cert-manager CRDs! Cannot continue with installation.$`, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + testApiServer, cleanup := install_framework.NewTestInstallApiServer(t) + defer cleanup() + + ctx, cancel := context.WithTimeout(context.TODO(), time.Second*20) + defer cancel() + + if test.prerun { + executeCommandAndCheckOutput(t, ctx, testApiServer.KubeConfig(), test.preInputArgs, test.preExpErr, test.preExpOutput) + } + + executeCommandAndCheckOutput(t, ctx, testApiServer.KubeConfig(), test.inputArgs, test.expErr, test.expOutput) + }) + } +} + +func executeCommandAndCheckOutput( + t *testing.T, + ctx context.Context, + kubeConfig string, + inputArgs []string, + expErr bool, + expOutput string, +) { + // Options to run status command + stdin := bytes.NewBufferString("") + stdout := bytes.NewBufferString("") + + cmd := cmd.NewCertManagerCtlCommand(ctx, stdin, stdout, stdout) + cmd.SetArgs(append([]string{ + fmt.Sprintf("--kubeconfig=%s", kubeConfig), + "--wait=false", + "x", + "install", + }, inputArgs...)) + + err := cmd.Execute() + if err != nil { + fmt.Fprintf(stdout, "%s\n", err) + + if !expErr { + t.Errorf("got unexpected error: %v", err) + } else { + t.Logf("got an error, which was expected, details: %v", err) + } + } else if expErr { + // expected error but error is nil + t.Errorf("expected but got no error") + } + + match, err := regexp.MatchString(strings.TrimSpace(expOutput), strings.TrimSpace(stdout.String())) + if err != nil { + t.Error(err) + } + dmp := diffmatchpatch.New() + if !match { + diffs := dmp.DiffMain(strings.TrimSpace(expOutput), strings.TrimSpace(stdout.String()), false) + t.Errorf( + "got unexpected output, diff (ignoring line anchors ^ and $ and regex for creation time):\n"+ + "%s\n"+ + "\n"+ + "expected: \n"+ + "%s\n"+ + "\n"+ + "got: \n"+ + "%s", + dmp.DiffPrettyText(diffs), + expOutput, + stdout.String(), + ) + } +} diff --git a/test/integration/ctl/install_framework/BUILD.bazel b/test/integration/ctl/install_framework/BUILD.bazel new file mode 100644 index 000000000..5d9a6cd4c --- /dev/null +++ b/test/integration/ctl/install_framework/BUILD.bazel @@ -0,0 +1,28 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["framework.go"], + importpath = "github.com/jetstack/cert-manager/test/integration/ctl/install_framework", + visibility = ["//visibility:public"], + deps = [ + "//test/internal/apiserver:go_default_library", + "@io_k8s_client_go//kubernetes:go_default_library", + "@io_k8s_client_go//rest:go_default_library", + "@io_k8s_sigs_controller_runtime//pkg/envtest:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/test/integration/ctl/install_framework/framework.go b/test/integration/ctl/install_framework/framework.go new file mode 100644 index 000000000..bebeafcd0 --- /dev/null +++ b/test/integration/ctl/install_framework/framework.go @@ -0,0 +1,109 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package install_framework + +import ( + "io/ioutil" + "os" + "testing" + + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/envtest" + + "github.com/jetstack/cert-manager/test/internal/apiserver" +) + +type TestInstallApiServer struct { + environment *envtest.Environment + testUser *envtest.AuthenticatedUser + + kubeClient kubernetes.Interface + + kubeConfig string +} + +type CleanupFunction func() + +func NewTestInstallApiServer(t *testing.T) (*TestInstallApiServer, CleanupFunction) { + env, stopFn := apiserver.RunBareControlPlane(t) + + testUser, err := env.ControlPlane.AddUser( + envtest.User{ + Name: "test", + Groups: []string{"system:masters"}, + }, + &rest.Config{ + // gotta go fast during tests -- we don't really care about overwhelming our test API server + QPS: 1000.0, + Burst: 2000.0, + }, + ) + if err != nil { + t.Error(err) + } + + kubeConfig, removeFile := createKubeConfigFile(t, testUser) + + kubeClientset, err := kubernetes.NewForConfig(env.Config) + if err != nil { + t.Error(err) + } + + return &TestInstallApiServer{ + environment: env, + testUser: testUser, + + kubeClient: kubeClientset, + + kubeConfig: kubeConfig, + }, func() { + defer removeFile() + stopFn() + } +} + +func createKubeConfigFile(t *testing.T, user *envtest.AuthenticatedUser) (string, CleanupFunction) { + tmpfile, err := ioutil.TempFile("", "config") + if err != nil { + t.Fatal(err) + } + path := tmpfile.Name() + + contents, err := user.KubeConfig() + if err != nil { + os.Remove(path) + t.Fatal(err) + } + if _, err := tmpfile.Write(contents); err != nil { + tmpfile.Close() + os.Remove(path) + t.Fatal(err) + } + if err := tmpfile.Close(); err != nil { + os.Remove(path) + t.Fatal(err) + } + + return path, func() { + os.Remove(path) + } +} + +func (s *TestInstallApiServer) KubeConfig() string { + return s.kubeConfig +} From 0ed01359306e89a4fab56d659c04d08796aaaa32 Mon Sep 17 00:00:00 2001 From: Inteon <42113979+inteon@users.noreply.github.com> Date: Fri, 23 Jul 2021 15:29:43 +0200 Subject: [PATCH 2/2] Improvements based on reviewer's feedback Signed-off-by: Inteon <42113979+inteon@users.noreply.github.com> --- cmd/ctl/pkg/install/install.go | 11 ++++++++--- test/integration/ctl/ctl_install.go | 10 +++------- test/integration/ctl/install_framework/framework.go | 3 +-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cmd/ctl/pkg/install/install.go b/cmd/ctl/pkg/install/install.go index b2cd56062..b5faae64d 100644 --- a/cmd/ctl/pkg/install/install.go +++ b/cmd/ctl/pkg/install/install.go @@ -228,9 +228,14 @@ func (o *InstallOptions) runInstall(ctx context.Context) (*release.Release, erro } // Install chart - o.client.DryRun = false // Apply DryRun cli flags - o.client.ClientOnly = false // Perform install against cluster - o.client.Atomic = o.client.Wait // If part of the install fails, also undo other installed resources + o.client.DryRun = false // Apply DryRun cli flags + o.client.ClientOnly = false // Perform install against cluster + // 'Atomic=True' means that if part of the install fails, all resource installs are reverted; + // Helm supports 3 diffent combinations of the (Atomic, Wait) boolean couple: + // (False, False), (False, True) or (True, True) + // For simplicity, we want do not support Waiting without the Atomic option (False, True), + // this allows this cli to use a single --wait=(True|False) flag + o.client.Atomic = o.client.Wait chartValues[installCRDsFlagName] = false // Do not render CRDs, as this might cause problems when uninstalling using helm return o.client.Run(chart, chartValues) diff --git a/test/integration/ctl/ctl_install.go b/test/integration/ctl/ctl_install.go index 887b3db3b..1ea2b00c6 100644 --- a/test/integration/ctl/ctl_install.go +++ b/test/integration/ctl/ctl_install.go @@ -129,13 +129,9 @@ func executeCommandAndCheckOutput( diffs := dmp.DiffMain(strings.TrimSpace(expOutput), strings.TrimSpace(stdout.String()), false) t.Errorf( "got unexpected output, diff (ignoring line anchors ^ and $ and regex for creation time):\n"+ - "%s\n"+ - "\n"+ - "expected: \n"+ - "%s\n"+ - "\n"+ - "got: \n"+ - "%s", + "diff: %s\n\n"+ + " exp: %s\n\n"+ + " got: %s", dmp.DiffPrettyText(diffs), expOutput, stdout.String(), diff --git a/test/integration/ctl/install_framework/framework.go b/test/integration/ctl/install_framework/framework.go index bebeafcd0..95ef00488 100644 --- a/test/integration/ctl/install_framework/framework.go +++ b/test/integration/ctl/install_framework/framework.go @@ -17,7 +17,6 @@ limitations under the License. package install_framework import ( - "io/ioutil" "os" "testing" @@ -78,7 +77,7 @@ func NewTestInstallApiServer(t *testing.T) (*TestInstallApiServer, CleanupFuncti } func createKubeConfigFile(t *testing.T, user *envtest.AuthenticatedUser) (string, CleanupFunction) { - tmpfile, err := ioutil.TempFile("", "config") + tmpfile, err := os.CreateTemp("", "config") if err != nil { t.Fatal(err) }