Kubectl: check version skew (#127365)

Signed-off-by: Omer Aplatony <omerap12@gmail.com>

Kubernetes-commit: 35307319740a3a52cf4632c24b7f99d675537bdf
This commit is contained in:
Omer Aplatony 2025-05-19 20:19:14 +03:00 committed by Kubernetes Publisher
parent 4afda566a9
commit 5ff92a69e3
4 changed files with 40 additions and 28 deletions

View File

@ -18,10 +18,10 @@ package version
import (
"fmt"
"io"
"math"
"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.
@ -29,26 +29,26 @@ import (
// 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
// getVersionSkewWarning returns 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 {
func getVersionSkewWarning(clientVersion, serverVersion apimachineryversion.Info) (string, error) {
parsedClientVersion, err := version.ParseSemantic(clientVersion.GitVersion)
if err != nil {
return err
return "", fmt.Errorf("client version error: %w", err)
}
parsedServerVersion, err := version.ParseSemantic(serverVersion.GitVersion)
if err != nil {
return err
return "", fmt.Errorf("server version error: %w", 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",
warningMessage := fmt.Sprintf("version difference between client (%d.%d) and server (%d.%d) exceeds the supported minor version skew of +/-%d",
parsedClientVersion.Major(), parsedClientVersion.Minor(), parsedServerVersion.Major(), parsedServerVersion.Minor(), supportedMinorVersionSkew)
return warningMessage, nil
}
return nil
return "", nil
}

View File

@ -17,14 +17,12 @@ limitations under the License.
package version
import (
"bytes"
apimachineryversion "k8s.io/apimachinery/pkg/version"
"testing"
apimachineryversion "k8s.io/apimachinery/pkg/version"
)
func TestPrintVersionSkewWarning(t *testing.T) {
output := &bytes.Buffer{}
testCases := []struct {
name string
clientVersion apimachineryversion.Info
@ -82,14 +80,15 @@ func TestPrintVersionSkewWarning(t *testing.T) {
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
output.Reset()
warningMessage, err := getVersionSkewWarning(tc.clientVersion, tc.serverVersion)
if err != nil {
t.Errorf("error: %s", err)
}
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())
if tc.isWarningExpected && warningMessage == "" {
t.Error("warning was expected")
} else if !tc.isWarningExpected && warningMessage != "" {
t.Errorf("warning was not expected. but got %s", warningMessage)
}
})
}

View File

@ -55,6 +55,7 @@ var (
type Options struct {
ClientOnly bool
Output string
WarningsAsErrors bool
args []string
@ -93,6 +94,9 @@ func NewCmdVersion(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cob
// Complete completes all the required options
func (o *Options) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
o.WarningsAsErrors = cmd.Flags().Lookup("warnings-as-errors").Value.String() == "true"
if o.ClientOnly {
return nil
}
@ -162,11 +166,17 @@ func (o *Options) Run() error {
}
if versionInfo.ServerVersion != nil {
if err := printVersionSkewWarning(o.ErrOut, *versionInfo.ClientVersion, *versionInfo.ServerVersion); err != nil {
warningMessage, err := getVersionSkewWarning(*versionInfo.ClientVersion, *versionInfo.ServerVersion)
if err != nil {
return err
}
if warningMessage != "" {
if o.WarningsAsErrors {
return errors.New(warningMessage)
}
fmt.Fprintf(o.ErrOut, "Warning: %s\n", warningMessage) //nolint:errcheck
}
}
return serverErr
}

View File

@ -20,8 +20,6 @@ import (
"strings"
"testing"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericiooptions"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
@ -32,13 +30,18 @@ func TestNewCmdVersionClientVersion(t *testing.T) {
defer tf.Cleanup()
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
o := NewOptions(streams)
if err := o.Complete(tf, &cobra.Command{}, nil); err != nil {
cmd := NewCmdVersion(tf, streams)
cmd.Flags().Bool("warnings-as-errors", false, "")
if err := o.Complete(tf, cmd, nil); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if err := o.Validate(); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if err := o.Complete(tf, &cobra.Command{}, []string{"extraParameter0"}); err != nil {
if err := o.Complete(tf, cmd, []string{"extraParameter0"}); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if err := o.Validate(); !strings.Contains(err.Error(), "extra arguments") {