mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 14:07:14 +00:00
Merge pull request #46238 from yguo0905/package-validator
Automatic merge from submit-queue (batch tested with PRs 46648, 46500, 46238, 46668, 46557) Support validating package versions in node conformance test **What this PR does / why we need it**: This PR adds a package validator in node conformance test for checking whether the locally installed packages meet the image spec. **Special notes for your reviewer**: The image spec for GKE (which has the package spec) will be in a separate PR. Then we will publish a new node conformance test image for GKE whose name should use the convention in https://github.com/kubernetes/kubernetes/issues/45760 and have `gke` in it. **Release note**: ``` NONE ```
This commit is contained in:
commit
a6f0033164
4
Godeps/Godeps.json
generated
4
Godeps/Godeps.json
generated
@ -308,8 +308,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/blang/semver",
|
"ImportPath": "github.com/blang/semver",
|
||||||
"Comment": "v3.0.1",
|
"Comment": "v3.5.0",
|
||||||
"Rev": "31b736133b98f26d5e078ec9eb591666edfd091f"
|
"Rev": "b38d23b8782a487059e8fc8773e9a5b228a77cb6"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/boltdb/bolt",
|
"ImportPath": "github.com/boltdb/bolt",
|
||||||
|
@ -15,12 +15,14 @@ go_library(
|
|||||||
"docker_validator.go",
|
"docker_validator.go",
|
||||||
"kernel_validator.go",
|
"kernel_validator.go",
|
||||||
"os_validator.go",
|
"os_validator.go",
|
||||||
|
"package_validator.go",
|
||||||
"report.go",
|
"report.go",
|
||||||
"types.go",
|
"types.go",
|
||||||
"validators.go",
|
"validators.go",
|
||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//vendor/github.com/blang/semver:go_default_library",
|
||||||
"//vendor/github.com/docker/engine-api/client:go_default_library",
|
"//vendor/github.com/docker/engine-api/client:go_default_library",
|
||||||
"//vendor/github.com/docker/engine-api/types:go_default_library",
|
"//vendor/github.com/docker/engine-api/types:go_default_library",
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
@ -36,6 +38,7 @@ go_test(
|
|||||||
"docker_validator_test.go",
|
"docker_validator_test.go",
|
||||||
"kernel_validator_test.go",
|
"kernel_validator_test.go",
|
||||||
"os_validator_test.go",
|
"os_validator_test.go",
|
||||||
|
"package_validator_test.go",
|
||||||
],
|
],
|
||||||
library = ":go_default_library",
|
library = ":go_default_library",
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
|
325
test/e2e_node/system/package_validator.go
Normal file
325
test/e2e_node/system/package_validator.go
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/errors"
|
||||||
|
|
||||||
|
"github.com/blang/semver"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// semVerDotsCount is the number of dots in a valid semantic version.
|
||||||
|
const semVerDotsCount int = 2
|
||||||
|
|
||||||
|
// packageManager is an interface that abstracts the basic operations of a
|
||||||
|
// package manager.
|
||||||
|
type packageManager interface {
|
||||||
|
// getPackageVersion returns the version of the package given the
|
||||||
|
// packageName, or an error if no such package exists.
|
||||||
|
getPackageVersion(packageName string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPackageManager returns the package manager on the running machine, and an
|
||||||
|
// error if no package managers is available.
|
||||||
|
func newPackageManager() (packageManager, error) {
|
||||||
|
if m, ok := newDPKG(); ok {
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to find package manager")
|
||||||
|
}
|
||||||
|
|
||||||
|
// dpkg implements packageManager. It uses "dpkg-query" to retrieve package
|
||||||
|
// information.
|
||||||
|
type dpkg struct{}
|
||||||
|
|
||||||
|
// newDPKG returns a Debian package manager. It returns (nil, false) if no such
|
||||||
|
// package manager exists on the running machine.
|
||||||
|
func newDPKG() (packageManager, bool) {
|
||||||
|
_, err := exec.LookPath("dpkg-query")
|
||||||
|
if err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return dpkg{}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPackageVersion returns the upstream package version for the package given
|
||||||
|
// the packageName, and an error if no such package exists.
|
||||||
|
func (_ dpkg) getPackageVersion(packageName string) (string, error) {
|
||||||
|
output, err := exec.Command("dpkg-query", "--show", "--showformat='${Version}'", packageName).Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("dpkg-query failed: %s", err)
|
||||||
|
}
|
||||||
|
version := extractUpstreamVersion(string(output))
|
||||||
|
if version == "" {
|
||||||
|
return "", fmt.Errorf("no version information")
|
||||||
|
}
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// packageValidator implements the Validator interface. It validates packages
|
||||||
|
// and their versions.
|
||||||
|
type packageValidator struct {
|
||||||
|
reporter Reporter
|
||||||
|
kernelRelease string
|
||||||
|
osDistro string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the package validator.
|
||||||
|
func (self *packageValidator) Name() string {
|
||||||
|
return "package"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks packages and their versions against the spec using the
|
||||||
|
// package manager on the running machine, and returns an error on any
|
||||||
|
// package/version mismatch.
|
||||||
|
func (self *packageValidator) Validate(spec SysSpec) (error, error) {
|
||||||
|
if len(spec.PackageSpecs) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if self.kernelRelease, err = getKernelRelease(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if self.osDistro, err = getOSDistro(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
manager, err := newPackageManager()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
specs := applyPackageSpecOverride(spec.PackageSpecs, spec.PackageSpecOverrides, self.osDistro)
|
||||||
|
return self.validate(specs, manager)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks packages and their versions against the packageSpecs using
|
||||||
|
// the packageManager, and returns an error on any package/version mismatch.
|
||||||
|
func (self *packageValidator) validate(packageSpecs []PackageSpec, manager packageManager) (error, error) {
|
||||||
|
var errs []error
|
||||||
|
for _, spec := range packageSpecs {
|
||||||
|
// Substitute variables in package name.
|
||||||
|
packageName := resolvePackageName(spec.Name, self.kernelRelease)
|
||||||
|
|
||||||
|
nameWithVerRange := fmt.Sprintf("%s (%s)", packageName, spec.VersionRange)
|
||||||
|
|
||||||
|
// Get the version of the package on the running machine.
|
||||||
|
version, err := manager.getPackageVersion(packageName)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(1).Infof("Failed to get the version for the package %q: %s\n", packageName, err)
|
||||||
|
errs = append(errs, err)
|
||||||
|
self.reporter.Report(nameWithVerRange, "not installed", bad)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version requirement will not be enforced if version range is
|
||||||
|
// not specified in the spec.
|
||||||
|
if spec.VersionRange == "" {
|
||||||
|
self.reporter.Report(packageName, version, good)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert both the version range in the spec and the version returned
|
||||||
|
// from package manager to semantic version format, and then check if
|
||||||
|
// the version is in the range.
|
||||||
|
sv, err := semver.Make(toSemVer(version))
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to convert %q to semantic version: %s\n", version, err)
|
||||||
|
errs = append(errs, err)
|
||||||
|
self.reporter.Report(nameWithVerRange, "internal error", bad)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
versionRange := semver.MustParseRange(toSemVerRange(spec.VersionRange))
|
||||||
|
if versionRange(sv) {
|
||||||
|
self.reporter.Report(nameWithVerRange, version, good)
|
||||||
|
} else {
|
||||||
|
errs = append(errs, fmt.Errorf("package \"%s %s\" does not meet the spec \"%s (%s)\"", packageName, sv, packageName, spec.VersionRange))
|
||||||
|
self.reporter.Report(nameWithVerRange, version, bad)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.NewAggregate(errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getKernelRelease returns the kernel release of the local machine.
|
||||||
|
func getKernelRelease() (string, error) {
|
||||||
|
output, err := exec.Command("uname", "-r").Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get kernel release: %s", err)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(output)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOSDistro returns the OS distro of the local machine.
|
||||||
|
func getOSDistro() (string, error) {
|
||||||
|
f := "/etc/lsb-release"
|
||||||
|
b, err := ioutil.ReadFile(f)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read %q: %s", f, err)
|
||||||
|
}
|
||||||
|
content := string(b)
|
||||||
|
switch {
|
||||||
|
case strings.Contains(content, "Ubuntu"):
|
||||||
|
return "ubuntu", nil
|
||||||
|
case strings.Contains(content, "Chrome OS"):
|
||||||
|
return "cos", nil
|
||||||
|
case strings.Contains(content, "CoreOS"):
|
||||||
|
return "coreos", nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("failed to get OS distro: %s", content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolvePackageName substitutes the variables in the packageName with the
|
||||||
|
// local information.
|
||||||
|
// E.g., "linux-headers-${KERNEL_RELEASE}" -> "linux-headers-4.4.0-75-generic".
|
||||||
|
func resolvePackageName(packageName string, kernelRelease string) string {
|
||||||
|
packageName = strings.Replace(packageName, "${KERNEL_RELEASE}", kernelRelease, -1)
|
||||||
|
return packageName
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyPackageSpecOverride applies the package spec overrides for the given
|
||||||
|
// osDistro to the packageSpecs and returns the applied result.
|
||||||
|
func applyPackageSpecOverride(packageSpecs []PackageSpec, overrides []PackageSpecOverride, osDistro string) []PackageSpec {
|
||||||
|
var override *PackageSpecOverride
|
||||||
|
for _, o := range overrides {
|
||||||
|
if o.OSDistro == osDistro {
|
||||||
|
override = &o
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if override == nil {
|
||||||
|
return packageSpecs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove packages in the spec that matches the overrides in
|
||||||
|
// Subtractions.
|
||||||
|
var out []PackageSpec
|
||||||
|
subtractions := make(map[string]bool)
|
||||||
|
for _, spec := range override.Subtractions {
|
||||||
|
subtractions[spec.Name] = true
|
||||||
|
}
|
||||||
|
for _, spec := range packageSpecs {
|
||||||
|
if _, ok := subtractions[spec.Name]; !ok {
|
||||||
|
out = append(out, spec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add packages in the spec that matches the overrides in Additions.
|
||||||
|
return append(out, override.Additions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractUpstreamVersion returns the upstream version of the given full
|
||||||
|
// version in dpkg format. E.g., "1:1.0.6-2ubuntu2.1" -> "1.0.6".
|
||||||
|
func extractUpstreamVersion(version string) string {
|
||||||
|
// The full version is in the format of
|
||||||
|
// "[epoch:]upstream_version[-debian_revision]". See
|
||||||
|
// https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version.
|
||||||
|
version = strings.Trim(version, " '")
|
||||||
|
if i := strings.Index(version, ":"); i != -1 {
|
||||||
|
version = version[i+1:]
|
||||||
|
}
|
||||||
|
if i := strings.Index(version, "-"); i != -1 {
|
||||||
|
version = version[:i]
|
||||||
|
}
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
// toSemVerRange converts the input to a semantic version range.
|
||||||
|
// E.g., ">=1.0" -> ">=1.0.x"
|
||||||
|
// ">=1" -> ">=1.x"
|
||||||
|
// ">=1 <=2.3" -> ">=1.x <=2.3.x"
|
||||||
|
// ">1 || >3.1.0 !4.2" -> ">1.x || >3.1.0 !4.2.x"
|
||||||
|
func toSemVerRange(input string) string {
|
||||||
|
var output []string
|
||||||
|
fields := strings.Fields(input)
|
||||||
|
for _, f := range fields {
|
||||||
|
numDots, hasDigits := 0, false
|
||||||
|
for _, c := range f {
|
||||||
|
switch {
|
||||||
|
case c == '.':
|
||||||
|
numDots++
|
||||||
|
case c >= '0' && c <= '9':
|
||||||
|
hasDigits = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hasDigits && numDots < semVerDotsCount {
|
||||||
|
f = strings.TrimRight(f, " ")
|
||||||
|
f += ".x"
|
||||||
|
}
|
||||||
|
output = append(output, f)
|
||||||
|
}
|
||||||
|
return strings.Join(output, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// toSemVer converts the input to a semantic version, and an empty string on
|
||||||
|
// error.
|
||||||
|
func toSemVer(version string) string {
|
||||||
|
// Remove the first non-digit and non-dot character as well as the ones
|
||||||
|
// following it.
|
||||||
|
// E.g., "1.8.19p1" -> "1.8.19".
|
||||||
|
if i := strings.IndexFunc(version, func(c rune) bool {
|
||||||
|
if (c < '0' || c > '9') && c != '.' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}); i != -1 {
|
||||||
|
version = version[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the trailing dots if there's any, and then returns an empty
|
||||||
|
// string if nothing left.
|
||||||
|
version = strings.TrimRight(version, ".")
|
||||||
|
if version == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
numDots := strings.Count(version, ".")
|
||||||
|
switch {
|
||||||
|
case numDots < semVerDotsCount:
|
||||||
|
// Add minor version and patch version.
|
||||||
|
// E.g. "1.18" -> "1.18.0" and "481" -> "481.0.0".
|
||||||
|
version += strings.Repeat(".0", semVerDotsCount-numDots)
|
||||||
|
case numDots > semVerDotsCount:
|
||||||
|
// Remove anything beyond the patch version
|
||||||
|
// E.g. "2.0.10.4" -> "2.0.10".
|
||||||
|
for numDots != semVerDotsCount {
|
||||||
|
if i := strings.LastIndex(version, "."); i != -1 {
|
||||||
|
version = version[:i]
|
||||||
|
numDots--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove leading zeros in major/minor/patch version.
|
||||||
|
// E.g., "2.02" -> "2.2"
|
||||||
|
// "8.0.0095" -> "8.0.95"
|
||||||
|
var subs []string
|
||||||
|
for _, s := range strings.Split(version, ".") {
|
||||||
|
s := strings.TrimLeft(s, "0")
|
||||||
|
if s == "" {
|
||||||
|
s = "0"
|
||||||
|
}
|
||||||
|
subs = append(subs, s)
|
||||||
|
}
|
||||||
|
return strings.Join(subs, ".")
|
||||||
|
}
|
266
test/e2e_node/system/package_validator_test.go
Normal file
266
test/e2e_node/system/package_validator_test.go
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExtractUpstreamVersion(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: "",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1.0.6",
|
||||||
|
expected: "1.0.6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1:1.0.6",
|
||||||
|
expected: "1.0.6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1.0.6-2ubuntu2.1",
|
||||||
|
expected: "1.0.6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1:1.0.6-2ubuntu2.1",
|
||||||
|
expected: "1.0.6",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
got := extractUpstreamVersion(test.input)
|
||||||
|
if test.expected != got {
|
||||||
|
t.Errorf("extractUpstreamVersion(%q) = %q, want %q", test.input, got, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToSemVer(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: "",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1.2.3",
|
||||||
|
expected: "1.2.3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1.8.19p1",
|
||||||
|
expected: "1.8.19",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1.8.19.p1",
|
||||||
|
expected: "1.8.19",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "p1",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1.18",
|
||||||
|
expected: "1.18.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "481",
|
||||||
|
expected: "481.0.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "2.0.10.4",
|
||||||
|
expected: "2.0.10",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "03",
|
||||||
|
expected: "3.0.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "2.02",
|
||||||
|
expected: "2.2.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "8.0.0095",
|
||||||
|
expected: "8.0.95",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
got := toSemVer(test.input)
|
||||||
|
if test.expected != got {
|
||||||
|
t.Errorf("toSemVer(%q) = %q, want %q", test.input, got, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToSemVerRange(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: "",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: ">=1.0.0",
|
||||||
|
expected: ">=1.0.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: ">=1.0",
|
||||||
|
expected: ">=1.0.x",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: ">=1",
|
||||||
|
expected: ">=1.x",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: ">=1 || !2.3",
|
||||||
|
expected: ">=1.x || !2.3.x",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: ">1 || >3.1.0 !4.2",
|
||||||
|
expected: ">1.x || >3.1.0 !4.2.x",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
got := toSemVerRange(test.input)
|
||||||
|
if test.expected != got {
|
||||||
|
t.Errorf("toSemVerRange(%q) = %q, want %q", test.input, got, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testPackageManager implements the packageManager interface.
|
||||||
|
type testPackageManager struct {
|
||||||
|
packageVersions map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m testPackageManager) getPackageVersion(packageName string) (string, error) {
|
||||||
|
if v, ok := m.packageVersions[packageName]; ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("package %q does not exist", packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatePackageVersion(t *testing.T) {
|
||||||
|
testKernelRelease := "test-kernel-release"
|
||||||
|
manager := testPackageManager{
|
||||||
|
packageVersions: map[string]string{
|
||||||
|
"foo": "1.0.0",
|
||||||
|
"bar": "2.1.0",
|
||||||
|
"bar-" + testKernelRelease: "3.0.0",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
v := &packageValidator{
|
||||||
|
reporter: DefaultReporter,
|
||||||
|
kernelRelease: testKernelRelease,
|
||||||
|
}
|
||||||
|
for _, test := range []struct {
|
||||||
|
desc string
|
||||||
|
specs []PackageSpec
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "all packages meet the spec",
|
||||||
|
specs: []PackageSpec{
|
||||||
|
{Name: "foo", VersionRange: ">=1.0"},
|
||||||
|
{Name: "bar", VersionRange: ">=2.0 <= 3.0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "package version does not meet the spec",
|
||||||
|
specs: []PackageSpec{
|
||||||
|
{Name: "foo", VersionRange: ">=1.0"},
|
||||||
|
{Name: "bar", VersionRange: ">=3.0"},
|
||||||
|
},
|
||||||
|
err: errors.New("package \"bar 2.1.0\" does not meet the spec \"bar (>=3.0)\""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "package not installed",
|
||||||
|
specs: []PackageSpec{
|
||||||
|
{Name: "baz"},
|
||||||
|
},
|
||||||
|
err: errors.New("package \"baz\" does not exist"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "use variable in package name",
|
||||||
|
specs: []PackageSpec{
|
||||||
|
{Name: "bar-${KERNEL_RELEASE}", VersionRange: ">=3.0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
_, err := v.validate(test.specs, manager)
|
||||||
|
if test.err == nil && err != nil {
|
||||||
|
t.Errorf("%s: v.validate(): err = %s", test.desc, err)
|
||||||
|
}
|
||||||
|
if test.err != nil {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("%s: v.validate() is expected to fail.", test.desc)
|
||||||
|
} else if test.err.Error() != err.Error() {
|
||||||
|
t.Errorf("%s: v.validate(): err = %q, want = %q", test.desc, err, test.err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyPackageOverride(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
overrides []PackageSpecOverride
|
||||||
|
osDistro string
|
||||||
|
specs []PackageSpec
|
||||||
|
expected []PackageSpec
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
specs: []PackageSpec{{Name: "foo", VersionRange: ">=1.0"}},
|
||||||
|
expected: []PackageSpec{{Name: "foo", VersionRange: ">=1.0"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
osDistro: "ubuntu",
|
||||||
|
overrides: []PackageSpecOverride{
|
||||||
|
{
|
||||||
|
OSDistro: "ubuntu",
|
||||||
|
Subtractions: []PackageSpec{{Name: "foo"}},
|
||||||
|
Additions: []PackageSpec{{Name: "bar", VersionRange: ">=2.0"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
specs: []PackageSpec{{Name: "foo", VersionRange: ">=1.0"}},
|
||||||
|
expected: []PackageSpec{{Name: "bar", VersionRange: ">=2.0"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
osDistro: "ubuntu",
|
||||||
|
overrides: []PackageSpecOverride{
|
||||||
|
{
|
||||||
|
OSDistro: "debian",
|
||||||
|
Subtractions: []PackageSpec{{Name: "foo"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
specs: []PackageSpec{{Name: "foo", VersionRange: ">=1.0"}},
|
||||||
|
expected: []PackageSpec{{Name: "foo", VersionRange: ">=1.0"}},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
got := applyPackageSpecOverride(test.specs, test.overrides, test.osDistro)
|
||||||
|
if !reflect.DeepEqual(test.expected, got) {
|
||||||
|
t.Errorf("applyPackageSpecOverride(%+v, %+v, %s) = %+v, want = %+v", test.specs, test.overrides, test.osDistro, got, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -65,6 +65,41 @@ type RuntimeSpec struct {
|
|||||||
*DockerSpec
|
*DockerSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PackageSpec defines the required packages and their versions.
|
||||||
|
// PackageSpec is only supported on OS distro with Debian package manager.
|
||||||
|
//
|
||||||
|
// TODO(yguo0905): Support operator OR of multiple packages for the case where
|
||||||
|
// either "foo (>=1.0)" or "bar (>=2.0)" is required.
|
||||||
|
type PackageSpec struct {
|
||||||
|
// Name is the name of the package to be checked.
|
||||||
|
Name string
|
||||||
|
// VersionRange represents a range of versions that the package must
|
||||||
|
// satisfy. Note that the version requirement will not be enforced if
|
||||||
|
// the version range is empty. For example,
|
||||||
|
// - "" would match any versions but the package must be installed.
|
||||||
|
// - ">=1" would match "1.0.0", "1.0.1", "1.1.0", and "2.0".
|
||||||
|
// - ">1.0 <2.0" would match between both ranges, so "1.1.1" and "1.8.7"
|
||||||
|
// but not "1.0.0" or "2.0.0".
|
||||||
|
// - "<2.0.0 || >=3.0.0" would match "1.0.0" and "3.0.0" but not "2.0.0".
|
||||||
|
VersionRange string
|
||||||
|
// Description explains the reason behind this package requirements.
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackageSpecOverride defines the overrides on the PackageSpec for an OS
|
||||||
|
// distro.
|
||||||
|
type PackageSpecOverride struct {
|
||||||
|
// OSDistro identifies to which OS distro this override applies.
|
||||||
|
// Must be "ubuntu", "cos" or "coreos".
|
||||||
|
OSDistro string
|
||||||
|
// Subtractions is a list of package names that are excluded from the
|
||||||
|
// package spec.
|
||||||
|
Subtractions []PackageSpec
|
||||||
|
// Additions is a list of additional package requirements included the
|
||||||
|
// package spec.
|
||||||
|
Additions []PackageSpec
|
||||||
|
}
|
||||||
|
|
||||||
// SysSpec defines the requirement of supported system. Currently, it only contains
|
// SysSpec defines the requirement of supported system. Currently, it only contains
|
||||||
// spec for OS, Kernel and Cgroups.
|
// spec for OS, Kernel and Cgroups.
|
||||||
type SysSpec struct {
|
type SysSpec struct {
|
||||||
@ -76,6 +111,11 @@ type SysSpec struct {
|
|||||||
Cgroups []string
|
Cgroups []string
|
||||||
// RuntimeSpec defines the spec for runtime.
|
// RuntimeSpec defines the spec for runtime.
|
||||||
RuntimeSpec RuntimeSpec
|
RuntimeSpec RuntimeSpec
|
||||||
|
// PackageSpec defines the required packages and their versions.
|
||||||
|
PackageSpecs []PackageSpec
|
||||||
|
// PackageSpec defines the overrides of the required packages and their
|
||||||
|
// versions for an OS distro.
|
||||||
|
PackageSpecOverrides []PackageSpecOverride
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultSysSpec is the default SysSpec.
|
// DefaultSysSpec is the default SysSpec.
|
||||||
|
@ -56,6 +56,7 @@ func ValidateDefault(runtime string) (error, error) {
|
|||||||
&OSValidator{Reporter: DefaultReporter},
|
&OSValidator{Reporter: DefaultReporter},
|
||||||
&KernelValidator{Reporter: DefaultReporter},
|
&KernelValidator{Reporter: DefaultReporter},
|
||||||
&CgroupsValidator{Reporter: DefaultReporter},
|
&CgroupsValidator{Reporter: DefaultReporter},
|
||||||
|
&packageValidator{reporter: DefaultReporter},
|
||||||
}
|
}
|
||||||
// Docker-specific validators.
|
// Docker-specific validators.
|
||||||
var dockerValidators = []Validator{
|
var dockerValidators = []Validator{
|
||||||
|
1
vendor/github.com/blang/semver/BUILD
generated
vendored
1
vendor/github.com/blang/semver/BUILD
generated
vendored
@ -11,6 +11,7 @@ go_library(
|
|||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"json.go",
|
"json.go",
|
||||||
|
"range.go",
|
||||||
"semver.go",
|
"semver.go",
|
||||||
"sort.go",
|
"sort.go",
|
||||||
"sql.go",
|
"sql.go",
|
||||||
|
77
vendor/github.com/blang/semver/README.md
generated
vendored
77
vendor/github.com/blang/semver/README.md
generated
vendored
@ -40,10 +40,52 @@ Features
|
|||||||
- Comparator-like comparisons
|
- Comparator-like comparisons
|
||||||
- Compare Helper Methods
|
- Compare Helper Methods
|
||||||
- InPlace manipulation
|
- InPlace manipulation
|
||||||
|
- Ranges `>=1.0.0 <2.0.0 || >=3.0.0 !3.0.1-beta.1`
|
||||||
- Sortable (implements sort.Interface)
|
- Sortable (implements sort.Interface)
|
||||||
- database/sql compatible (sql.Scanner/Valuer)
|
- database/sql compatible (sql.Scanner/Valuer)
|
||||||
- encoding/json compatible (json.Marshaler/Unmarshaler)
|
- encoding/json compatible (json.Marshaler/Unmarshaler)
|
||||||
|
|
||||||
|
Ranges
|
||||||
|
------
|
||||||
|
|
||||||
|
A `Range` is a set of conditions which specify which versions satisfy the range.
|
||||||
|
|
||||||
|
A condition is composed of an operator and a version. The supported operators are:
|
||||||
|
|
||||||
|
- `<1.0.0` Less than `1.0.0`
|
||||||
|
- `<=1.0.0` Less than or equal to `1.0.0`
|
||||||
|
- `>1.0.0` Greater than `1.0.0`
|
||||||
|
- `>=1.0.0` Greater than or equal to `1.0.0`
|
||||||
|
- `1.0.0`, `=1.0.0`, `==1.0.0` Equal to `1.0.0`
|
||||||
|
- `!1.0.0`, `!=1.0.0` Not equal to `1.0.0`. Excludes version `1.0.0`.
|
||||||
|
|
||||||
|
A `Range` can link multiple `Ranges` separated by space:
|
||||||
|
|
||||||
|
Ranges can be linked by logical AND:
|
||||||
|
|
||||||
|
- `>1.0.0 <2.0.0` would match between both ranges, so `1.1.1` and `1.8.7` but not `1.0.0` or `2.0.0`
|
||||||
|
- `>1.0.0 <3.0.0 !2.0.3-beta.2` would match every version between `1.0.0` and `3.0.0` except `2.0.3-beta.2`
|
||||||
|
|
||||||
|
Ranges can also be linked by logical OR:
|
||||||
|
|
||||||
|
- `<2.0.0 || >=3.0.0` would match `1.x.x` and `3.x.x` but not `2.x.x`
|
||||||
|
|
||||||
|
AND has a higher precedence than OR. It's not possible to use brackets.
|
||||||
|
|
||||||
|
Ranges can be combined by both AND and OR
|
||||||
|
|
||||||
|
- `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1`
|
||||||
|
|
||||||
|
Range usage:
|
||||||
|
|
||||||
|
```
|
||||||
|
v, err := semver.Parse("1.2.3")
|
||||||
|
range, err := semver.ParseRange(">1.0.0 <2.0.0 || >=3.0.0")
|
||||||
|
if range(v) {
|
||||||
|
//valid
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
Example
|
Example
|
||||||
-----
|
-----
|
||||||
@ -103,23 +145,30 @@ if err != nil {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Benchmarks
|
Benchmarks
|
||||||
-----
|
-----
|
||||||
|
|
||||||
BenchmarkParseSimple 5000000 328 ns/op 49 B/op 1 allocs/op
|
BenchmarkParseSimple-4 5000000 390 ns/op 48 B/op 1 allocs/op
|
||||||
BenchmarkParseComplex 1000000 2105 ns/op 263 B/op 7 allocs/op
|
BenchmarkParseComplex-4 1000000 1813 ns/op 256 B/op 7 allocs/op
|
||||||
BenchmarkParseAverage 1000000 1301 ns/op 168 B/op 4 allocs/op
|
BenchmarkParseAverage-4 1000000 1171 ns/op 163 B/op 4 allocs/op
|
||||||
BenchmarkStringSimple 10000000 130 ns/op 5 B/op 1 allocs/op
|
BenchmarkStringSimple-4 20000000 119 ns/op 16 B/op 1 allocs/op
|
||||||
BenchmarkStringLarger 5000000 280 ns/op 32 B/op 2 allocs/op
|
BenchmarkStringLarger-4 10000000 206 ns/op 32 B/op 2 allocs/op
|
||||||
BenchmarkStringComplex 3000000 512 ns/op 80 B/op 3 allocs/op
|
BenchmarkStringComplex-4 5000000 324 ns/op 80 B/op 3 allocs/op
|
||||||
BenchmarkStringAverage 5000000 387 ns/op 47 B/op 2 allocs/op
|
BenchmarkStringAverage-4 5000000 273 ns/op 53 B/op 2 allocs/op
|
||||||
BenchmarkValidateSimple 500000000 7.92 ns/op 0 B/op 0 allocs/op
|
BenchmarkValidateSimple-4 200000000 9.33 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkValidateComplex 2000000 923 ns/op 0 B/op 0 allocs/op
|
BenchmarkValidateComplex-4 3000000 469 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkValidateAverage 5000000 452 ns/op 0 B/op 0 allocs/op
|
BenchmarkValidateAverage-4 5000000 256 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkCompareSimple 100000000 11.2 ns/op 0 B/op 0 allocs/op
|
BenchmarkCompareSimple-4 100000000 11.8 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkCompareComplex 50000000 40.9 ns/op 0 B/op 0 allocs/op
|
BenchmarkCompareComplex-4 50000000 30.8 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkCompareAverage 50000000 43.8 ns/op 0 B/op 0 allocs/op
|
BenchmarkCompareAverage-4 30000000 41.5 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkSort 5000000 436 ns/op 259 B/op 2 allocs/op
|
BenchmarkSort-4 3000000 419 ns/op 256 B/op 2 allocs/op
|
||||||
|
BenchmarkRangeParseSimple-4 2000000 850 ns/op 192 B/op 5 allocs/op
|
||||||
|
BenchmarkRangeParseAverage-4 1000000 1677 ns/op 400 B/op 10 allocs/op
|
||||||
|
BenchmarkRangeParseComplex-4 300000 5214 ns/op 1440 B/op 30 allocs/op
|
||||||
|
BenchmarkRangeMatchSimple-4 50000000 25.6 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkRangeMatchAverage-4 30000000 56.4 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkRangeMatchComplex-4 10000000 153 ns/op 0 B/op 0 allocs/op
|
||||||
|
|
||||||
See benchmark cases at [semver_test.go](semver_test.go)
|
See benchmark cases at [semver_test.go](semver_test.go)
|
||||||
|
|
||||||
|
17
vendor/github.com/blang/semver/package.json
generated
vendored
Normal file
17
vendor/github.com/blang/semver/package.json
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"author": "blang",
|
||||||
|
"bugs": {
|
||||||
|
"URL": "https://github.com/blang/semver/issues",
|
||||||
|
"url": "https://github.com/blang/semver/issues"
|
||||||
|
},
|
||||||
|
"gx": {
|
||||||
|
"dvcsimport": "github.com/blang/semver"
|
||||||
|
},
|
||||||
|
"gxVersion": "0.10.0",
|
||||||
|
"language": "go",
|
||||||
|
"license": "MIT",
|
||||||
|
"name": "semver",
|
||||||
|
"releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
|
||||||
|
"version": "3.4.0"
|
||||||
|
}
|
||||||
|
|
416
vendor/github.com/blang/semver/range.go
generated
vendored
Normal file
416
vendor/github.com/blang/semver/range.go
generated
vendored
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
package semver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type wildcardType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
noneWildcard wildcardType = iota
|
||||||
|
majorWildcard wildcardType = 1
|
||||||
|
minorWildcard wildcardType = 2
|
||||||
|
patchWildcard wildcardType = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
func wildcardTypefromInt(i int) wildcardType {
|
||||||
|
switch i {
|
||||||
|
case 1:
|
||||||
|
return majorWildcard
|
||||||
|
case 2:
|
||||||
|
return minorWildcard
|
||||||
|
case 3:
|
||||||
|
return patchWildcard
|
||||||
|
default:
|
||||||
|
return noneWildcard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type comparator func(Version, Version) bool
|
||||||
|
|
||||||
|
var (
|
||||||
|
compEQ comparator = func(v1 Version, v2 Version) bool {
|
||||||
|
return v1.Compare(v2) == 0
|
||||||
|
}
|
||||||
|
compNE = func(v1 Version, v2 Version) bool {
|
||||||
|
return v1.Compare(v2) != 0
|
||||||
|
}
|
||||||
|
compGT = func(v1 Version, v2 Version) bool {
|
||||||
|
return v1.Compare(v2) == 1
|
||||||
|
}
|
||||||
|
compGE = func(v1 Version, v2 Version) bool {
|
||||||
|
return v1.Compare(v2) >= 0
|
||||||
|
}
|
||||||
|
compLT = func(v1 Version, v2 Version) bool {
|
||||||
|
return v1.Compare(v2) == -1
|
||||||
|
}
|
||||||
|
compLE = func(v1 Version, v2 Version) bool {
|
||||||
|
return v1.Compare(v2) <= 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type versionRange struct {
|
||||||
|
v Version
|
||||||
|
c comparator
|
||||||
|
}
|
||||||
|
|
||||||
|
// rangeFunc creates a Range from the given versionRange.
|
||||||
|
func (vr *versionRange) rangeFunc() Range {
|
||||||
|
return Range(func(v Version) bool {
|
||||||
|
return vr.c(v, vr.v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range represents a range of versions.
|
||||||
|
// A Range can be used to check if a Version satisfies it:
|
||||||
|
//
|
||||||
|
// range, err := semver.ParseRange(">1.0.0 <2.0.0")
|
||||||
|
// range(semver.MustParse("1.1.1") // returns true
|
||||||
|
type Range func(Version) bool
|
||||||
|
|
||||||
|
// OR combines the existing Range with another Range using logical OR.
|
||||||
|
func (rf Range) OR(f Range) Range {
|
||||||
|
return Range(func(v Version) bool {
|
||||||
|
return rf(v) || f(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AND combines the existing Range with another Range using logical AND.
|
||||||
|
func (rf Range) AND(f Range) Range {
|
||||||
|
return Range(func(v Version) bool {
|
||||||
|
return rf(v) && f(v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseRange parses a range and returns a Range.
|
||||||
|
// If the range could not be parsed an error is returned.
|
||||||
|
//
|
||||||
|
// Valid ranges are:
|
||||||
|
// - "<1.0.0"
|
||||||
|
// - "<=1.0.0"
|
||||||
|
// - ">1.0.0"
|
||||||
|
// - ">=1.0.0"
|
||||||
|
// - "1.0.0", "=1.0.0", "==1.0.0"
|
||||||
|
// - "!1.0.0", "!=1.0.0"
|
||||||
|
//
|
||||||
|
// A Range can consist of multiple ranges separated by space:
|
||||||
|
// Ranges can be linked by logical AND:
|
||||||
|
// - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0"
|
||||||
|
// - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2
|
||||||
|
//
|
||||||
|
// Ranges can also be linked by logical OR:
|
||||||
|
// - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x"
|
||||||
|
//
|
||||||
|
// AND has a higher precedence than OR. It's not possible to use brackets.
|
||||||
|
//
|
||||||
|
// Ranges can be combined by both AND and OR
|
||||||
|
//
|
||||||
|
// - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1`
|
||||||
|
func ParseRange(s string) (Range, error) {
|
||||||
|
parts := splitAndTrim(s)
|
||||||
|
orParts, err := splitORParts(parts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
expandedParts, err := expandWildcardVersion(orParts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var orFn Range
|
||||||
|
for _, p := range expandedParts {
|
||||||
|
var andFn Range
|
||||||
|
for _, ap := range p {
|
||||||
|
opStr, vStr, err := splitComparatorVersion(ap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vr, err := buildVersionRange(opStr, vStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err)
|
||||||
|
}
|
||||||
|
rf := vr.rangeFunc()
|
||||||
|
|
||||||
|
// Set function
|
||||||
|
if andFn == nil {
|
||||||
|
andFn = rf
|
||||||
|
} else { // Combine with existing function
|
||||||
|
andFn = andFn.AND(rf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if orFn == nil {
|
||||||
|
orFn = andFn
|
||||||
|
} else {
|
||||||
|
orFn = orFn.OR(andFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return orFn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitORParts splits the already cleaned parts by '||'.
|
||||||
|
// Checks for invalid positions of the operator and returns an
|
||||||
|
// error if found.
|
||||||
|
func splitORParts(parts []string) ([][]string, error) {
|
||||||
|
var ORparts [][]string
|
||||||
|
last := 0
|
||||||
|
for i, p := range parts {
|
||||||
|
if p == "||" {
|
||||||
|
if i == 0 {
|
||||||
|
return nil, fmt.Errorf("First element in range is '||'")
|
||||||
|
}
|
||||||
|
ORparts = append(ORparts, parts[last:i])
|
||||||
|
last = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if last == len(parts) {
|
||||||
|
return nil, fmt.Errorf("Last element in range is '||'")
|
||||||
|
}
|
||||||
|
ORparts = append(ORparts, parts[last:])
|
||||||
|
return ORparts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildVersionRange takes a slice of 2: operator and version
|
||||||
|
// and builds a versionRange, otherwise an error.
|
||||||
|
func buildVersionRange(opStr, vStr string) (*versionRange, error) {
|
||||||
|
c := parseComparator(opStr)
|
||||||
|
if c == nil {
|
||||||
|
return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, ""))
|
||||||
|
}
|
||||||
|
v, err := Parse(vStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &versionRange{
|
||||||
|
v: v,
|
||||||
|
c: c,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// inArray checks if a byte is contained in an array of bytes
|
||||||
|
func inArray(s byte, list []byte) bool {
|
||||||
|
for _, el := range list {
|
||||||
|
if el == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitAndTrim splits a range string by spaces and cleans whitespaces
|
||||||
|
func splitAndTrim(s string) (result []string) {
|
||||||
|
last := 0
|
||||||
|
var lastChar byte
|
||||||
|
excludeFromSplit := []byte{'>', '<', '='}
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) {
|
||||||
|
if last < i-1 {
|
||||||
|
result = append(result, s[last:i])
|
||||||
|
}
|
||||||
|
last = i + 1
|
||||||
|
} else if s[i] != ' ' {
|
||||||
|
lastChar = s[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if last < len(s)-1 {
|
||||||
|
result = append(result, s[last:])
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range result {
|
||||||
|
result[i] = strings.Replace(v, " ", "", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parts := strings.Split(s, " ")
|
||||||
|
// for _, x := range parts {
|
||||||
|
// if s := strings.TrimSpace(x); len(s) != 0 {
|
||||||
|
// result = append(result, s)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitComparatorVersion splits the comparator from the version.
|
||||||
|
// Input must be free of leading or trailing spaces.
|
||||||
|
func splitComparatorVersion(s string) (string, string, error) {
|
||||||
|
i := strings.IndexFunc(s, unicode.IsDigit)
|
||||||
|
if i == -1 {
|
||||||
|
return "", "", fmt.Errorf("Could not get version from string: %q", s)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(s[0:i]), s[i:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getWildcardType will return the type of wildcard that the
|
||||||
|
// passed version contains
|
||||||
|
func getWildcardType(vStr string) wildcardType {
|
||||||
|
parts := strings.Split(vStr, ".")
|
||||||
|
nparts := len(parts)
|
||||||
|
wildcard := parts[nparts-1]
|
||||||
|
|
||||||
|
possibleWildcardType := wildcardTypefromInt(nparts)
|
||||||
|
if wildcard == "x" {
|
||||||
|
return possibleWildcardType
|
||||||
|
}
|
||||||
|
|
||||||
|
return noneWildcard
|
||||||
|
}
|
||||||
|
|
||||||
|
// createVersionFromWildcard will convert a wildcard version
|
||||||
|
// into a regular version, replacing 'x's with '0's, handling
|
||||||
|
// special cases like '1.x.x' and '1.x'
|
||||||
|
func createVersionFromWildcard(vStr string) string {
|
||||||
|
// handle 1.x.x
|
||||||
|
vStr2 := strings.Replace(vStr, ".x.x", ".x", 1)
|
||||||
|
vStr2 = strings.Replace(vStr2, ".x", ".0", 1)
|
||||||
|
parts := strings.Split(vStr2, ".")
|
||||||
|
|
||||||
|
// handle 1.x
|
||||||
|
if len(parts) == 2 {
|
||||||
|
return vStr2 + ".0"
|
||||||
|
}
|
||||||
|
|
||||||
|
return vStr2
|
||||||
|
}
|
||||||
|
|
||||||
|
// incrementMajorVersion will increment the major version
|
||||||
|
// of the passed version
|
||||||
|
func incrementMajorVersion(vStr string) (string, error) {
|
||||||
|
parts := strings.Split(vStr, ".")
|
||||||
|
i, err := strconv.Atoi(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
parts[0] = strconv.Itoa(i + 1)
|
||||||
|
|
||||||
|
return strings.Join(parts, "."), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// incrementMajorVersion will increment the minor version
|
||||||
|
// of the passed version
|
||||||
|
func incrementMinorVersion(vStr string) (string, error) {
|
||||||
|
parts := strings.Split(vStr, ".")
|
||||||
|
i, err := strconv.Atoi(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
parts[1] = strconv.Itoa(i + 1)
|
||||||
|
|
||||||
|
return strings.Join(parts, "."), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// expandWildcardVersion will expand wildcards inside versions
|
||||||
|
// following these rules:
|
||||||
|
//
|
||||||
|
// * when dealing with patch wildcards:
|
||||||
|
// >= 1.2.x will become >= 1.2.0
|
||||||
|
// <= 1.2.x will become < 1.3.0
|
||||||
|
// > 1.2.x will become >= 1.3.0
|
||||||
|
// < 1.2.x will become < 1.2.0
|
||||||
|
// != 1.2.x will become < 1.2.0 >= 1.3.0
|
||||||
|
//
|
||||||
|
// * when dealing with minor wildcards:
|
||||||
|
// >= 1.x will become >= 1.0.0
|
||||||
|
// <= 1.x will become < 2.0.0
|
||||||
|
// > 1.x will become >= 2.0.0
|
||||||
|
// < 1.0 will become < 1.0.0
|
||||||
|
// != 1.x will become < 1.0.0 >= 2.0.0
|
||||||
|
//
|
||||||
|
// * when dealing with wildcards without
|
||||||
|
// version operator:
|
||||||
|
// 1.2.x will become >= 1.2.0 < 1.3.0
|
||||||
|
// 1.x will become >= 1.0.0 < 2.0.0
|
||||||
|
func expandWildcardVersion(parts [][]string) ([][]string, error) {
|
||||||
|
var expandedParts [][]string
|
||||||
|
for _, p := range parts {
|
||||||
|
var newParts []string
|
||||||
|
for _, ap := range p {
|
||||||
|
if strings.Index(ap, "x") != -1 {
|
||||||
|
opStr, vStr, err := splitComparatorVersion(ap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
versionWildcardType := getWildcardType(vStr)
|
||||||
|
flatVersion := createVersionFromWildcard(vStr)
|
||||||
|
|
||||||
|
var resultOperator string
|
||||||
|
var shouldIncrementVersion bool
|
||||||
|
switch opStr {
|
||||||
|
case ">":
|
||||||
|
resultOperator = ">="
|
||||||
|
shouldIncrementVersion = true
|
||||||
|
case ">=":
|
||||||
|
resultOperator = ">="
|
||||||
|
case "<":
|
||||||
|
resultOperator = "<"
|
||||||
|
case "<=":
|
||||||
|
resultOperator = "<"
|
||||||
|
shouldIncrementVersion = true
|
||||||
|
case "", "=", "==":
|
||||||
|
newParts = append(newParts, ">="+flatVersion)
|
||||||
|
resultOperator = "<"
|
||||||
|
shouldIncrementVersion = true
|
||||||
|
case "!=", "!":
|
||||||
|
newParts = append(newParts, "<"+flatVersion)
|
||||||
|
resultOperator = ">="
|
||||||
|
shouldIncrementVersion = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultVersion string
|
||||||
|
if shouldIncrementVersion {
|
||||||
|
switch versionWildcardType {
|
||||||
|
case patchWildcard:
|
||||||
|
resultVersion, _ = incrementMinorVersion(flatVersion)
|
||||||
|
case minorWildcard:
|
||||||
|
resultVersion, _ = incrementMajorVersion(flatVersion)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resultVersion = flatVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
ap = resultOperator + resultVersion
|
||||||
|
}
|
||||||
|
newParts = append(newParts, ap)
|
||||||
|
}
|
||||||
|
expandedParts = append(expandedParts, newParts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return expandedParts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseComparator(s string) comparator {
|
||||||
|
switch s {
|
||||||
|
case "==":
|
||||||
|
fallthrough
|
||||||
|
case "":
|
||||||
|
fallthrough
|
||||||
|
case "=":
|
||||||
|
return compEQ
|
||||||
|
case ">":
|
||||||
|
return compGT
|
||||||
|
case ">=":
|
||||||
|
return compGE
|
||||||
|
case "<":
|
||||||
|
return compLT
|
||||||
|
case "<=":
|
||||||
|
return compLE
|
||||||
|
case "!":
|
||||||
|
fallthrough
|
||||||
|
case "!=":
|
||||||
|
return compNE
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustParseRange is like ParseRange but panics if the range cannot be parsed.
|
||||||
|
func MustParseRange(s string) Range {
|
||||||
|
r, err := ParseRange(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(`semver: ParseRange(` + s + `): ` + err.Error())
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
23
vendor/github.com/blang/semver/semver.go
generated
vendored
23
vendor/github.com/blang/semver/semver.go
generated
vendored
@ -200,6 +200,29 @@ func Make(s string) (Version, error) {
|
|||||||
return Parse(s)
|
return Parse(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseTolerant allows for certain version specifications that do not strictly adhere to semver
|
||||||
|
// specs to be parsed by this library. It does so by normalizing versions before passing them to
|
||||||
|
// Parse(). It currently trims spaces, removes a "v" prefix, and adds a 0 patch number to versions
|
||||||
|
// with only major and minor components specified
|
||||||
|
func ParseTolerant(s string) (Version, error) {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
s = strings.TrimPrefix(s, "v")
|
||||||
|
|
||||||
|
// Split into major.minor.(patch+pr+meta)
|
||||||
|
parts := strings.SplitN(s, ".", 3)
|
||||||
|
if len(parts) < 3 {
|
||||||
|
if strings.ContainsAny(parts[len(parts)-1], "+-") {
|
||||||
|
return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data")
|
||||||
|
}
|
||||||
|
for len(parts) < 3 {
|
||||||
|
parts = append(parts, "0")
|
||||||
|
}
|
||||||
|
s = strings.Join(parts, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Parse(s)
|
||||||
|
}
|
||||||
|
|
||||||
// Parse parses version string and returns a validated Version or error
|
// Parse parses version string and returns a validated Version or error
|
||||||
func Parse(s string) (Version, error) {
|
func Parse(s string) (Version, error) {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
|
Loading…
Reference in New Issue
Block a user