Skip to content

Commit

Permalink
Kubernetes Version Server Check For Kudo Install (#836)
Browse files Browse the repository at this point in the history
* adding install kubernete version validation as a minVersion
* kubernetesVersion is now a required field for operators
  • Loading branch information
kensipe committed Sep 18, 2019
1 parent 268932d commit 9b27e7a
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 10 deletions.
5 changes: 4 additions & 1 deletion pkg/kudoctl/cmd/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,11 @@ func installCrds(crds *packages.PackageCRDs, kc *kudo.Client, options *Options,
return err
}

// Operator part
if err := kc.ValidateServerForOperator(crds.Operator); err != nil {
return err
}

// Operator part
// Check if Operator exists
if !kc.OperatorExistsInCluster(crds.Operator.ObjectMeta.Name, settings.Namespace) {
if err := installSingleOperatorToCluster(operatorName, settings.Namespace, crds.Operator, kc); err != nil {
Expand Down
20 changes: 15 additions & 5 deletions pkg/kudoctl/cmd/install/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"github.com/kudobuilder/kudo/pkg/kudoctl/util/kudo"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/version"
fakediscovery "k8s.io/client-go/discovery/fake"
)

func TestValidate(t *testing.T) {
Expand All @@ -36,10 +38,6 @@ func TestValidate(t *testing.T) {
}
}

func newTestClient() *kudo.Client {
return kudo.NewClientFromK8s(fake.NewSimpleClientset())
}

func TestParameterValidation_InstallCrds(t *testing.T) {
crds := packages.PackageCRDs{
Operator: &v1alpha1.Operator{
Expand All @@ -53,6 +51,9 @@ func TestParameterValidation_InstallCrds(t *testing.T) {
},
Name: "test",
},
Spec: v1alpha1.OperatorSpec{
KubernetesVersion: "1.15",
},
},
Instance: &v1alpha1.Instance{
TypeMeta: metav1.TypeMeta{
Expand Down Expand Up @@ -104,7 +105,16 @@ func TestParameterValidation_InstallCrds(t *testing.T) {
}

for _, tt := range tests {
kc := newTestClient()
client := fake.NewSimpleClientset()
kc := kudo.NewClientFromK8s(client)

fakeDiscovery, ok := client.Discovery().(*fakediscovery.FakeDiscovery)
if !ok {
t.Fatalf("couldn't convert Discovery() to *FakeDiscovery")
}
fakeDiscovery.FakedServerVersion = &version.Info{
GitVersion: "v1.16.0",
}

testCrds := crds
testCrds.OperatorVersion.Spec.Parameters = tt.parameters
Expand Down
43 changes: 41 additions & 2 deletions pkg/kudoctl/util/kudo/kudo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@ package kudo
import (
"encoding/json"
"fmt"

"strings"
"time"

"github.com/kudobuilder/kudo/pkg/apis/kudo/v1alpha1"
"github.com/kudobuilder/kudo/pkg/client/clientset/versioned"
"github.com/kudobuilder/kudo/pkg/kudoctl/clog"
"github.com/kudobuilder/kudo/pkg/util/kudo"
"k8s.io/apimachinery/pkg/types"
"github.com/kudobuilder/kudo/pkg/version"

"github.com/pkg/errors"
v1core "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/discovery"

// Import Kubernetes authentication providers to support GKE, etc.
_ "k8s.io/client-go/plugin/pkg/client/auth"
Expand Down Expand Up @@ -225,3 +226,41 @@ func (c *Client) InstallInstanceObjToCluster(obj *v1alpha1.Instance, namespace s
clog.V(2).Printf("instance %v created in namespace %v", createdObj.Name, namespace)
return createdObj, nil
}

// ValidateServerForOperator validates that the k8s server version and kudo version are valid for operator
// error message will provide detail of failure, otherwise nil
func (c *Client) ValidateServerForOperator(operator *v1alpha1.Operator) error {
expectedKubver, err := version.New(operator.Spec.KubernetesVersion)
if err != nil {
return fmt.Errorf("unable to parse operators kubernetes version: %w", err)
}
//TODO : to be added in when we support kudo server providing server version
//expectedKudoVer, err := semver.NewVersion(operator.Spec.KudoVersion)
//if err != nil {
// return fmt.Errorf("Unable to parse operators kudo version: %w", err)
//}
// semvar compares patch, for which we do not want to... compare maj, min only
kVer, err := getKubeVersion(c.clientset.Discovery())
if err != nil {
return err
}

kSemVer, err := version.FromGithubVersion(kVer)
if err != nil {
return err
}
if expectedKubver.CompareMajorMinor(kSemVer) > 0 {
return fmt.Errorf("expected kubernetes version of %v is not supported with version: %v", expectedKubver, kSemVer)
}

return nil
}

// getKubeVersion returns stringified version of k8s server
func getKubeVersion(client discovery.DiscoveryInterface) (string, error) {
v, err := client.ServerVersion()
if err != nil {
return "", err
}
return v.String(), nil
}
4 changes: 2 additions & 2 deletions pkg/kudoctl/util/kudo/kudo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import (
"reflect"
"testing"

"github.com/kudobuilder/kudo/pkg/util/kudo"

"github.com/kudobuilder/kudo/pkg/apis/kudo/v1alpha1"
"github.com/kudobuilder/kudo/pkg/client/clientset/versioned/fake"
"github.com/kudobuilder/kudo/pkg/util/kudo"
util "github.com/kudobuilder/kudo/pkg/util/kudo"

v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
Expand Down
65 changes: 65 additions & 0 deletions pkg/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"os"
"runtime"
"strings"

"github.com/Masterminds/semver"
)

// Info contains versioning information.
Expand Down Expand Up @@ -49,3 +51,66 @@ func Get() Info {
Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
}
}

// Version is an extension of semver.Version
type Version struct {
*semver.Version
}

// CompareMajorMinor provides Compare results -1, 0, 1 for only the major and minor element
// of the semver, ignoring the patch or prerelease elements. This is useful if you are looking
// for minVersion for example 1.15.6 is version 1.15 or higher.
func (v *Version) CompareMajorMinor(o *Version) int {
if d := compareSegment(v.Major(), o.Major()); d != 0 {
return d
}
if d := compareSegment(v.Minor(), o.Minor()); d != 0 {
return d
}
return 0
}

// compares v1 against v2 resulting in -1, 0, 1 for less than, equal, greater than
func compareSegment(v1, v2 int64) int {
if v1 < v2 {
return -1
}
if v1 > v2 {
return 1
}

return 0
}

// New provides an instance of Version from a semver string
func New(v string) (*Version, error) {
ver, err := semver.NewVersion(v)
if err != nil {
return nil, err
}
return FromSemVer(ver), nil
}

// FromGithubVersion provides a version parsed from github semver which starts with "v".
// v1.5.2 provides a sem version of 1.5.2
func FromGithubVersion(v string) (*Version, error) {
return New(Clean(v))
}

// FromSemVer converts a semver.Version to our Version
func FromSemVer(v *semver.Version) *Version {
return &Version{v}
}

// MustParse parses a given version and panics on error.
func MustParse(v string) *Version {
return FromSemVer(semver.MustParse(v))
}

// Clean returns version without a prefixed v if it exists
func Clean(ver string) string {
if strings.HasPrefix(ver, "v") {
return ver[1:]
}
return ver
}
46 changes: 46 additions & 0 deletions pkg/version/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package version

import (
"testing"

"github.com/magiconair/properties/assert"
)

func Test_validVersion(t *testing.T) {

tests := []struct {
name string
actual *Version
expected *Version
val int
}{
{"expect early version", MustParse("1.5"), MustParse("1.4"), -1},
{"expect same version", MustParse("1.5"), MustParse("1.5"), 0},
{"expect newer version", MustParse("1.5"), MustParse("1.6"), 1},
{"full semver is not a factor", MustParse("1.5.8"), MustParse("1.5.0"), 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
val := tt.expected.CompareMajorMinor(tt.actual)
assert.Equal(t, tt.val, val)
})
}
}

func TestClean(t *testing.T) {
tests := []struct {
name string
actual string
expected string
}{
{"clean ver", "1.0.0", "1.0.0"},
{"clean ver", "v1.0.0", "1.0.0"},
{"short ver", "v1.0", "1.0"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Clean(tt.actual)
assert.Equal(t, result, tt.expected)
})
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: "upgrade-operator"
version: "0.1.0"
kubernetesVersion: 1.13
maintainers:
- name: Your name
email: <your@email.com>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: "upgrade-operator"
version: "0.2.0"
kubernetesVersion: 1.13
maintainers:
- name: Your name
email: <your@email.com>
Expand Down

0 comments on commit 9b27e7a

Please sign in to comment.