mirror of https://github.com/k3s-io/k3s
Add a package for handling version numbers (including non-semvers)
parent
15f9572b8c
commit
bb60f0415a
|
@ -266,6 +266,7 @@ pkg/util/ratelimit
|
|||
pkg/util/replicaset
|
||||
pkg/util/restoptions
|
||||
pkg/util/validation/field
|
||||
pkg/util/version
|
||||
pkg/util/workqueue
|
||||
pkg/version/prometheus
|
||||
pkg/volume
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"version.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["version_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [],
|
||||
)
|
|
@ -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/kubernetes/pkg/util/version"
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
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
|
||||
}
|
||||
|
||||
// BuildMetadata returns the build metadata, if v is a Semantic Version, or ""
|
||||
func (v *Version) BuildMetadata() string {
|
||||
return v.buildMetadata
|
||||
}
|
||||
|
||||
// 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 {
|
||||
for i := range v.components {
|
||||
switch {
|
||||
case i >= len(other.components):
|
||||
if v.components[i] != 0 {
|
||||
return 1
|
||||
}
|
||||
case other.components[i] < v.components[i]:
|
||||
return 1
|
||||
case other.components[i] > v.components[i]:
|
||||
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 := range vPR {
|
||||
if i >= len(oPR) {
|
||||
return 1
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testItem struct {
|
||||
version string
|
||||
unparsed string
|
||||
equalsPrev bool
|
||||
}
|
||||
|
||||
func testOne(v *Version, item, prev testItem) error {
|
||||
str := v.String()
|
||||
if item.unparsed == "" {
|
||||
if str != item.version {
|
||||
return fmt.Errorf("bad round-trip: %q -> %q", item.version, str)
|
||||
}
|
||||
} else {
|
||||
if str != item.unparsed {
|
||||
return fmt.Errorf("bad unparse: %q -> %q, expected %q", item.version, str, item.unparsed)
|
||||
}
|
||||
}
|
||||
|
||||
if prev.version != "" {
|
||||
cmp, err := v.Compare(prev.version)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unexpected parse error: %v", err)
|
||||
}
|
||||
switch {
|
||||
case cmp == -1:
|
||||
return fmt.Errorf("unexpected ordering %q < %q", item.version, prev.version)
|
||||
case cmp == 0 && !item.equalsPrev:
|
||||
return fmt.Errorf("unexpected comparison %q == %q", item.version, item.version)
|
||||
case cmp == 1 && item.equalsPrev:
|
||||
return fmt.Errorf("unexpected comparison %q != %q", item.version, item.version)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestSemanticVersions(t *testing.T) {
|
||||
tests := []testItem{
|
||||
// This is every version string that appears in the 2.0 semver spec,
|
||||
// sorted in strictly increasing order except as noted.
|
||||
{version: "0.1.0"},
|
||||
{version: "1.0.0-0.3.7"},
|
||||
{version: "1.0.0-alpha"},
|
||||
{version: "1.0.0-alpha+001", equalsPrev: true},
|
||||
{version: "1.0.0-alpha.1"},
|
||||
{version: "1.0.0-alpha.beta"},
|
||||
{version: "1.0.0-beta"},
|
||||
{version: "1.0.0-beta+exp.sha.5114f85", equalsPrev: true},
|
||||
{version: "1.0.0-beta.2"},
|
||||
{version: "1.0.0-beta.11"},
|
||||
{version: "1.0.0-rc.1"},
|
||||
{version: "1.0.0-x.7.z.92"},
|
||||
{version: "1.0.0"},
|
||||
{version: "1.0.0+20130313144700", equalsPrev: true},
|
||||
{version: "1.9.0"},
|
||||
{version: "1.10.0"},
|
||||
{version: "1.11.0"},
|
||||
{version: "2.0.0"},
|
||||
{version: "2.1.0"},
|
||||
{version: "2.1.1"},
|
||||
{version: "42.0.0"},
|
||||
|
||||
// We also allow whitespace and "v" prefix
|
||||
{version: " 42.0.0", unparsed: "42.0.0", equalsPrev: true},
|
||||
{version: "\t42.0.0 ", unparsed: "42.0.0", equalsPrev: true},
|
||||
{version: "43.0.0-1", unparsed: "43.0.0-1"},
|
||||
{version: "43.0.0-1 ", unparsed: "43.0.0-1", equalsPrev: true},
|
||||
{version: "v43.0.0-1", unparsed: "43.0.0-1", equalsPrev: true},
|
||||
{version: " v43.0.0", unparsed: "43.0.0"},
|
||||
{version: " 43.0.0 ", unparsed: "43.0.0", equalsPrev: true},
|
||||
}
|
||||
|
||||
var prev testItem
|
||||
for _, item := range tests {
|
||||
v, err := ParseSemantic(item.version)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected parse error: %v", err)
|
||||
continue
|
||||
}
|
||||
err = testOne(v, item, prev)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
prev = item
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadSemanticVersions(t *testing.T) {
|
||||
tests := []string{
|
||||
// "MUST take the form X.Y.Z"
|
||||
"1",
|
||||
"1.2",
|
||||
"1.2.3.4",
|
||||
".2.3",
|
||||
"1..3",
|
||||
"1.2.",
|
||||
"",
|
||||
"..",
|
||||
// "where X, Y, and Z are non-negative integers"
|
||||
"-1.2.3",
|
||||
"1.-2.3",
|
||||
"1.2.-3",
|
||||
"1a.2.3",
|
||||
"1.2a.3",
|
||||
"1.2.3a",
|
||||
"a1.2.3",
|
||||
"a.b.c",
|
||||
"1 .2.3",
|
||||
"1. 2.3",
|
||||
// "and MUST NOT contain leading zeroes."
|
||||
"01.2.3",
|
||||
"1.02.3",
|
||||
"1.2.03",
|
||||
// "[pre-release] identifiers MUST comprise only ASCII alphanumerics and hyphen"
|
||||
"1.2.3-/",
|
||||
// "[pre-release] identifiers MUST NOT be empty"
|
||||
"1.2.3-",
|
||||
"1.2.3-.",
|
||||
"1.2.3-foo.",
|
||||
"1.2.3-.foo",
|
||||
// "Numeric [pre-release] identifiers MUST NOT include leading zeroes"
|
||||
"1.2.3-01",
|
||||
// "[build metadata] identifiers MUST comprise only ASCII alphanumerics and hyphen"
|
||||
"1.2.3+/",
|
||||
// "[build metadata] identifiers MUST NOT be empty"
|
||||
"1.2.3+",
|
||||
"1.2.3+.",
|
||||
"1.2.3+foo.",
|
||||
"1.2.3+.foo",
|
||||
|
||||
// whitespace/"v"-prefix checks
|
||||
"v 1.2.3",
|
||||
"vv1.2.3",
|
||||
}
|
||||
|
||||
for i := range tests {
|
||||
_, err := ParseSemantic(tests[i])
|
||||
if err == nil {
|
||||
t.Errorf("unexpected success parsing invalid semver %q", tests[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericVersions(t *testing.T) {
|
||||
tests := []testItem{
|
||||
// This is all of the strings from TestSemanticVersions, plus some strings
|
||||
// from TestBadSemanticVersions that should parse as generic versions,
|
||||
// plus some additional strings.
|
||||
{version: "0.1.0", unparsed: "0.1.0"},
|
||||
{version: "1.0.0-0.3.7", unparsed: "1.0.0"},
|
||||
{version: "1.0.0-alpha", unparsed: "1.0.0", equalsPrev: true},
|
||||
{version: "1.0.0-alpha+001", unparsed: "1.0.0", equalsPrev: true},
|
||||
{version: "1.0.0-alpha.1", unparsed: "1.0.0", equalsPrev: true},
|
||||
{version: "1.0.0-alpha.beta", unparsed: "1.0.0", equalsPrev: true},
|
||||
{version: "1.0.0.beta", unparsed: "1.0.0", equalsPrev: true},
|
||||
{version: "1.0.0-beta+exp.sha.5114f85", unparsed: "1.0.0", equalsPrev: true},
|
||||
{version: "1.0.0.beta.2", unparsed: "1.0.0", equalsPrev: true},
|
||||
{version: "1.0.0.beta.11", unparsed: "1.0.0", equalsPrev: true},
|
||||
{version: "1.0.0.rc.1", unparsed: "1.0.0", equalsPrev: true},
|
||||
{version: "1.0.0-x.7.z.92", unparsed: "1.0.0", equalsPrev: true},
|
||||
{version: "1.0.0", unparsed: "1.0.0", equalsPrev: true},
|
||||
{version: "1.0.0+20130313144700", unparsed: "1.0.0", equalsPrev: true},
|
||||
{version: "1.2", unparsed: "1.2"},
|
||||
{version: "1.2a.3", unparsed: "1.2", equalsPrev: true},
|
||||
{version: "1.2.3", unparsed: "1.2.3"},
|
||||
{version: "1.2.3a", unparsed: "1.2.3", equalsPrev: true},
|
||||
{version: "1.2.3-foo.", unparsed: "1.2.3", equalsPrev: true},
|
||||
{version: "1.2.3-.foo", unparsed: "1.2.3", equalsPrev: true},
|
||||
{version: "1.2.3-01", unparsed: "1.2.3", equalsPrev: true},
|
||||
{version: "1.2.3+", unparsed: "1.2.3", equalsPrev: true},
|
||||
{version: "1.2.3+foo.", unparsed: "1.2.3", equalsPrev: true},
|
||||
{version: "1.2.3+.foo", unparsed: "1.2.3", equalsPrev: true},
|
||||
{version: "1.02.3", unparsed: "1.2.3", equalsPrev: true},
|
||||
{version: "1.2.03", unparsed: "1.2.3", equalsPrev: true},
|
||||
{version: "1.2.003", unparsed: "1.2.3", equalsPrev: true},
|
||||
{version: "1.2.3.4", unparsed: "1.2.3.4"},
|
||||
{version: "1.2.3.4b3", unparsed: "1.2.3.4", equalsPrev: true},
|
||||
{version: "1.2.3.4.5", unparsed: "1.2.3.4.5"},
|
||||
{version: "1.9.0", unparsed: "1.9.0"},
|
||||
{version: "1.10.0", unparsed: "1.10.0"},
|
||||
{version: "1.11.0", unparsed: "1.11.0"},
|
||||
{version: "2.0.0", unparsed: "2.0.0"},
|
||||
{version: "2.1.0", unparsed: "2.1.0"},
|
||||
{version: "2.1.1", unparsed: "2.1.1"},
|
||||
{version: "42.0.0", unparsed: "42.0.0"},
|
||||
{version: " 42.0.0", unparsed: "42.0.0", equalsPrev: true},
|
||||
{version: "\t42.0.0 ", unparsed: "42.0.0", equalsPrev: true},
|
||||
{version: "42.0.0-1", unparsed: "42.0.0", equalsPrev: true},
|
||||
{version: "42.0.0-1 ", unparsed: "42.0.0", equalsPrev: true},
|
||||
{version: "v42.0.0-1", unparsed: "42.0.0", equalsPrev: true},
|
||||
{version: " v43.0.0", unparsed: "43.0.0"},
|
||||
{version: " 43.0.0 ", unparsed: "43.0.0", equalsPrev: true},
|
||||
}
|
||||
|
||||
var prev testItem
|
||||
for _, item := range tests {
|
||||
v, err := ParseGeneric(item.version)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected parse error: %v", err)
|
||||
continue
|
||||
}
|
||||
err = testOne(v, item, prev)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
prev = item
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadGenericVersions(t *testing.T) {
|
||||
tests := []string{
|
||||
"1",
|
||||
"01.2.3",
|
||||
"-1.2.3",
|
||||
"1.-2.3",
|
||||
".2.3",
|
||||
"1..3",
|
||||
"1a.2.3",
|
||||
"a1.2.3",
|
||||
"1 .2.3",
|
||||
"1. 2.3",
|
||||
"1.bob",
|
||||
"bob",
|
||||
"v 1.2.3",
|
||||
"vv1.2.3",
|
||||
"",
|
||||
".",
|
||||
}
|
||||
|
||||
for i := range tests {
|
||||
_, err := ParseGeneric(tests[i])
|
||||
if err == nil {
|
||||
t.Errorf("unexpected success parsing invalid version %q", tests[i])
|
||||
}
|
||||
}
|
||||
}
|
|
@ -872,6 +872,7 @@ k8s.io/kubernetes/pkg/util/testing,jlowdermilk,1
|
|||
k8s.io/kubernetes/pkg/util/threading,roberthbailey,1
|
||||
k8s.io/kubernetes/pkg/util/validation,Q-Lee,1
|
||||
k8s.io/kubernetes/pkg/util/validation/field,timstclair,1
|
||||
k8s.io/kubernetes/pkg/util/version,danwinship,0
|
||||
k8s.io/kubernetes/pkg/util/wait,Q-Lee,1
|
||||
k8s.io/kubernetes/pkg/util/workqueue,mtaufen,1
|
||||
k8s.io/kubernetes/pkg/util/wsstream,timothysc,1
|
||||
|
|
|
Loading…
Reference in New Issue