From 260f160f0f1fe709ab211bfea563c6623cb3625f Mon Sep 17 00:00:00 2001 From: fossedihelm Date: Mon, 3 Jan 2022 09:22:14 +0100 Subject: [PATCH] virtctl: warning for server/client version diverged When a virtctl command returns an error we check if the server and client versions are aligned: if not, show a warning message suggesting, implicitly, that a possible cause could be this difference Signed-off-by: fossedihelm --- cmd/virt-chroot/main.go | 4 +- pkg/virtctl/BUILD.bazel | 1 + pkg/virtctl/root.go | 14 +++-- pkg/virtctl/version/BUILD.bazel | 23 +++++++- pkg/virtctl/version/version.go | 49 +++++++++++++++-- pkg/virtctl/version/version_suite_test.go | 11 ++++ pkg/virtctl/version/version_test.go | 54 +++++++++++++++++++ .../kubecli/generated_mock_kubevirt.go | 37 ++++++++++++- .../kubevirt.io/client-go/kubecli/kubevirt.go | 8 ++- .../kubevirt.io/client-go/kubecli/version.go | 2 +- tests/utils.go | 2 +- 11 files changed, 188 insertions(+), 17 deletions(-) create mode 100644 pkg/virtctl/version/version_suite_test.go create mode 100644 pkg/virtctl/version/version_test.go diff --git a/cmd/virt-chroot/main.go b/cmd/virt-chroot/main.go index 69e883d73adc..e934420a29c4 100644 --- a/cmd/virt-chroot/main.go +++ b/cmd/virt-chroot/main.go @@ -106,7 +106,7 @@ func main() { }, Run: func(cmd *cobra.Command, args []string) { - _, _ = fmt.Fprint(cmd.OutOrStderr(), cmd.UsageString()) + cmd.Printf(cmd.UsageString()) }, } @@ -169,7 +169,7 @@ func main() { Short: "run selinux operations in specific namespaces", Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { - _, _ = fmt.Fprint(cmd.OutOrStderr(), cmd.UsageString()) + cmd.Printf(cmd.UsageString()) }, } diff --git a/pkg/virtctl/BUILD.bazel b/pkg/virtctl/BUILD.bazel index af4f8d5ded41..b573a58e6bd5 100644 --- a/pkg/virtctl/BUILD.bazel +++ b/pkg/virtctl/BUILD.bazel @@ -27,6 +27,7 @@ go_library( "//staging/src/kubevirt.io/client-go/log:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/pflag:go_default_library", + "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", ], ) diff --git a/pkg/virtctl/root.go b/pkg/virtctl/root.go index c942d69fb692..3175b51d996c 100644 --- a/pkg/virtctl/root.go +++ b/pkg/virtctl/root.go @@ -6,6 +6,8 @@ import ( "path/filepath" "strings" + "k8s.io/client-go/tools/clientcmd" + "github.com/spf13/cobra" "kubevirt.io/client-go/kubecli" @@ -28,7 +30,7 @@ import ( var programName string -func NewVirtctlCommand() *cobra.Command { +func NewVirtctlCommand() (*cobra.Command, clientcmd.ClientConfig) { programName := GetProgramName(filepath.Base(os.Args[0])) @@ -55,7 +57,7 @@ func NewVirtctlCommand() *cobra.Command { SilenceUsage: true, SilenceErrors: true, Run: func(cmd *cobra.Command, args []string) { - fmt.Fprint(cmd.OutOrStderr(), cmd.UsageString()) + cmd.Printf(cmd.UsageString()) }, } @@ -63,7 +65,7 @@ func NewVirtctlCommand() *cobra.Command { Use: "options", Hidden: true, Run: func(cmd *cobra.Command, args []string) { - fmt.Fprint(cmd.OutOrStderr(), cmd.UsageString()) + cmd.Printf(cmd.UsageString()) }, } optionsCmd.SetUsageTemplate(templates.OptionsUsageTemplate()) @@ -71,6 +73,7 @@ func NewVirtctlCommand() *cobra.Command { clientConfig := kubecli.DefaultClientConfig(rootCmd.PersistentFlags()) AddGlogFlags(rootCmd.PersistentFlags()) rootCmd.SetUsageTemplate(templates.MainUsageTemplate()) + rootCmd.SetOut(os.Stdout) rootCmd.AddCommand( configuration.NewListPermittedDevices(clientConfig), console.NewCommand(clientConfig), @@ -97,7 +100,7 @@ func NewVirtctlCommand() *cobra.Command { guestfs.NewGuestfsShellCommand(clientConfig), optionsCmd, ) - return rootCmd + return rootCmd, clientConfig } // GetProgramName returns the command name to display in help texts. @@ -115,8 +118,9 @@ func GetProgramName(binary string) string { func Execute() { log.InitializeLogging(programName) - cmd := NewVirtctlCommand() + cmd, clientConfig := NewVirtctlCommand() if err := cmd.Execute(); err != nil { + version.CheckClientServerVersion(&clientConfig) fmt.Fprintln(cmd.Root().ErrOrStderr(), strings.TrimSpace(err.Error())) os.Exit(1) } diff --git a/pkg/virtctl/version/BUILD.bazel b/pkg/virtctl/version/BUILD.bazel index 12b0abcaf1ea..4b89e8f24520 100644 --- a/pkg/virtctl/version/BUILD.bazel +++ b/pkg/virtctl/version/BUILD.bazel @@ -1,4 +1,5 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") +load("//staging/src/kubevirt.io/client-go/version:def.bzl", "version_x_defs") go_library( name = "go_default_library", @@ -9,7 +10,27 @@ go_library( "//pkg/virtctl/templates:go_default_library", "//staging/src/kubevirt.io/client-go/kubecli:go_default_library", "//staging/src/kubevirt.io/client-go/version:go_default_library", + "//vendor/github.com/blang/semver:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", ], ) + +go_test( + name = "go_default_test", + srcs = [ + "version_suite_test.go", + "version_test.go", + ], + x_defs = version_x_defs(), + deps = [ + ":go_default_library", + "//pkg/virtctl:go_default_library", + "//staging/src/kubevirt.io/client-go/kubecli:go_default_library", + "//staging/src/kubevirt.io/client-go/testutils:go_default_library", + "//staging/src/kubevirt.io/client-go/version:go_default_library", + "//vendor/github.com/golang/mock/gomock:go_default_library", + "//vendor/github.com/onsi/ginkgo/v2:go_default_library", + "//vendor/github.com/onsi/gomega:go_default_library", + ], +) diff --git a/pkg/virtctl/version/version.go b/pkg/virtctl/version/version.go index 2b45da879613..30d45d53ca1d 100644 --- a/pkg/virtctl/version/version.go +++ b/pkg/virtctl/version/version.go @@ -2,6 +2,9 @@ package version import ( "fmt" + "strings" + + "github.com/blang/semver" "github.com/spf13/cobra" "k8s.io/client-go/tools/clientcmd" @@ -11,10 +14,15 @@ import ( "kubevirt.io/kubevirt/pkg/virtctl/templates" ) -var clientOnly bool +var ( + cmd *cobra.Command + clientOnly bool +) + +const versionsNotAlignedWarnMessage = "You are using a client virtctl version that is different from the KubeVirt version running in the cluster\nClient Version: %s\nServer Version: %s\n" func VersionCommand(clientConfig clientcmd.ClientConfig) *cobra.Command { - cmd := &cobra.Command{ + cmd = &cobra.Command{ Use: "version", Short: "Print the client and server version information.", Example: usage(), @@ -40,7 +48,7 @@ type Version struct { } func (v *Version) Run() error { - fmt.Printf("Client Version: %s\n", fmt.Sprintf("%#v", version.Get())) + cmd.Printf("Client Version: %s\n", fmt.Sprintf("%#v", version.Get())) if !clientOnly { virCli, err := kubecli.GetKubevirtClientFromClientConfig(v.clientConfig) @@ -53,8 +61,41 @@ func (v *Version) Run() error { return err } - fmt.Printf("Server Version: %s\n", fmt.Sprintf("%#v", *serverInfo)) + cmd.Printf("Server Version: %s\n", fmt.Sprintf("%#v", *serverInfo)) } return nil } + +func CheckClientServerVersion(clientConfig *clientcmd.ClientConfig) { + clientVersion := version.Get() + virCli, err := kubecli.GetKubevirtClientFromClientConfig(*clientConfig) + if err != nil { + cmd.Println(err) + return + } + + serverVersion, err := virCli.ServerVersion().Get() + if err != nil { + cmd.Println(err) + return + } + + clientGitVersion := strings.TrimPrefix(clientVersion.GitVersion, "v") + serverGitVersion := strings.TrimPrefix(serverVersion.GitVersion, "v") + client, err := semver.Make(clientGitVersion) + if err != nil { + cmd.Println(err) + return + } + + server, err := semver.Make(serverGitVersion) + if err != nil { + cmd.Println(err) + return + } + + if client.Major != server.Major || client.Minor != server.Minor { + cmd.Printf(versionsNotAlignedWarnMessage, clientVersion, *serverVersion) + } +} diff --git a/pkg/virtctl/version/version_suite_test.go b/pkg/virtctl/version/version_suite_test.go new file mode 100644 index 000000000000..08c2b45b894f --- /dev/null +++ b/pkg/virtctl/version/version_suite_test.go @@ -0,0 +1,11 @@ +package version_test + +import ( + "testing" + + "kubevirt.io/client-go/testutils" +) + +func TestVersion(t *testing.T) { + testutils.KubeVirtTestSuiteSetup(t) +} diff --git a/pkg/virtctl/version/version_test.go b/pkg/virtctl/version/version_test.go new file mode 100644 index 000000000000..6a5d18c536d0 --- /dev/null +++ b/pkg/virtctl/version/version_test.go @@ -0,0 +1,54 @@ +package version_test + +import ( + "bytes" + "fmt" + goruntime "runtime" + "time" + + "github.com/golang/mock/gomock" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "kubevirt.io/client-go/kubecli" + virt_version "kubevirt.io/client-go/version" + "kubevirt.io/kubevirt/pkg/virtctl" + "kubevirt.io/kubevirt/pkg/virtctl/version" +) + +var _ = Describe("Version", func() { + + var ctrl *gomock.Controller + + BeforeEach(func() { + ctrl = gomock.NewController(GinkgoT()) + kubecli.GetKubevirtClientFromClientConfig = kubecli.GetMockKubevirtClientFromClientConfig + kubecli.MockKubevirtClientInstance = kubecli.NewMockKubevirtClient(ctrl) + serverVersionInterface := kubecli.NewMockServerVersionInterface(ctrl) + kubecli.MockKubevirtClientInstance.EXPECT().ServerVersion().Return(serverVersionInterface).AnyTimes() + serverVersionInterface.EXPECT().Get().Return(&virt_version.Info{ + GitVersion: "v0.46.1", + GitCommit: "fda30004223b51f9e604276419a2b376652cb5ad", + GitTreeState: "clear", + BuildDate: time.Now().Format("%Y-%m-%dT%H:%M:%SZ"), + GoVersion: goruntime.Version(), + Compiler: goruntime.Compiler, + Platform: fmt.Sprintf("%s/%s", goruntime.GOOS, goruntime.GOARCH), + }, nil, + ).AnyTimes() + }) + + Context("should print a message if server and client virtctl versions are different", func() { + It("in version command", func() { + var buf bytes.Buffer + cmd, clientConfig := virtctl.NewVirtctlCommand() + cmd.SetOut(&buf) + version.CheckClientServerVersion(&clientConfig) + //Print out the captured output to show the test output also in the console + fmt.Printf(buf.String()) + Expect(buf.String()).To(ContainSubstring("You are using a client virtctl version that is different from the KubeVirt version running in the cluster"), + "Warning message was not shown or has been changed") + }) + + }) +}) diff --git a/staging/src/kubevirt.io/client-go/kubecli/generated_mock_kubevirt.go b/staging/src/kubevirt.io/client-go/kubecli/generated_mock_kubevirt.go index 1d1d67320c9e..53bcb76406c3 100644 --- a/staging/src/kubevirt.io/client-go/kubecli/generated_mock_kubevirt.go +++ b/staging/src/kubevirt.io/client-go/kubecli/generated_mock_kubevirt.go @@ -73,6 +73,7 @@ import ( v1alpha18 "kubevirt.io/client-go/generated/kubevirt/clientset/versioned/typed/snapshot/v1alpha1" versioned2 "kubevirt.io/client-go/generated/network-attachment-definition-client/clientset/versioned" versioned3 "kubevirt.io/client-go/generated/prometheus-operator/clientset/versioned" + version "kubevirt.io/client-go/version" ) // Mock of KubevirtClient interface @@ -226,9 +227,9 @@ func (_mr *_MockKubevirtClientRecorder) MigrationPolicy() *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "MigrationPolicy") } -func (_m *MockKubevirtClient) ServerVersion() *ServerVersion { +func (_m *MockKubevirtClient) ServerVersion() ServerVersionInterface { ret := _m.ctrl.Call(_m, "ServerVersion") - ret0, _ := ret[0].(*ServerVersion) + ret0, _ := ret[0].(ServerVersionInterface) return ret0 } @@ -1781,3 +1782,35 @@ func (_m *MockKubeVirtInterface) PatchStatus(name string, pt types.PatchType, da func (_mr *_MockKubeVirtInterfaceRecorder) PatchStatus(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "PatchStatus", arg0, arg1, arg2, arg3) } + +// Mock of ServerVersionInterface interface +type MockServerVersionInterface struct { + ctrl *gomock.Controller + recorder *_MockServerVersionInterfaceRecorder +} + +// Recorder for MockServerVersionInterface (not exported) +type _MockServerVersionInterfaceRecorder struct { + mock *MockServerVersionInterface +} + +func NewMockServerVersionInterface(ctrl *gomock.Controller) *MockServerVersionInterface { + mock := &MockServerVersionInterface{ctrl: ctrl} + mock.recorder = &_MockServerVersionInterfaceRecorder{mock} + return mock +} + +func (_m *MockServerVersionInterface) EXPECT() *_MockServerVersionInterfaceRecorder { + return _m.recorder +} + +func (_m *MockServerVersionInterface) Get() (*version.Info, error) { + ret := _m.ctrl.Call(_m, "Get") + ret0, _ := ret[0].(*version.Info) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockServerVersionInterfaceRecorder) Get() *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Get") +} diff --git a/staging/src/kubevirt.io/client-go/kubecli/kubevirt.go b/staging/src/kubevirt.io/client-go/kubecli/kubevirt.go index 641ea98aa6e9..39e6db90f167 100644 --- a/staging/src/kubevirt.io/client-go/kubecli/kubevirt.go +++ b/staging/src/kubevirt.io/client-go/kubecli/kubevirt.go @@ -30,6 +30,8 @@ import ( "net" "time" + "kubevirt.io/client-go/version" + migrationsv1 "kubevirt.io/client-go/generated/kubevirt/clientset/versioned/typed/migrations/v1alpha1" secv1 "github.com/openshift/client-go/security/clientset/versioned/typed/security/v1" @@ -69,7 +71,7 @@ type KubevirtClient interface { VirtualMachineFlavor(namespace string) flavorv1alpha1.VirtualMachineFlavorInterface VirtualMachineClusterFlavor() flavorv1alpha1.VirtualMachineClusterFlavorInterface MigrationPolicy() migrationsv1.MigrationPolicyInterface - ServerVersion() *ServerVersion + ServerVersion() ServerVersionInterface ClusterProfiler() *ClusterProfiler GuestfsVersion() *GuestfsVersion RestClient() *rest.RESTClient @@ -280,3 +282,7 @@ type KubeVirtInterface interface { UpdateStatus(*v1.KubeVirt) (*v1.KubeVirt, error) PatchStatus(name string, pt types.PatchType, data []byte, patchOptions *metav1.PatchOptions) (result *v1.KubeVirt, err error) } + +type ServerVersionInterface interface { + Get() (*version.Info, error) +} diff --git a/staging/src/kubevirt.io/client-go/kubecli/version.go b/staging/src/kubevirt.io/client-go/kubecli/version.go index 938583cc5fa7..7c14ef44bb59 100644 --- a/staging/src/kubevirt.io/client-go/kubecli/version.go +++ b/staging/src/kubevirt.io/client-go/kubecli/version.go @@ -38,7 +38,7 @@ const ( ApiGroupName = "/apis/" + v1.SubresourceGroupName ) -func (k *kubevirt) ServerVersion() *ServerVersion { +func (k *kubevirt) ServerVersion() ServerVersionInterface { return &ServerVersion{ restClient: k.restClient, resource: "version", diff --git a/tests/utils.go b/tests/utils.go index af2dfbcb512f..bd4c77e8a928 100644 --- a/tests/utils.go +++ b/tests/utils.go @@ -3164,7 +3164,7 @@ func NewVirtctlCommand(args ...string) *cobra.Command { if kubeconfig != nil && kubeconfig.String() != "" { commandline = append(commandline, "--kubeconfig", kubeconfig.String()) } - cmd := virtctl.NewVirtctlCommand() + cmd, _ := virtctl.NewVirtctlCommand() cmd.SetArgs(append(commandline, args...)) return cmd }