Merge pull request #45 from qiujian16/handle311

Handle kube 1.11
This commit is contained in:
OpenShift Merge Robot
2020-07-01 17:50:44 -04:00
committed by GitHub
10 changed files with 797 additions and 13 deletions

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -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
}

View File

@@ -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))
}
}

View File

@@ -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, "/")...)...)
}

View File

@@ -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,

18
vendor/k8s.io/apimachinery/pkg/util/version/doc.go generated vendored Normal file
View File

@@ -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"

322
vendor/k8s.io/apimachinery/pkg/util/version/version.go generated vendored Normal file
View File

@@ -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
}

1
vendor/modules.txt vendored
View File

@@ -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