diff --git a/Makefile b/Makefile index 9c05781b8..834b999b4 100644 --- a/Makefile +++ b/Makefile @@ -51,11 +51,12 @@ endif $(call add-bindata,cluster-manager,./manifests/cluster-manager/...,bindata,bindata,./pkg/operators/clustermanager/bindata/bindata.go) $(call add-bindata,klusterlet,./manifests/klusterlet/...,bindata,bindata,./pkg/operators/klusterlet/bindata/bindata.go) +$(call add-bindata,klusterletkube111,./manifests/klusterletkube111/...,kube111bindata,kube111bindata,./pkg/operators/klusterlet/kube111bindata/bindata.go) copy-crd: bash -x hack/copy-crds.sh -update-all: copy-crd update-bindata-cluster-manager update-bindata-klusterlet update-csv +update-all: copy-crd update-bindata-cluster-manager update-bindata-klusterlet update-bindata-klusterletkube111 update-csv verify-crds: bash -x hack/verify-crds.sh diff --git a/manifests/klusterletkube111/klusterlet-registration-operator-clusterrolebinding.yaml b/manifests/klusterletkube111/klusterlet-registration-operator-clusterrolebinding.yaml new file mode 100644 index 000000000..9252b1357 --- /dev/null +++ b/manifests/klusterletkube111/klusterlet-registration-operator-clusterrolebinding.yaml @@ -0,0 +1,13 @@ +# Create the cluster rolebinding so klusterlet can deploy others +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: open-cluster-management:{{ .KlusterletName }}-registration-operator:operator-kube111 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - kind: ServiceAccount + name: klusterlet + namespace: {{ .OperatorNamespace }} diff --git a/manifests/klusterletkube111/klusterlet-work-clusterrolebinding.yaml b/manifests/klusterletkube111/klusterlet-work-clusterrolebinding.yaml new file mode 100644 index 000000000..99454b717 --- /dev/null +++ b/manifests/klusterletkube111/klusterlet-work-clusterrolebinding.yaml @@ -0,0 +1,13 @@ +# create this clusterrolebinding so work agent is able to create any clusterrole +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: open-cluster-management:{{ .KlusterletName }}-work:agent-kube111 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - kind: ServiceAccount + name: {{ .KlusterletName }}-work-sa + namespace: {{ .KlusterletNamespace }} diff --git a/pkg/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller.go b/pkg/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller.go index 567a7f0f6..810fc414c 100644 --- a/pkg/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller.go +++ b/pkg/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller.go @@ -10,6 +10,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/version" appsinformer "k8s.io/client-go/informers/apps/v1" coreinformer "k8s.io/client-go/informers/core/v1" "k8s.io/client-go/kubernetes" @@ -29,6 +30,7 @@ import ( operatorapiv1 "github.com/open-cluster-management/api/operator/v1" "github.com/open-cluster-management/registration-operator/pkg/helpers" "github.com/open-cluster-management/registration-operator/pkg/operators/klusterlet/bindata" + "github.com/open-cluster-management/registration-operator/pkg/operators/klusterlet/kube111bindata" ) const ( @@ -48,12 +50,19 @@ var ( "manifests/klusterlet/klusterlet-work-clusterrolebinding.yaml", "manifests/klusterlet/klusterlet-work-clusterrolebinding-addition.yaml", } + + kube111StaticResourceFiles = []string{ + "manifests/klusterletkube111/klusterlet-registration-operator-clusterrolebinding.yaml", + "manifests/klusterletkube111/klusterlet-work-clusterrolebinding.yaml", + } ) type klusterletController struct { - klusterletClient operatorv1client.KlusterletInterface - klusterletLister operatorlister.KlusterletLister - kubeClient kubernetes.Interface + klusterletClient operatorv1client.KlusterletInterface + klusterletLister operatorlister.KlusterletLister + kubeClient kubernetes.Interface + kubeVersion *version.Version + operatorNamespace string } // NewKlusterletController construct klusterlet controller @@ -63,11 +72,15 @@ func NewKlusterletController( klusterletInformer operatorinformer.KlusterletInformer, secretInformer coreinformer.SecretInformer, deploymentInformer appsinformer.DeploymentInformer, + kubeVersion *version.Version, + operatorNamespace string, recorder events.Recorder) factory.Controller { controller := &klusterletController{ - kubeClient: kubeClient, - klusterletClient: klusterletClient, - klusterletLister: klusterletInformer.Lister(), + kubeClient: kubeClient, + klusterletClient: klusterletClient, + klusterletLister: klusterletInformer.Lister(), + kubeVersion: kubeVersion, + operatorNamespace: operatorNamespace, } return factory.New().WithSync(controller.sync). @@ -90,6 +103,7 @@ type klusterletConfig struct { ExternalServerURL string HubKubeConfigSecret string BootStrapKubeConfigSecret string + OperatorNamespace string } func (n *klusterletController) sync(ctx context.Context, controllerContext factory.SyncContext) error { @@ -114,6 +128,7 @@ func (n *klusterletController) sync(ctx context.Context, controllerContext facto BootStrapKubeConfigSecret: helpers.BootstrapHubKubeConfigSecret, HubKubeConfigSecret: helpers.HubKubeConfigSecret, ExternalServerURL: getServersFromKlusterlet(klusterlet), + OperatorNamespace: n.operatorNamespace, } // If namespace is not set, use the default namespace if config.KlusterletNamespace == "" { @@ -167,7 +182,25 @@ func (n *klusterletController) sync(ctx context.Context, controllerContext facto return err } - // Deploy the static resources + errs := []error{} + // If kube version is less than 1.12, deploy static resource for kube 1.11 at first + // TODO remove this when we do not support kube 1.11 any longer + if cnt, err := n.kubeVersion.Compare("v1.12.0"); err == nil && cnt < 0 { + resourceResult := resourceapply.ApplyDirectly( + resourceapply.NewKubeClientHolder(n.kubeClient), + controllerContext.Recorder(), + func(name string) ([]byte, error) { + return assets.MustCreateAssetFromTemplate(name, kube111bindata.MustAsset(filepath.Join("", name)), config).Data, nil + }, + kube111StaticResourceFiles..., + ) + for _, result := range resourceResult { + if result.Error != nil { + errs = append(errs, fmt.Errorf("%q (%T): %v", result.File, result.Type, result.Error)) + } + } + } + // Apply static files resourceResults := resourceapply.ApplyDirectly( resourceapply.NewKubeClientHolder(n.kubeClient), @@ -177,7 +210,7 @@ func (n *klusterletController) sync(ctx context.Context, controllerContext facto }, staticResourceFiles..., ) - errs := []error{} + for _, result := range resourceResults { if result.Error != nil { errs = append(errs, fmt.Errorf("%q (%T): %v", result.File, result.Type, result.Error)) @@ -315,6 +348,27 @@ func (n *klusterletController) cleanUp(ctx context.Context, controllerContext fa return err } } + + // TODO remove this when we do not support kube 1.11 any longer + cnt, err := n.kubeVersion.Compare("v1.12.0") + klog.Errorf("comapare version %d, %v", cnt, err) + if cnt, err := n.kubeVersion.Compare("v1.12.0"); err == nil && cnt < 0 { + for _, file := range kube111StaticResourceFiles { + err := helpers.CleanUpStaticObject( + ctx, + n.kubeClient, + nil, + nil, + func(name string) ([]byte, error) { + return assets.MustCreateAssetFromTemplate(name, kube111bindata.MustAsset(filepath.Join("", name)), config).Data, nil + }, + file, + ) + if err != nil { + return err + } + } + } return nil } diff --git a/pkg/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller_test.go b/pkg/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller_test.go index dc752dd3a..e2beac120 100644 --- a/pkg/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller_test.go +++ b/pkg/operators/klusterlet/controllers/klusterletcontroller/klusterlet_controller_test.go @@ -17,6 +17,7 @@ import ( "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/version" fakekube "k8s.io/client-go/kubernetes/fake" clienttesting "k8s.io/client-go/testing" "k8s.io/client-go/tools/cache" @@ -87,11 +88,14 @@ func newTestController(klusterlet *opratorapiv1.Klusterlet, objects ...runtime.O fakeKubeClient := fakekube.NewSimpleClientset(objects...) fakeOperatorClient := fakeoperatorclient.NewSimpleClientset(klusterlet) operatorInformers := operatorinformers.NewSharedInformerFactory(fakeOperatorClient, 5*time.Minute) + kubeVersion, _ := version.ParseGeneric("v1.18.0") hubController := &klusterletController{ - klusterletClient: fakeOperatorClient.OperatorV1().Klusterlets(), - kubeClient: fakeKubeClient, - klusterletLister: operatorInformers.Operator().V1().Klusterlets().Lister(), + klusterletClient: fakeOperatorClient.OperatorV1().Klusterlets(), + kubeClient: fakeKubeClient, + klusterletLister: operatorInformers.Operator().V1().Klusterlets().Lister(), + kubeVersion: kubeVersion, + operatorNamespace: "open-cluster-management", } store := operatorInformers.Operator().V1().Klusterlets().Informer().GetStore() @@ -290,7 +294,7 @@ func TestSyncDelete(t *testing.T) { } if len(kubeActions) != 12 { - t.Errorf("Expected 7 delete actions, but got %d", len(kubeActions)) + t.Errorf("Expected 12 delete actions, but got %d", len(kubeActions)) } } @@ -418,3 +422,71 @@ func TestClusterNameChange(t *testing.T) { } ensureDeployments(t, controller.kubeClient.Actions(), "update", "https://localhost", "cluster3", "cluster3", 2) } + +func TestDeployOnKube111(t *testing.T) { + klusterlet := newKlusterlet("klusterlet", "testns", "cluster1") + bootStrapSecret := newSecret(helpers.BootstrapHubKubeConfigSecret, "testns") + hubKubeConfigSecret := newSecret(helpers.HubKubeConfigSecret, "testns") + hubKubeConfigSecret.Data["kubeconfig"] = []byte("dummuykubeconnfig") + namespace := newNamespace("testns") + controller := newTestController(klusterlet, bootStrapSecret, hubKubeConfigSecret, namespace) + kubeVersion, _ := version.ParseGeneric("v1.11.0") + controller.controller.kubeVersion = kubeVersion + syncContext := testinghelper.NewFakeSyncContext(t, "klusterlet") + + err := controller.controller.sync(nil, syncContext) + if err != nil { + t.Errorf("Expected non error when sync, %v", err) + } + + createObjects := []runtime.Object{} + kubeActions := controller.kubeClient.Actions() + for _, action := range kubeActions { + if action.GetVerb() == "create" { + object := action.(clienttesting.CreateActionImpl).Object + createObjects = append(createObjects, object) + } + } + + // Check if resources are created as expected + if len(createObjects) != 13 { + t.Errorf("Expect 13 objects created in the sync loop, actual %d", len(createObjects)) + } + for _, object := range createObjects { + ensureObject(t, object, klusterlet) + } + + operatorAction := controller.operatorClient.Actions() + if len(operatorAction) != 2 { + t.Errorf("Expect 2 actions in the sync loop, actual %#v", operatorAction) + } + + testinghelper.AssertGet(t, operatorAction[0], "operator.open-cluster-management.io", "v1", "klusterlets") + testinghelper.AssertAction(t, operatorAction[1], "update") + testinghelper.AssertOnlyConditions( + t, operatorAction[1].(clienttesting.UpdateActionImpl).Object, + testinghelper.NamedCondition(klusterletApplied, "KlusterletApplied", metav1.ConditionTrue)) + + // Delete the klusterlet + now := metav1.Now() + klusterlet.ObjectMeta.SetDeletionTimestamp(&now) + controller.operatorStore.Update(klusterlet) + controller.kubeClient.ClearActions() + err = controller.controller.sync(nil, syncContext) + if err != nil { + t.Errorf("Expected non error when sync, %v", err) + } + + deleteActions := []clienttesting.DeleteActionImpl{} + kubeActions = controller.kubeClient.Actions() + for _, action := range kubeActions { + if action.GetVerb() == "delete" { + deleteAction := action.(clienttesting.DeleteActionImpl) + deleteActions = append(deleteActions, deleteAction) + } + } + + if len(kubeActions) != 14 { + t.Errorf("Expected 14 delete actions, but got %d", len(kubeActions)) + } +} diff --git a/pkg/operators/klusterlet/kube111bindata/bindata.go b/pkg/operators/klusterlet/kube111bindata/bindata.go new file mode 100644 index 000000000..e1cf82517 --- /dev/null +++ b/pkg/operators/klusterlet/kube111bindata/bindata.go @@ -0,0 +1,268 @@ +// Code generated for package kube111bindata by go-bindata DO NOT EDIT. (@generated) +// sources: +// manifests/klusterletkube111/klusterlet-registration-operator-clusterrolebinding.yaml +// manifests/klusterletkube111/klusterlet-work-clusterrolebinding.yaml +package kube111bindata + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +// Name return file name +func (fi bindataFileInfo) Name() string { + return fi.name +} + +// Size return file size +func (fi bindataFileInfo) Size() int64 { + return fi.size +} + +// Mode return file mode +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} + +// Mode return file modify time +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} + +// IsDir return file whether a directory +func (fi bindataFileInfo) IsDir() bool { + return fi.mode&os.ModeDir != 0 +} + +// Sys return file is sys mode +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _manifestsKlusterletkube111KlusterletRegistrationOperatorClusterrolebindingYaml = []byte(`# Create the cluster rolebinding so klusterlet can deploy others +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: open-cluster-management:{{ .KlusterletName }}-registration-operator:operator-kube111 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - kind: ServiceAccount + name: klusterlet + namespace: {{ .OperatorNamespace }} +`) + +func manifestsKlusterletkube111KlusterletRegistrationOperatorClusterrolebindingYamlBytes() ([]byte, error) { + return _manifestsKlusterletkube111KlusterletRegistrationOperatorClusterrolebindingYaml, nil +} + +func manifestsKlusterletkube111KlusterletRegistrationOperatorClusterrolebindingYaml() (*asset, error) { + bytes, err := manifestsKlusterletkube111KlusterletRegistrationOperatorClusterrolebindingYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "manifests/klusterletkube111/klusterlet-registration-operator-clusterrolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _manifestsKlusterletkube111KlusterletWorkClusterrolebindingYaml = []byte(`# create this clusterrolebinding so work agent is able to create any clusterrole +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: open-cluster-management:{{ .KlusterletName }}-work:agent-kube111 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - kind: ServiceAccount + name: {{ .KlusterletName }}-work-sa + namespace: {{ .KlusterletNamespace }} +`) + +func manifestsKlusterletkube111KlusterletWorkClusterrolebindingYamlBytes() ([]byte, error) { + return _manifestsKlusterletkube111KlusterletWorkClusterrolebindingYaml, nil +} + +func manifestsKlusterletkube111KlusterletWorkClusterrolebindingYaml() (*asset, error) { + bytes, err := manifestsKlusterletkube111KlusterletWorkClusterrolebindingYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "manifests/klusterletkube111/klusterlet-work-clusterrolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "manifests/klusterletkube111/klusterlet-registration-operator-clusterrolebinding.yaml": manifestsKlusterletkube111KlusterletRegistrationOperatorClusterrolebindingYaml, + "manifests/klusterletkube111/klusterlet-work-clusterrolebinding.yaml": manifestsKlusterletkube111KlusterletWorkClusterrolebindingYaml, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "manifests": {nil, map[string]*bintree{ + "klusterletkube111": {nil, map[string]*bintree{ + "klusterlet-registration-operator-clusterrolebinding.yaml": {manifestsKlusterletkube111KlusterletRegistrationOperatorClusterrolebindingYaml, map[string]*bintree{}}, + "klusterlet-work-clusterrolebinding.yaml": {manifestsKlusterletkube111KlusterletWorkClusterrolebindingYaml, map[string]*bintree{}}, + }}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} diff --git a/pkg/operators/manager.go b/pkg/operators/manager.go index 8e6f99f11..444217fe5 100644 --- a/pkg/operators/manager.go +++ b/pkg/operators/manager.go @@ -2,9 +2,11 @@ package operators import ( "context" + "io/ioutil" "time" apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + versionutil "k8s.io/apimachinery/pkg/util/version" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" apiregistrationclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset" @@ -20,6 +22,9 @@ import ( "github.com/open-cluster-management/registration-operator/pkg/operators/klusterlet/controllers/statuscontroller" ) +// defaultSpokeComponentNamespace is the default namespace in which the operator is deployed +const defaultComponentNamespace = "open-cluster-management" + // RunClusterManagerOperator starts a new cluster manager operator func RunClusterManagerOperator(ctx context.Context, controllerContext *controllercmd.ControllerContext) error { // Build kubclient client and informer for managed cluster @@ -75,6 +80,14 @@ func RunKlusterletOperator(ctx context.Context, controllerContext *controllercmd if err != nil { return err } + version, err := kubeClient.ServerVersion() + if err != nil { + return err + } + kubeVersion, err := versionutil.ParseGeneric(version.String()) + if err != nil { + return err + } kubeInformer := informers.NewSharedInformerFactory(kubeClient, 5*time.Minute) @@ -85,12 +98,21 @@ func RunKlusterletOperator(ctx context.Context, controllerContext *controllercmd } operatorInformer := operatorinformer.NewSharedInformerFactory(operatorClient, 5*time.Minute) + // Read component namespace + operatorNamespace := defaultComponentNamespace + nsBytes, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") + if err == nil { + operatorNamespace = string(nsBytes) + } + klusterletController := klusterletcontroller.NewKlusterletController( kubeClient, operatorClient.OperatorV1().Klusterlets(), operatorInformer.Operator().V1().Klusterlets(), kubeInformer.Core().V1().Secrets(), kubeInformer.Apps().V1().Deployments(), + kubeVersion, + operatorNamespace, controllerContext.EventRecorder) statusController := statuscontroller.NewKlusterletStatusController( kubeClient, diff --git a/vendor/k8s.io/apimachinery/pkg/util/version/doc.go b/vendor/k8s.io/apimachinery/pkg/util/version/doc.go new file mode 100644 index 000000000..5b2b22b6d --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/util/version/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2016 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 provides utilities for version number comparisons +package version // import "k8s.io/apimachinery/pkg/util/version" diff --git a/vendor/k8s.io/apimachinery/pkg/util/version/version.go b/vendor/k8s.io/apimachinery/pkg/util/version/version.go new file mode 100644 index 000000000..8946926aa --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/util/version/version.go @@ -0,0 +1,322 @@ +/* +Copyright 2016 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" + "fmt" + "regexp" + "strconv" + "strings" +) + +// Version is an opqaue representation of a version number +type Version struct { + components []uint + semver bool + preRelease string + buildMetadata string +} + +var ( + // versionMatchRE splits a version string into numeric and "extra" parts + versionMatchRE = regexp.MustCompile(`^\s*v?([0-9]+(?:\.[0-9]+)*)(.*)*$`) + // extraMatchRE splits the "extra" part of versionMatchRE into semver pre-release and build metadata; it does not validate the "no leading zeroes" constraint for pre-release + extraMatchRE = regexp.MustCompile(`^(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?\s*$`) +) + +func parse(str string, semver bool) (*Version, error) { + parts := versionMatchRE.FindStringSubmatch(str) + if parts == nil { + return nil, fmt.Errorf("could not parse %q as version", str) + } + numbers, extra := parts[1], parts[2] + + components := strings.Split(numbers, ".") + if (semver && len(components) != 3) || (!semver && len(components) < 2) { + return nil, fmt.Errorf("illegal version string %q", str) + } + + v := &Version{ + components: make([]uint, len(components)), + semver: semver, + } + for i, comp := range components { + if (i == 0 || semver) && strings.HasPrefix(comp, "0") && comp != "0" { + return nil, fmt.Errorf("illegal zero-prefixed version component %q in %q", comp, str) + } + num, err := strconv.ParseUint(comp, 10, 0) + if err != nil { + return nil, fmt.Errorf("illegal non-numeric version component %q in %q: %v", comp, str, err) + } + v.components[i] = uint(num) + } + + if semver && extra != "" { + extraParts := extraMatchRE.FindStringSubmatch(extra) + if extraParts == nil { + return nil, fmt.Errorf("could not parse pre-release/metadata (%s) in version %q", extra, str) + } + v.preRelease, v.buildMetadata = extraParts[1], extraParts[2] + + for _, comp := range strings.Split(v.preRelease, ".") { + if _, err := strconv.ParseUint(comp, 10, 0); err == nil { + if strings.HasPrefix(comp, "0") && comp != "0" { + return nil, fmt.Errorf("illegal zero-prefixed version component %q in %q", comp, str) + } + } + } + } + + return v, nil +} + +// ParseGeneric parses a "generic" version string. The version string must consist of two +// or more dot-separated numeric fields (the first of which can't have leading zeroes), +// followed by arbitrary uninterpreted data (which need not be separated from the final +// numeric field by punctuation). For convenience, leading and trailing whitespace is +// ignored, and the version can be preceded by the letter "v". See also ParseSemantic. +func ParseGeneric(str string) (*Version, error) { + return parse(str, false) +} + +// MustParseGeneric is like ParseGeneric except that it panics on error +func MustParseGeneric(str string) *Version { + v, err := ParseGeneric(str) + if err != nil { + panic(err) + } + return v +} + +// ParseSemantic parses a version string that exactly obeys the syntax and semantics of +// the "Semantic Versioning" specification (http://semver.org/) (although it ignores +// leading and trailing whitespace, and allows the version to be preceded by "v"). For +// version strings that are not guaranteed to obey the Semantic Versioning syntax, use +// ParseGeneric. +func ParseSemantic(str string) (*Version, error) { + return parse(str, true) +} + +// MustParseSemantic is like ParseSemantic except that it panics on error +func MustParseSemantic(str string) *Version { + v, err := ParseSemantic(str) + if err != nil { + panic(err) + } + return v +} + +// Major returns the major release number +func (v *Version) Major() uint { + return v.components[0] +} + +// Minor returns the minor release number +func (v *Version) Minor() uint { + return v.components[1] +} + +// Patch returns the patch release number if v is a Semantic Version, or 0 +func (v *Version) Patch() uint { + if len(v.components) < 3 { + return 0 + } + return v.components[2] +} + +// BuildMetadata returns the build metadata, if v is a Semantic Version, or "" +func (v *Version) BuildMetadata() string { + return v.buildMetadata +} + +// PreRelease returns the prerelease metadata, if v is a Semantic Version, or "" +func (v *Version) PreRelease() string { + return v.preRelease +} + +// Components returns the version number components +func (v *Version) Components() []uint { + return v.components +} + +// WithMajor returns copy of the version object with requested major number +func (v *Version) WithMajor(major uint) *Version { + result := *v + result.components = []uint{major, v.Minor(), v.Patch()} + return &result +} + +// WithMinor returns copy of the version object with requested minor number +func (v *Version) WithMinor(minor uint) *Version { + result := *v + result.components = []uint{v.Major(), minor, v.Patch()} + return &result +} + +// WithPatch returns copy of the version object with requested patch number +func (v *Version) WithPatch(patch uint) *Version { + result := *v + result.components = []uint{v.Major(), v.Minor(), patch} + return &result +} + +// WithPreRelease returns copy of the version object with requested prerelease +func (v *Version) WithPreRelease(preRelease string) *Version { + result := *v + result.components = []uint{v.Major(), v.Minor(), v.Patch()} + result.preRelease = preRelease + return &result +} + +// WithBuildMetadata returns copy of the version object with requested buildMetadata +func (v *Version) WithBuildMetadata(buildMetadata string) *Version { + result := *v + result.components = []uint{v.Major(), v.Minor(), v.Patch()} + result.buildMetadata = buildMetadata + return &result +} + +// String converts a Version back to a string; note that for versions parsed with +// ParseGeneric, this will not include the trailing uninterpreted portion of the version +// number. +func (v *Version) String() string { + var buffer bytes.Buffer + + for i, comp := range v.components { + if i > 0 { + buffer.WriteString(".") + } + buffer.WriteString(fmt.Sprintf("%d", comp)) + } + if v.preRelease != "" { + buffer.WriteString("-") + buffer.WriteString(v.preRelease) + } + if v.buildMetadata != "" { + buffer.WriteString("+") + buffer.WriteString(v.buildMetadata) + } + + return buffer.String() +} + +// compareInternal returns -1 if v is less than other, 1 if it is greater than other, or 0 +// if they are equal +func (v *Version) compareInternal(other *Version) int { + + vLen := len(v.components) + oLen := len(other.components) + for i := 0; i < vLen && i < oLen; i++ { + switch { + case other.components[i] < v.components[i]: + return 1 + case other.components[i] > v.components[i]: + return -1 + } + } + + // If components are common but one has more items and they are not zeros, it is bigger + switch { + case oLen < vLen && !onlyZeros(v.components[oLen:]): + return 1 + case oLen > vLen && !onlyZeros(other.components[vLen:]): + return -1 + } + + if !v.semver || !other.semver { + return 0 + } + + switch { + case v.preRelease == "" && other.preRelease != "": + return 1 + case v.preRelease != "" && other.preRelease == "": + return -1 + case v.preRelease == other.preRelease: // includes case where both are "" + return 0 + } + + vPR := strings.Split(v.preRelease, ".") + oPR := strings.Split(other.preRelease, ".") + for i := 0; i < len(vPR) && i < len(oPR); i++ { + vNum, err := strconv.ParseUint(vPR[i], 10, 0) + if err == nil { + oNum, err := strconv.ParseUint(oPR[i], 10, 0) + if err == nil { + switch { + case oNum < vNum: + return 1 + case oNum > vNum: + return -1 + default: + continue + } + } + } + if oPR[i] < vPR[i] { + return 1 + } else if oPR[i] > vPR[i] { + return -1 + } + } + + switch { + case len(oPR) < len(vPR): + return 1 + case len(oPR) > len(vPR): + return -1 + } + + return 0 +} + +// returns false if array contain any non-zero element +func onlyZeros(array []uint) bool { + for _, num := range array { + if num != 0 { + return false + } + } + return true +} + +// AtLeast tests if a version is at least equal to a given minimum version. If both +// Versions are Semantic Versions, this will use the Semantic Version comparison +// algorithm. Otherwise, it will compare only the numeric components, with non-present +// components being considered "0" (ie, "1.4" is equal to "1.4.0"). +func (v *Version) AtLeast(min *Version) bool { + return v.compareInternal(min) != -1 +} + +// LessThan tests if a version is less than a given version. (It is exactly the opposite +// of AtLeast, for situations where asking "is v too old?" makes more sense than asking +// "is v new enough?".) +func (v *Version) LessThan(other *Version) bool { + return v.compareInternal(other) == -1 +} + +// Compare compares v against a version string (which will be parsed as either Semantic +// or non-Semantic depending on v). On success it returns -1 if v is less than other, 1 if +// it is greater than other, or 0 if they are equal. +func (v *Version) Compare(other string) (int, error) { + ov, err := parse(other, v.semver) + if err != nil { + return 0, err + } + return v.compareInternal(ov), nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 5d4d34604..9a0334102 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -526,6 +526,7 @@ k8s.io/apimachinery/pkg/util/strategicpatch k8s.io/apimachinery/pkg/util/uuid k8s.io/apimachinery/pkg/util/validation k8s.io/apimachinery/pkg/util/validation/field +k8s.io/apimachinery/pkg/util/version k8s.io/apimachinery/pkg/util/wait k8s.io/apimachinery/pkg/util/waitgroup k8s.io/apimachinery/pkg/util/yaml