From 79dc7a91831036de7d477eacad4708cd9b430b54 Mon Sep 17 00:00:00 2001 From: brianpursley Date: Wed, 3 Mar 2021 20:55:05 -0500 Subject: [PATCH] Add warning if client/server version difference exceeds the supported skew Kubernetes-commit: 0c28cad8d142059b488347badd46433d50e47feb --- pkg/cmd/version/skew_warning.go | 54 ++++++++++++++++ pkg/cmd/version/skew_warning_test.go | 96 ++++++++++++++++++++++++++++ pkg/cmd/version/version.go | 6 ++ 3 files changed, 156 insertions(+) create mode 100644 pkg/cmd/version/skew_warning.go create mode 100644 pkg/cmd/version/skew_warning_test.go diff --git a/pkg/cmd/version/skew_warning.go b/pkg/cmd/version/skew_warning.go new file mode 100644 index 00000000..3b9d0101 --- /dev/null +++ b/pkg/cmd/version/skew_warning.go @@ -0,0 +1,54 @@ +/* +Copyright 2021 The Kubernetes 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 version + +import ( + "fmt" + "io" + "k8s.io/apimachinery/pkg/util/version" + apimachineryversion "k8s.io/apimachinery/pkg/version" + "math" +) + +// supportedMinorVersionSkew is the maximum supported difference between the client and server minor versions. +// For example: client versions 1.18, 1.19, and 1.20 would be within the supported version skew for server version 1.19, +// and server versions 1.18, 1.19, and 1.20 would be within the supported version skew for client version 1.19. +const supportedMinorVersionSkew = 1 + +// printVersionSkewWarning prints a warning message if the difference between the client and version is greater than +// the supported version skew. +func printVersionSkewWarning(w io.Writer, clientVersion, serverVersion apimachineryversion.Info) error { + parsedClientVersion, err := version.ParseSemantic(clientVersion.GitVersion) + if err != nil { + return err + } + + parsedServerVersion, err := version.ParseSemantic(serverVersion.GitVersion) + if err != nil { + return err + } + + majorVersionDifference := math.Abs(float64(parsedClientVersion.Major()) - float64(parsedServerVersion.Major())) + minorVersionDifference := math.Abs(float64(parsedClientVersion.Minor()) - float64(parsedServerVersion.Minor())) + + if majorVersionDifference > 0 || minorVersionDifference > supportedMinorVersionSkew { + fmt.Fprintf(w, "WARNING: version difference between client (%d.%d) and server (%d.%d) exceeds the supported minor version skew of +/-%d\n", + parsedClientVersion.Major(), parsedClientVersion.Minor(), parsedServerVersion.Major(), parsedServerVersion.Minor(), supportedMinorVersionSkew) + } + + return nil +} diff --git a/pkg/cmd/version/skew_warning_test.go b/pkg/cmd/version/skew_warning_test.go new file mode 100644 index 00000000..778bdfc7 --- /dev/null +++ b/pkg/cmd/version/skew_warning_test.go @@ -0,0 +1,96 @@ +/* +Copyright 2021 The Kubernetes 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 version + +import ( + "bytes" + apimachineryversion "k8s.io/apimachinery/pkg/version" + "testing" +) + +func TestPrintVersionSkewWarning(t *testing.T) { + output := &bytes.Buffer{} + + testCases := []struct { + name string + clientVersion apimachineryversion.Info + serverVersion apimachineryversion.Info + isWarningExpected bool + }{ + { + name: "Should not warn if server and client versions are same", + clientVersion: apimachineryversion.Info{GitVersion: "v1.19.1"}, + serverVersion: apimachineryversion.Info{GitVersion: "v1.19.1"}, + isWarningExpected: false, + }, + { + name: "Should not warn if server and client versions are same and server is alpha", + clientVersion: apimachineryversion.Info{GitVersion: "v1.19.1"}, + serverVersion: apimachineryversion.Info{GitVersion: "v1.19.7-alpha"}, + isWarningExpected: false, + }, + { + name: "Should not warn if server and client versions are same and server is beta", + clientVersion: apimachineryversion.Info{GitVersion: "v1.19.1"}, + serverVersion: apimachineryversion.Info{GitVersion: "v1.19.7-beta"}, + isWarningExpected: false, + }, + { + name: "Should not warn if server is 1 minor version ahead of client", + clientVersion: apimachineryversion.Info{GitVersion: "v1.18.5"}, + serverVersion: apimachineryversion.Info{GitVersion: "v1.19.1"}, + isWarningExpected: false, + }, + { + name: "Should not warn if server is 1 minor version behind client", + clientVersion: apimachineryversion.Info{GitVersion: "v1.19.1"}, + serverVersion: apimachineryversion.Info{GitVersion: "v1.18.5"}, + isWarningExpected: false, + }, + { + name: "Should warn if server is 2 minor versions ahead of client", + clientVersion: apimachineryversion.Info{GitVersion: "v1.17.7"}, + serverVersion: apimachineryversion.Info{GitVersion: "v1.19.1"}, + isWarningExpected: true, + }, + { + name: "Should warn if server is 2 minor versions behind client", + clientVersion: apimachineryversion.Info{GitVersion: "v1.19.1"}, + serverVersion: apimachineryversion.Info{GitVersion: "v1.17.7"}, + isWarningExpected: true, + }, + { + name: "Should warn if major versions are not equal", + clientVersion: apimachineryversion.Info{GitVersion: "v1.19.1"}, + serverVersion: apimachineryversion.Info{GitVersion: "v2.19.1"}, + isWarningExpected: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + output.Reset() + + printVersionSkewWarning(output, tc.clientVersion, tc.serverVersion) + + if tc.isWarningExpected && output.Len() == 0 { + t.Error("warning was expected, but not written to the output") + } else if !tc.isWarningExpected && output.Len() > 0 { + t.Errorf("warning was not expected, but was written to the output: %s", output.String()) + } + }) + } +} diff --git a/pkg/cmd/version/version.go b/pkg/cmd/version/version.go index 7c7cf26a..7f95146c 100644 --- a/pkg/cmd/version/version.go +++ b/pkg/cmd/version/version.go @@ -158,5 +158,11 @@ func (o *Options) Run() error { return fmt.Errorf("VersionOptions were not validated: --output=%q should have been rejected", o.Output) } + if serverVersion != nil { + if err := printVersionSkewWarning(o.ErrOut, clientVersion, *serverVersion); err != nil { + return err + } + } + return serverErr }