From 80041039ab93578280e485d711010ec55239914f Mon Sep 17 00:00:00 2001 From: Francesco Romani Date: Sun, 13 Feb 2022 10:31:17 +0100 Subject: [PATCH 1/7] clientutil: add support for discovery client The kubernetes discovery client allows to introspect the cluster capabilities, for example the API server. Add support in the clientutil to create such a client. Signed-off-by: Francesco Romani --- pkg/clientutil/client.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/clientutil/client.go b/pkg/clientutil/client.go index 34a1512f..366f3766 100644 --- a/pkg/clientutil/client.go +++ b/pkg/clientutil/client.go @@ -17,6 +17,7 @@ package clientutil import ( + "k8s.io/client-go/discovery" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" @@ -73,6 +74,19 @@ func NewK8sExt() (*apiextension.Clientset, error) { return clientset, nil } +func NewDiscoveryClient() (*discovery.DiscoveryClient, error) { + cfg, err := config.GetConfig() + if err != nil { + return nil, err + } + + cli, err := discovery.NewDiscoveryClientForConfig(cfg) + if err != nil { + return nil, err + } + return cli, nil +} + func NewTopologyClient() (*topologyclientset.Clientset, error) { cfg, err := config.GetConfig() if err != nil { From 37b1f95674bc6c13a4b48c3d81d5aeca5ecdf872 Mon Sep 17 00:00:00 2001 From: Francesco Romani Date: Sun, 13 Feb 2022 10:33:40 +0100 Subject: [PATCH 2/7] validate: overhaul: check cluster version Overhaul the `validate` package to check and store the cluster version. Breaks the API - will require a major bump. In this change, we add support to check the cluster version, enabling smarter validation check down the road (e.g. feature gates). Now validation helpers can (and should) alter the validation state accumulating intermediate results - while still returning their specific validation results. Same goes for the cluster version - it needs to be stored in the Validator object, and possibly to be checked first. This is why we introduce and recommend create functions. Signed-off-by: Francesco Romani --- go.mod | 2 + go.sum | 3 + pkg/commands/validate.go | 13 +- pkg/validator/clusterversion.go | 83 +++++++ pkg/validator/clusterversion_test.go | 71 ++++++ pkg/validator/kubeletconfig.go | 78 ++++--- pkg/validator/kubeletconfig_test.go | 3 +- pkg/validator/validator.go | 40 ++++ .../aquasecurity/go-version/LICENSE | 201 +++++++++++++++++ .../aquasecurity/go-version/pkg/part/any.go | 33 +++ .../aquasecurity/go-version/pkg/part/empty.go | 28 +++ .../go-version/pkg/part/infinity.go | 51 +++++ .../aquasecurity/go-version/pkg/part/int.go | 58 +++++ .../aquasecurity/go-version/pkg/part/list.go | 149 +++++++++++++ .../aquasecurity/go-version/pkg/part/part.go | 21 ++ .../go-version/pkg/part/string.go | 94 ++++++++ .../go-version/pkg/prerelease/prerelease.go | 22 ++ .../go-version/pkg/version/constraint.go | 205 ++++++++++++++++++ .../go-version/pkg/version/version.go | 199 +++++++++++++++++ .../pkg/version/version_collection.go | 17 ++ vendor/golang.org/x/xerrors/LICENSE | 27 +++ vendor/golang.org/x/xerrors/PATENTS | 22 ++ vendor/golang.org/x/xerrors/README | 2 + vendor/golang.org/x/xerrors/adaptor.go | 193 +++++++++++++++++ vendor/golang.org/x/xerrors/codereview.cfg | 1 + vendor/golang.org/x/xerrors/doc.go | 22 ++ vendor/golang.org/x/xerrors/errors.go | 33 +++ vendor/golang.org/x/xerrors/fmt.go | 187 ++++++++++++++++ vendor/golang.org/x/xerrors/format.go | 34 +++ vendor/golang.org/x/xerrors/frame.go | 56 +++++ vendor/golang.org/x/xerrors/go.mod | 3 + .../golang.org/x/xerrors/internal/internal.go | 8 + vendor/golang.org/x/xerrors/wrap.go | 106 +++++++++ vendor/modules.txt | 9 + 34 files changed, 2036 insertions(+), 38 deletions(-) create mode 100644 pkg/validator/clusterversion.go create mode 100644 pkg/validator/clusterversion_test.go create mode 100644 vendor/github.com/aquasecurity/go-version/LICENSE create mode 100644 vendor/github.com/aquasecurity/go-version/pkg/part/any.go create mode 100644 vendor/github.com/aquasecurity/go-version/pkg/part/empty.go create mode 100644 vendor/github.com/aquasecurity/go-version/pkg/part/infinity.go create mode 100644 vendor/github.com/aquasecurity/go-version/pkg/part/int.go create mode 100644 vendor/github.com/aquasecurity/go-version/pkg/part/list.go create mode 100644 vendor/github.com/aquasecurity/go-version/pkg/part/part.go create mode 100644 vendor/github.com/aquasecurity/go-version/pkg/part/string.go create mode 100644 vendor/github.com/aquasecurity/go-version/pkg/prerelease/prerelease.go create mode 100644 vendor/github.com/aquasecurity/go-version/pkg/version/constraint.go create mode 100644 vendor/github.com/aquasecurity/go-version/pkg/version/version.go create mode 100644 vendor/github.com/aquasecurity/go-version/pkg/version/version_collection.go create mode 100644 vendor/golang.org/x/xerrors/LICENSE create mode 100644 vendor/golang.org/x/xerrors/PATENTS create mode 100644 vendor/golang.org/x/xerrors/README create mode 100644 vendor/golang.org/x/xerrors/adaptor.go create mode 100644 vendor/golang.org/x/xerrors/codereview.cfg create mode 100644 vendor/golang.org/x/xerrors/doc.go create mode 100644 vendor/golang.org/x/xerrors/errors.go create mode 100644 vendor/golang.org/x/xerrors/fmt.go create mode 100644 vendor/golang.org/x/xerrors/format.go create mode 100644 vendor/golang.org/x/xerrors/frame.go create mode 100644 vendor/golang.org/x/xerrors/go.mod create mode 100644 vendor/golang.org/x/xerrors/internal/internal.go create mode 100644 vendor/golang.org/x/xerrors/wrap.go diff --git a/go.mod b/go.mod index 35a9d9c2..9c81b4d0 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/k8stopologyawareschedwg/deployer go 1.16 require ( + github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 github.com/coreos/ignition/v2 v2.7.0 github.com/hashicorp/go-version v1.2.0 github.com/k8stopologyawareschedwg/noderesourcetopology-api v0.0.12 @@ -16,6 +17,7 @@ require ( k8s.io/apiextensions-apiserver v0.22.3 k8s.io/apimachinery v0.22.3 k8s.io/client-go v0.22.3 + k8s.io/klog/v2 v2.10.0 k8s.io/kube-scheduler v0.22.3 k8s.io/kubelet v0.22.3 k8s.io/kubernetes v1.22.3 // indirect diff --git a/go.sum b/go.sum index 09757fdc..9b5cc9b8 100644 --- a/go.sum +++ b/go.sum @@ -158,6 +158,8 @@ github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apparentlymart/go-cidr v1.0.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= +github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 h1:rcEG5HI490FF0a7zuvxOxen52ddygCfNVjP0XOCMl+M= +github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -1339,6 +1341,7 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/uudashr/gocognit v1.0.5/go.mod h1:wgYz0mitoKOTysqxTDMOUXg+Jb5SvtihkfmugIZYpEA= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= diff --git a/pkg/commands/validate.go b/pkg/commands/validate.go index 5b93a927..9b24c476 100644 --- a/pkg/commands/validate.go +++ b/pkg/commands/validate.go @@ -51,20 +51,21 @@ type validationOutput struct { } func validateCluster(cmd *cobra.Command, commonOpts *CommonOptions, opts *validateOptions, args []string) error { - nodeList, err := nodes.GetWorkers() + vd, err := validator.NewValidator(commonOpts.DebugLog) if err != nil { return err } - vd := validator.Validator{ - Log: commonOpts.DebugLog, - } - items, err := vd.ValidateClusterConfig(nodeList) + nodeList, err := nodes.GetWorkers() if err != nil { return err } - printValidationResults(items, opts.jsonOutput) + if _, err := vd.ValidateClusterConfig(nodeList); err != nil { + return err + } + + printValidationResults(vd.Results(), opts.jsonOutput) return nil } diff --git a/pkg/validator/clusterversion.go b/pkg/validator/clusterversion.go new file mode 100644 index 00000000..49f1475c --- /dev/null +++ b/pkg/validator/clusterversion.go @@ -0,0 +1,83 @@ +/* + * 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. + * + * Copyright 2022 Red Hat, Inc. + */ + +package validator + +import ( + "k8s.io/client-go/discovery" + + goversion "github.com/aquasecurity/go-version/pkg/version" +) + +const ( + ExpectedMinKubeVersion = "1.21" +) + +const ( + ComponentAPIVersion = "API Version" +) + +func (vd *Validator) ValidateClusterVersion(cli *discovery.DiscoveryClient) ([]ValidationResult, error) { + ver, err := cli.ServerVersion() + if err != nil { + return nil, err + } + vd.serverVersion = ver + vrs := ValidateClusterVersion(ver.GitVersion) + vd.results = append(vd.results, vrs...) + return vrs, nil +} + +func ValidateClusterVersion(clusterVersion string) []ValidationResult { + ok, err := isAPIVersionAtLeast(clusterVersion, ExpectedMinKubeVersion) + if err != nil { + return []ValidationResult{ + { + /* no specific nodes: all are affected! */ + Area: AreaCluster, + Component: ComponentAPIVersion, + /* no specific Setting: implicit in the component! */ + Expected: "valid version", + Detected: err.Error(), + }, + } + } + if !ok { + return []ValidationResult{ + { + /* no specific nodes: all are affected! */ + Area: AreaCluster, + Component: ComponentAPIVersion, + /* no specific Setting: implicit in the component! */ + Expected: ExpectedMinKubeVersion, + Detected: clusterVersion, + }, + } + } + return nil +} + +func isAPIVersionAtLeast(server, refver string) (bool, error) { + ref, err := goversion.Parse(refver) + if err != nil { + return false, err + } + ser, err := goversion.Parse(server) + if err != nil { + return false, err + } + return ser.Compare(ref) >= 0, nil +} diff --git a/pkg/validator/clusterversion_test.go b/pkg/validator/clusterversion_test.go new file mode 100644 index 00000000..7e1c7bfd --- /dev/null +++ b/pkg/validator/clusterversion_test.go @@ -0,0 +1,71 @@ +/* + * 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. + * + * Copyright 2022 Red Hat, Inc. + */ + +package validator + +import ( + "testing" +) + +func TestClusterVersionValidations(t *testing.T) { + type testCase struct { + version string + expected []ValidationResult + } + + testCases := []testCase{ + { + version: "1.23", + expected: []ValidationResult{}, + }, + { + version: "", + expected: []ValidationResult{ + { + Area: AreaCluster, + Component: ComponentAPIVersion, + }, + }, + }, + { + version: "INVALID", + expected: []ValidationResult{ + { + Area: AreaCluster, + Component: ComponentAPIVersion, + }, + }, + }, + { + version: "1.10", + expected: []ValidationResult{ + { + Area: AreaCluster, + Component: ComponentAPIVersion, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.version, func(t *testing.T) { + got := ValidateClusterVersion(tc.version) + if !matchValidationResults(tc.expected, got) { + t.Fatalf("validation failed:\nexpected=%#v\ngot=%#v", tc.expected, got) + } + }) + } +} diff --git a/pkg/validator/kubeletconfig.go b/pkg/validator/kubeletconfig.go index 1d3d8797..a0b6bef6 100644 --- a/pkg/validator/kubeletconfig.go +++ b/pkg/validator/kubeletconfig.go @@ -21,16 +21,12 @@ import ( "time" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/version" kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1" "github.com/k8stopologyawareschedwg/deployer/pkg/kubeletconfig" ) -const ( - AreaCluster = "cluster" - AreaKubelet = "kubelet" -) - const ( ComponentConfiguration = "configuration" ComponentFeatureGates = "feature gates" @@ -50,7 +46,11 @@ const ( ExpectedTopologyManagerPolicy = "single-numa-node" ) -func (vd Validator) ValidateClusterConfig(nodes []corev1.Node) ([]ValidationResult, error) { +const ( + kubeMinVersionGetAllocatable = "1.23" +) + +func (vd *Validator) ValidateClusterConfig(nodes []corev1.Node) ([]ValidationResult, error) { nodeNames := []string{} for _, node := range nodes { nodeNames = append(nodeNames, node.Name) @@ -68,7 +68,6 @@ func (vd Validator) ValidateClusterConfig(nodes []corev1.Node) ([]ValidationResu vrs := []ValidationResult{} if len(kubeConfs) == 0 { - vd.Log.Printf("no worker nodes found") vrs = append(vrs, ValidationResult{ /* no specific nodes: all are missing! */ Area: AreaCluster, @@ -79,18 +78,27 @@ func (vd Validator) ValidateClusterConfig(nodes []corev1.Node) ([]ValidationResu }) } else { for nodeName, kubeletConf := range kubeConfs { - vrs = append(vrs, vd.ValidateNodeKubeletConfig(nodeName, kubeletConf)...) + vrs = append(vrs, vd.ValidateNodeKubeletConfig(nodeName, vd.serverVersion, kubeletConf)...) } } + vd.results = append(vd.results, vrs...) return vrs, nil } -func (vd Validator) ValidateNodeKubeletConfig(nodeName string, kubeletConf *kubeletconfigv1beta1.KubeletConfiguration) []ValidationResult { +func (vd *Validator) ValidateNodeKubeletConfig(nodeName string, nodeVersion *version.Info, kubeletConf *kubeletconfigv1beta1.KubeletConfiguration) []ValidationResult { + vrs := ValidateClusterNodeKubeletConfig(nodeName, nodeVersion, kubeletConf) + result := "OK" + if len(vrs) > 0 { + result = fmt.Sprintf("%d issues found", len(vrs)) + } + vd.Log.Printf("validated node %q: %s", nodeName, result) + return vrs +} + +func ValidateClusterNodeKubeletConfig(nodeName string, nodeVersion *version.Info, kubeletConf *kubeletconfigv1beta1.KubeletConfiguration) []ValidationResult { vrs := []ValidationResult{} if kubeletConf == nil { - vd.Log.Printf("missing kubelet configuration for node %q", nodeName) - vrs = append(vrs, ValidationResult{ Node: nodeName, Area: AreaKubelet, @@ -102,25 +110,27 @@ func (vd Validator) ValidateNodeKubeletConfig(nodeName string, kubeletConf *kube return vrs } - if kubeletConf.FeatureGates == nil { - vrs = append(vrs, ValidationResult{ - Node: nodeName, - Area: AreaKubelet, - Component: ComponentFeatureGates, - /* no specific Setting: all are missing! */ - Expected: "present", - Detected: "missing data", - }) - } else { - if enabled := kubeletConf.FeatureGates[ExpectedPodResourcesFeatureGate]; !enabled { + if needCheckFeatureGates(nodeVersion) { + if kubeletConf.FeatureGates == nil { vrs = append(vrs, ValidationResult{ Node: nodeName, Area: AreaKubelet, Component: ComponentFeatureGates, - Setting: ExpectedPodResourcesFeatureGate, - Expected: "enabled", - Detected: "disabled", + /* no specific Setting: all are missing! */ + Expected: "present", + Detected: "missing data", }) + } else { + if enabled := kubeletConf.FeatureGates[ExpectedPodResourcesFeatureGate]; !enabled { + vrs = append(vrs, ValidationResult{ + Node: nodeName, + Area: AreaKubelet, + Component: ComponentFeatureGates, + Setting: ExpectedPodResourcesFeatureGate, + Expected: "enabled", + Detected: "disabled", + }) + } } } @@ -157,10 +167,18 @@ func (vd Validator) ValidateNodeKubeletConfig(nodeName string, kubeletConf *kube Detected: kubeletConf.TopologyManagerPolicy, }) } - result := "OK" - if len(vrs) > 0 { - result = fmt.Sprintf("%d issues found", len(vrs)) - } - vd.Log.Printf("validated node %q: %s", nodeName, result) return vrs } + +func needCheckFeatureGates(nodeVersion *version.Info) bool { + if nodeVersion == nil { + // we don't know, we don't take any risk + return true + } + if nodeVersion.GitVersion == "" { + // ditto + return true + } + ok, _ := isAPIVersionAtLeast(nodeVersion.GitVersion, kubeMinVersionGetAllocatable) + return !ok // note NOT +} diff --git a/pkg/validator/kubeletconfig_test.go b/pkg/validator/kubeletconfig_test.go index 74d095c8..37b842ce 100644 --- a/pkg/validator/kubeletconfig_test.go +++ b/pkg/validator/kubeletconfig_test.go @@ -201,13 +201,12 @@ func TestKubeletValidations(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - got := vd.ValidateNodeKubeletConfig(nodeName, tc.kubeletConf) + got := vd.ValidateNodeKubeletConfig(nodeName, tc.nodeVersion, tc.kubeletConf) if !matchValidationResults(tc.expected, got) { t.Fatalf("validation failed:\nexpected=%#v\ngot=%#v", tc.expected, got) } }) } - } func matchValidationResults(expected, got []ValidationResult) bool { diff --git a/pkg/validator/validator.go b/pkg/validator/validator.go index ce208c2a..62dd609c 100644 --- a/pkg/validator/validator.go +++ b/pkg/validator/validator.go @@ -19,10 +19,46 @@ package validator import ( "fmt" "log" + + "k8s.io/apimachinery/pkg/version" + "k8s.io/client-go/discovery" + + "github.com/k8stopologyawareschedwg/deployer/pkg/clientutil" +) + +const ( + AreaCluster = "cluster" + AreaKubelet = "kubelet" ) type Validator struct { Log *log.Logger + + results []ValidationResult + serverVersion *version.Info +} + +func NewValidatorWithDiscoveryClient(logger *log.Logger, cli *discovery.DiscoveryClient) (*Validator, error) { + vd := &Validator{ + Log: logger, + } + _, err := vd.ValidateClusterVersion(cli) + if err != nil { + return nil, err + } + return vd, nil +} + +func NewValidator(logger *log.Logger) (*Validator, error) { + cli, err := clientutil.NewDiscoveryClient() + if err != nil { + return nil, err + } + return NewValidatorWithDiscoveryClient(logger, cli) +} + +func (vd *Validator) Results() []ValidationResult { + return vd.results } type ValidationResult struct { @@ -35,6 +71,10 @@ type ValidationResult struct { } func (vr ValidationResult) String() string { + if vr.Area == AreaCluster { + return fmt.Sprintf("Incorrect configuration of cluster: component %q setting %q: expected %q detected %q", + vr.Component, vr.Setting, vr.Expected, vr.Detected) + } return fmt.Sprintf("Incorrect configuration of node %q area %q component %q setting %q: expected %q detected %q", vr.Node, vr.Area, vr.Component, vr.Setting, vr.Expected, vr.Detected) } diff --git a/vendor/github.com/aquasecurity/go-version/LICENSE b/vendor/github.com/aquasecurity/go-version/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/vendor/github.com/aquasecurity/go-version/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/aquasecurity/go-version/pkg/part/any.go b/vendor/github.com/aquasecurity/go-version/pkg/part/any.go new file mode 100644 index 00000000..c9536bc6 --- /dev/null +++ b/vendor/github.com/aquasecurity/go-version/pkg/part/any.go @@ -0,0 +1,33 @@ +package part + +import ( + "golang.org/x/xerrors" +) + +type Any bool + +func NewAny(s string) (Any, error) { + if s == "*" || s == "x" || s == "X" { + return true, nil + } + return true, xerrors.New("not wildcard") +} + +func (s Any) Compare(other Part) int { + if s { + return 0 + } + return -1 +} + +func (s Any) IsNull() bool { + return false +} + +func (s Any) IsAny() bool { + return bool(s) +} + +func (s Any) IsEmpty() bool { + return false +} diff --git a/vendor/github.com/aquasecurity/go-version/pkg/part/empty.go b/vendor/github.com/aquasecurity/go-version/pkg/part/empty.go new file mode 100644 index 00000000..4af30162 --- /dev/null +++ b/vendor/github.com/aquasecurity/go-version/pkg/part/empty.go @@ -0,0 +1,28 @@ +package part + +type Empty struct { + any bool +} + +func NewEmpty(any bool) Empty { + return Empty{any: any} +} + +func (s Empty) Compare(other Part) int { + if s.IsAny() { + return 0 + } + return Uint64(0).Compare(other) +} + +func (s Empty) IsNull() bool { + return false +} + +func (s Empty) IsAny() bool { + return s.any +} + +func (s Empty) IsEmpty() bool { + return true +} diff --git a/vendor/github.com/aquasecurity/go-version/pkg/part/infinity.go b/vendor/github.com/aquasecurity/go-version/pkg/part/infinity.go new file mode 100644 index 00000000..7bfe30c6 --- /dev/null +++ b/vendor/github.com/aquasecurity/go-version/pkg/part/infinity.go @@ -0,0 +1,51 @@ +package part + +var Infinity = InfinityType{} + +type InfinityType struct{} + +func (InfinityType) Compare(other Part) int { + switch other.(type) { + case InfinityType: + return 0 + default: + return 1 + } +} + +func (InfinityType) IsNull() bool { + return false +} + +func (InfinityType) IsAny() bool { + return false +} + +func (InfinityType) IsEmpty() bool { + return false +} + +var NegativeInfinity = NegativeInfinityType{} + +type NegativeInfinityType struct{} + +func (NegativeInfinityType) Compare(other Part) int { + switch other.(type) { + case NegativeInfinityType: + return 0 + default: + return -1 + } +} + +func (NegativeInfinityType) IsNull() bool { + return false +} + +func (NegativeInfinityType) IsAny() bool { + return false +} + +func (NegativeInfinityType) IsEmpty() bool { + return false +} diff --git a/vendor/github.com/aquasecurity/go-version/pkg/part/int.go b/vendor/github.com/aquasecurity/go-version/pkg/part/int.go new file mode 100644 index 00000000..04c2a1bc --- /dev/null +++ b/vendor/github.com/aquasecurity/go-version/pkg/part/int.go @@ -0,0 +1,58 @@ +package part + +import ( + "strconv" +) + +const Zero = Uint64(0) + +type Uint64 uint64 + +func NewUint64(s string) (Uint64, error) { + n, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return 0, err + } + return Uint64(n), nil +} + +func (s Uint64) Compare(other Part) int { + if other == nil { + return 1 + } else if s == other { + return 0 + } + + switch o := other.(type) { + case Uint64: + if s < o { + return -1 + } + return 1 + case String: + return -1 + case PreString: + return 1 + case Any: + return 0 + case Empty: + if o.IsAny() { + return 0 + } + return s.Compare(Uint64(0)) + default: + panic("unknown type") + } +} + +func (s Uint64) IsNull() bool { + return s == 0 +} + +func (s Uint64) IsAny() bool { + return false +} + +func (s Uint64) IsEmpty() bool { + return false +} diff --git a/vendor/github.com/aquasecurity/go-version/pkg/part/list.go b/vendor/github.com/aquasecurity/go-version/pkg/part/list.go new file mode 100644 index 00000000..2796b5b9 --- /dev/null +++ b/vendor/github.com/aquasecurity/go-version/pkg/part/list.go @@ -0,0 +1,149 @@ +package part + +import ( + "fmt" + "reflect" + "strings" +) + +type Parts []Part + +func NewParts(s string) Parts { + var parts []Part + if s == "" { + return parts + } + + for _, p := range strings.Split(s, ".") { + parts = append(parts, NewPart(p)) + } + return parts +} + +func (parts Parts) Normalize() Parts { + ret := make(Parts, len(parts)) + copy(ret, parts) + + for i := len(ret) - 1; i >= 0; i-- { + lastItem := ret[i] + if lastItem.IsNull() { + ret = ret[:i] + continue + } + break + } + return ret +} + +func (parts Parts) Padding(size int, padding Part) Parts { + diff := size - len(parts) + if diff <= 0 { + return parts + } + + padded := parts + for i := 0; i < diff; i++ { + padded = append(padded, padding) + } + return padded +} + +func (parts Parts) Compare(other Part) int { + if other == nil { + return 1 + } else if other.IsAny() { + return 0 + } + + var o Parts + switch t := other.(type) { + case InfinityType: + return -1 + case NegativeInfinityType: + return 1 + case Parts: + o = t + default: + return -1 + } + + if reflect.DeepEqual(parts, o) { + return 0 + } + + iter := parts.Zip(o) + for tuple := iter(); tuple != nil; tuple = iter() { + var l, r = tuple.Left, tuple.Right + if l == nil { + return -1 + } + if r == nil { + return 1 + } + + if l.IsAny() || r.IsAny() { + return 0 + } + + if result := l.Compare(r); result != 0 { + return result + } + } + return 0 +} + +func (parts Parts) IsNull() bool { + return parts.IsAny() || len(parts) == 0 +} + +func (parts Parts) IsAny() bool { + for _, p := range parts { + if p.IsAny() { + return true + } + } + return false +} + +func (parts Parts) IsEmpty() bool { + return false +} + +func (parts Parts) String() string { + s := make([]string, len(parts)) + for i, p := range parts { + s[i] = fmt.Sprint(p) + } + return strings.Join(s, ".") +} + +type ZipTuple struct { + Left Part + Right Part +} + +func (parts Parts) Zip(other Parts) func() *ZipTuple { + i := 0 + return func() *ZipTuple { + var part1, part2 Part + if i < len(parts) { + part1 = parts[i] + } + if i < len(other) { + part2 = other[i] + } + if part1 == nil && part2 == nil { + return nil + } + i++ + return &ZipTuple{Left: part1, Right: part2} + } +} + +func Uint64SliceToParts(uint64Parts []Uint64) Parts { + parts := make(Parts, len(uint64Parts)) + for i, u := range uint64Parts { + parts[i] = u + } + return parts +} diff --git a/vendor/github.com/aquasecurity/go-version/pkg/part/part.go b/vendor/github.com/aquasecurity/go-version/pkg/part/part.go new file mode 100644 index 00000000..8cc3e72b --- /dev/null +++ b/vendor/github.com/aquasecurity/go-version/pkg/part/part.go @@ -0,0 +1,21 @@ +package part + +type Part interface { + Compare(Part) int + IsNull() bool + IsAny() bool + IsEmpty() bool +} + +func NewPart(s string) Part { + var p Part + p, err := NewUint64(s) + if err == nil { + return p + } + p, err = NewAny(s) + if err == nil { + return p + } + return NewString(s) +} diff --git a/vendor/github.com/aquasecurity/go-version/pkg/part/string.go b/vendor/github.com/aquasecurity/go-version/pkg/part/string.go new file mode 100644 index 00000000..0cb6b914 --- /dev/null +++ b/vendor/github.com/aquasecurity/go-version/pkg/part/string.go @@ -0,0 +1,94 @@ +package part + +import ( + "strings" +) + +type String string + +func NewString(s string) String { + return String(s) +} + +func (s String) Compare(other Part) int { + if other == nil { + return 1 + } else if s == other { + return 0 + } + + switch o := other.(type) { + case Uint64: + return 1 + case String: + return strings.Compare(string(s), string(o)) + case PreString: + return strings.Compare(string(s), string(o)) + case Any: + return 0 + case Empty: + if o.IsAny() { + return 0 + } + return s.Compare(Uint64(0)) + } + return 0 +} + +func (s String) IsNull() bool { + return s == "" +} + +func (s String) IsAny() bool { + return false +} + +func (s String) IsEmpty() bool { + return false +} + +// PreString is less than the number +// e.g. a < 1 +type PreString string + +func NewPreString(s string) PreString { + return PreString(s) +} + +func (s PreString) Compare(other Part) int { + if other == nil { + return 1 + } else if s == other { + return 0 + } + + switch o := other.(type) { + case Uint64: + return -1 + case String: + return strings.Compare(string(s), string(o)) + case PreString: + return strings.Compare(string(s), string(o)) + case Any: + return 0 + case Empty: + if o.IsAny() { + return 0 + } + + return s.Compare(Uint64(0)) + } + return 0 +} + +func (s PreString) IsNull() bool { + return s == "" +} + +func (s PreString) IsAny() bool { + return false +} + +func (s PreString) IsEmpty() bool { + return false +} diff --git a/vendor/github.com/aquasecurity/go-version/pkg/prerelease/prerelease.go b/vendor/github.com/aquasecurity/go-version/pkg/prerelease/prerelease.go new file mode 100644 index 00000000..3df34c64 --- /dev/null +++ b/vendor/github.com/aquasecurity/go-version/pkg/prerelease/prerelease.go @@ -0,0 +1,22 @@ +package prerelease + +import ( + "reflect" + + "github.com/aquasecurity/go-version/pkg/part" +) + +func Compare(p1, p2 part.Parts) int { + switch { + case reflect.DeepEqual(p1, p2): + return 0 + case p1.IsAny() || p2.IsAny(): + return 0 + case p1.IsNull(): + return 1 + case p2.IsNull(): + return -1 + } + + return p1.Compare(p2) +} diff --git a/vendor/github.com/aquasecurity/go-version/pkg/version/constraint.go b/vendor/github.com/aquasecurity/go-version/pkg/version/constraint.go new file mode 100644 index 00000000..6bb01755 --- /dev/null +++ b/vendor/github.com/aquasecurity/go-version/pkg/version/constraint.go @@ -0,0 +1,205 @@ +package version + +import ( + "fmt" + "regexp" + "strings" + + "golang.org/x/xerrors" +) + +var ( + constraintOperators = map[string]operatorFunc{ + "": constraintEqual, + "=": constraintEqual, + "==": constraintEqual, + "!=": constraintNotEqual, + ">": constraintGreaterThan, + "<": constraintLessThan, + ">=": constraintGreaterThanEqual, + "=>": constraintGreaterThanEqual, + "<=": constraintLessThanEqual, + "=<": constraintLessThanEqual, + "~>": constraintPessimistic, + "~": constraintTilde, + "^": constraintCaret, + } + constraintRegexp *regexp.Regexp + validConstraintRegexp *regexp.Regexp +) + +type operatorFunc func(v, c Version) bool + +func init() { + ops := make([]string, 0, len(constraintOperators)) + for k := range constraintOperators { + ops = append(ops, regexp.QuoteMeta(k)) + } + + constraintRegexp = regexp.MustCompile(fmt.Sprintf( + `(%s)\s*(%s)`, + strings.Join(ops, "|"), + regex)) + + validConstraintRegexp = regexp.MustCompile(fmt.Sprintf( + `^\s*(\s*(%s)\s*(%s)\s*\,?)*\s*$`, + strings.Join(ops, "|"), + regex)) +} + +// Constraints is one or more constraint that a version can be checked against. +type Constraints struct { + constraints [][]constraint +} + +type constraint struct { + version Version + operator operatorFunc + original string +} + +// NewConstraints parses a given constraint and returns a new instance of Constraints +func NewConstraints(v string) (Constraints, error) { + var css [][]constraint + for _, vv := range strings.Split(v, "||") { + // Validate the segment + if !validConstraintRegexp.MatchString(vv) { + return Constraints{}, xerrors.Errorf("improper constraint: %s", vv) + } + + ss := constraintRegexp.FindAllString(vv, -1) + if ss == nil { + ss = append(ss, strings.TrimSpace(vv)) + } + + var cs []constraint + for _, single := range ss { + c, err := newConstraint(single) + if err != nil { + return Constraints{}, err + } + cs = append(cs, c) + } + css = append(css, cs) + } + + return Constraints{ + constraints: css, + }, nil + +} + +func newConstraint(c string) (constraint, error) { + m := constraintRegexp.FindStringSubmatch(c) + if m == nil { + return constraint{}, xerrors.Errorf("improper constraint: %s", c) + } + + v, err := Parse(m[2]) + if err != nil { + return constraint{}, xerrors.Errorf("version parse error (%s): %w", m[2], err) + } + + return constraint{ + version: v, + operator: constraintOperators[m[1]], + original: c, + }, nil +} + +func (c constraint) check(v Version) bool { + return c.operator(v, c.version) +} + +func (c constraint) String() string { + return c.original +} + +// Check tests if a version satisfies all the constraints. +func (cs Constraints) Check(v Version) bool { + for _, c := range cs.constraints { + if andCheck(v, c) { + return true + } + } + + return false +} + +// Returns the string format of the constraints +func (cs Constraints) String() string { + var csStr []string + for _, orC := range cs.constraints { + var cstr []string + for _, andC := range orC { + cstr = append(cstr, andC.String()) + } + csStr = append(csStr, strings.Join(cstr, ",")) + } + + return strings.Join(csStr, "||") +} + +func andCheck(v Version, constraints []constraint) bool { + for _, c := range constraints { + if !c.check(v) { + return false + } + } + return true +} + +//------------------------------------------------------------------- +// Constraint functions +//------------------------------------------------------------------- + +func constraintEqual(v, c Version) bool { + return v.Equal(c) +} + +func constraintNotEqual(v, c Version) bool { + return !v.Equal(c) +} + +func constraintGreaterThan(v, c Version) bool { + return v.GreaterThan(c) +} + +func constraintLessThan(v, c Version) bool { + return v.LessThan(c) +} + +func constraintGreaterThanEqual(v, c Version) bool { + return v.GreaterThanOrEqual(c) +} + +func constraintLessThanEqual(v, c Version) bool { + return v.LessThanOrEqual(c) +} + +func constraintPessimistic(v, c Version) bool { + return v.GreaterThanOrEqual(c) && v.LessThan(c.PessimisticBump()) +} + +func constraintTilde(v, c Version) bool { + // ~*, ~>* --> >= 0.0.0 (any) + // ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0 + // ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0 + // ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0 + // ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0 + // ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0 + return v.GreaterThanOrEqual(c) && v.LessThan(c.TildeBump()) +} + +func constraintCaret(v, c Version) bool { + // ^* --> (any) + // ^1.2.3 --> >=1.2.3 <2.0.0 + // ^1.2 --> >=1.2.0 <2.0.0 + // ^1 --> >=1.0.0 <2.0.0 + // ^0.2.3 --> >=0.2.3 <0.3.0 + // ^0.2 --> >=0.2.0 <0.3.0 + // ^0.0.3 --> >=0.0.3 <0.0.4 + // ^0.0 --> >=0.0.0 <0.1.0 + // ^0 --> >=0.0.0 <1.0.0 + return v.GreaterThanOrEqual(c) && v.LessThan(c.CaretBump()) +} diff --git a/vendor/github.com/aquasecurity/go-version/pkg/version/version.go b/vendor/github.com/aquasecurity/go-version/pkg/version/version.go new file mode 100644 index 00000000..62c7836f --- /dev/null +++ b/vendor/github.com/aquasecurity/go-version/pkg/version/version.go @@ -0,0 +1,199 @@ +package version + +import ( + "bytes" + "fmt" + "regexp" + "strings" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/go-version/pkg/part" + "github.com/aquasecurity/go-version/pkg/prerelease" +) + +// The compiled regular expression used to test the validity of a version. +var ( + versionRegex *regexp.Regexp +) + +const ( + // The raw regular expression string used for testing the validity of a version. + regex = `v?([0-9]+(\.[0-9]+)*)` + + `(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-?([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` + + `(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` + + `?` +) + +// Version represents a single version. +type Version struct { + segments []part.Uint64 + preRelease part.Parts + buildMetadata string + original string +} + +func init() { + versionRegex = regexp.MustCompile("^" + regex + "$") +} + +// Parse parses the given version and returns a new Version. +func Parse(v string) (Version, error) { + matches := versionRegex.FindStringSubmatch(v) + if matches == nil { + return Version{}, xerrors.Errorf("malformed version: %s", v) + } + + var segments []part.Uint64 + for _, str := range strings.Split(matches[1], ".") { + val, err := part.NewUint64(str) + if err != nil { + return Version{}, xerrors.Errorf("error parsing version: %w", err) + } + + segments = append(segments, val) + } + + pre := matches[7] + if pre == "" { + pre = matches[4] + } + + return Version{ + segments: segments, + buildMetadata: matches[10], + preRelease: part.NewParts(pre), + original: v, + }, nil +} + +// Compare compares this version to another version. This +// returns -1, 0, or 1 if this version is smaller, equal, +// or larger than the other version, respectively. +func (v Version) Compare(other Version) int { + // A quick, efficient equality check + if v.String() == other.String() { + return 0 + } + + p1 := part.Uint64SliceToParts(v.segments).Normalize() + p2 := part.Uint64SliceToParts(other.segments).Normalize() + + p1 = p1.Padding(len(p2), part.Zero) + p2 = p2.Padding(len(p1), part.Zero) + + if result := p1.Compare(p2); result != 0 { + return result + } + + return prerelease.Compare(v.preRelease, other.preRelease) +} + +// Equal tests if two versions are equal. +func (v Version) Equal(o Version) bool { + return v.Compare(o) == 0 +} + +// GreaterThan tests if this version is greater than another version. +func (v Version) GreaterThan(o Version) bool { + return v.Compare(o) > 0 +} + +// GreaterThanOrEqual tests if this version is greater than or equal to another version. +func (v Version) GreaterThanOrEqual(o Version) bool { + return v.Compare(o) >= 0 +} + +// LessThan tests if this version is less than another version. +func (v Version) LessThan(o Version) bool { + return v.Compare(o) < 0 +} + +// LessThanOrEqual tests if this version is less than or equal to another version. +func (v Version) LessThanOrEqual(o Version) bool { + return v.Compare(o) <= 0 +} + +// String returns the full version string included pre-release +// and metadata information. +func (v Version) String() string { + var buf bytes.Buffer + fmt.Fprintf(&buf, "%d", v.segments[0]) + for _, s := range v.segments[1:len(v.segments)] { + fmt.Fprintf(&buf, ".%d", s) + } + + if !v.preRelease.IsNull() { + fmt.Fprintf(&buf, "-%s", v.preRelease) + } + if v.buildMetadata != "" { + fmt.Fprintf(&buf, "+%s", v.buildMetadata) + } + + return buf.String() +} + +// Original returns the original parsed version as-is, including any +// potential whitespace, `v` prefix, etc. +func (v Version) Original() string { + return v.original +} + +// PessimisticBump returns the maximum version of "~>" +// It works like Gem::Version.bump() +// https://docs.ruby-lang.org/en/2.6.0/Gem/Version.html#method-i-bump +func (v Version) PessimisticBump() Version { + size := len(v.segments) + if size == 1 { + v.segments[0] += 1 + return v + } + + v.segments[size-1] = 0 + v.segments[size-2] += 1 + + v.preRelease = part.Parts{} + v.buildMetadata = "" + + return v +} + +// TildeBump returns the maximum version of "~" +// https://docs.npmjs.com/cli/v6/using-npm/semver#tilde-ranges-123-12-1 +func (v Version) TildeBump() Version { + if len(v.segments) == 2 { + v.segments[1] += 1 + return v + } + + return v.PessimisticBump() +} + +// CaretBump returns the maximum version of "^" +// https://docs.npmjs.com/cli/v6/using-npm/semver#caret-ranges-123-025-004 +func (v Version) CaretBump() Version { + found := -1 + for i, s := range v.segments { + if s != 0 { + v.segments[i] += 1 + found = i + break + } + } + + if found >= 0 { + // zero padding + // ^1.2.3 => 2.0.0 + for i := found + 1; i < len(v.segments); i++ { + v.segments[i] = 0 + } + } else { + // ^0.0 => 0.1 + v.segments[len(v.segments)-1] += 1 + } + + v.preRelease = part.Parts{} + v.buildMetadata = "" + + return v +} diff --git a/vendor/github.com/aquasecurity/go-version/pkg/version/version_collection.go b/vendor/github.com/aquasecurity/go-version/pkg/version/version_collection.go new file mode 100644 index 00000000..1384e5be --- /dev/null +++ b/vendor/github.com/aquasecurity/go-version/pkg/version/version_collection.go @@ -0,0 +1,17 @@ +package version + +// Collection is a type that implements the sort.Interface interface +// so that versions can be sorted. +type Collection []Version + +func (v Collection) Len() int { + return len(v) +} + +func (v Collection) Less(i, j int) bool { + return v[i].LessThan(v[j]) +} + +func (v Collection) Swap(i, j int) { + v[i], v[j] = v[j], v[i] +} diff --git a/vendor/golang.org/x/xerrors/LICENSE b/vendor/golang.org/x/xerrors/LICENSE new file mode 100644 index 00000000..e4a47e17 --- /dev/null +++ b/vendor/golang.org/x/xerrors/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2019 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/xerrors/PATENTS b/vendor/golang.org/x/xerrors/PATENTS new file mode 100644 index 00000000..73309904 --- /dev/null +++ b/vendor/golang.org/x/xerrors/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/xerrors/README b/vendor/golang.org/x/xerrors/README new file mode 100644 index 00000000..aac7867a --- /dev/null +++ b/vendor/golang.org/x/xerrors/README @@ -0,0 +1,2 @@ +This repository holds the transition packages for the new Go 1.13 error values. +See golang.org/design/29934-error-values. diff --git a/vendor/golang.org/x/xerrors/adaptor.go b/vendor/golang.org/x/xerrors/adaptor.go new file mode 100644 index 00000000..4317f248 --- /dev/null +++ b/vendor/golang.org/x/xerrors/adaptor.go @@ -0,0 +1,193 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import ( + "bytes" + "fmt" + "io" + "reflect" + "strconv" +) + +// FormatError calls the FormatError method of f with an errors.Printer +// configured according to s and verb, and writes the result to s. +func FormatError(f Formatter, s fmt.State, verb rune) { + // Assuming this function is only called from the Format method, and given + // that FormatError takes precedence over Format, it cannot be called from + // any package that supports errors.Formatter. It is therefore safe to + // disregard that State may be a specific printer implementation and use one + // of our choice instead. + + // limitations: does not support printing error as Go struct. + + var ( + sep = " " // separator before next error + p = &state{State: s} + direct = true + ) + + var err error = f + + switch verb { + // Note that this switch must match the preference order + // for ordinary string printing (%#v before %+v, and so on). + + case 'v': + if s.Flag('#') { + if stringer, ok := err.(fmt.GoStringer); ok { + io.WriteString(&p.buf, stringer.GoString()) + goto exit + } + // proceed as if it were %v + } else if s.Flag('+') { + p.printDetail = true + sep = "\n - " + } + case 's': + case 'q', 'x', 'X': + // Use an intermediate buffer in the rare cases that precision, + // truncation, or one of the alternative verbs (q, x, and X) are + // specified. + direct = false + + default: + p.buf.WriteString("%!") + p.buf.WriteRune(verb) + p.buf.WriteByte('(') + switch { + case err != nil: + p.buf.WriteString(reflect.TypeOf(f).String()) + default: + p.buf.WriteString("") + } + p.buf.WriteByte(')') + io.Copy(s, &p.buf) + return + } + +loop: + for { + switch v := err.(type) { + case Formatter: + err = v.FormatError((*printer)(p)) + case fmt.Formatter: + v.Format(p, 'v') + break loop + default: + io.WriteString(&p.buf, v.Error()) + break loop + } + if err == nil { + break + } + if p.needColon || !p.printDetail { + p.buf.WriteByte(':') + p.needColon = false + } + p.buf.WriteString(sep) + p.inDetail = false + p.needNewline = false + } + +exit: + width, okW := s.Width() + prec, okP := s.Precision() + + if !direct || (okW && width > 0) || okP { + // Construct format string from State s. + format := []byte{'%'} + if s.Flag('-') { + format = append(format, '-') + } + if s.Flag('+') { + format = append(format, '+') + } + if s.Flag(' ') { + format = append(format, ' ') + } + if okW { + format = strconv.AppendInt(format, int64(width), 10) + } + if okP { + format = append(format, '.') + format = strconv.AppendInt(format, int64(prec), 10) + } + format = append(format, string(verb)...) + fmt.Fprintf(s, string(format), p.buf.String()) + } else { + io.Copy(s, &p.buf) + } +} + +var detailSep = []byte("\n ") + +// state tracks error printing state. It implements fmt.State. +type state struct { + fmt.State + buf bytes.Buffer + + printDetail bool + inDetail bool + needColon bool + needNewline bool +} + +func (s *state) Write(b []byte) (n int, err error) { + if s.printDetail { + if len(b) == 0 { + return 0, nil + } + if s.inDetail && s.needColon { + s.needNewline = true + if b[0] == '\n' { + b = b[1:] + } + } + k := 0 + for i, c := range b { + if s.needNewline { + if s.inDetail && s.needColon { + s.buf.WriteByte(':') + s.needColon = false + } + s.buf.Write(detailSep) + s.needNewline = false + } + if c == '\n' { + s.buf.Write(b[k:i]) + k = i + 1 + s.needNewline = true + } + } + s.buf.Write(b[k:]) + if !s.inDetail { + s.needColon = true + } + } else if !s.inDetail { + s.buf.Write(b) + } + return len(b), nil +} + +// printer wraps a state to implement an xerrors.Printer. +type printer state + +func (s *printer) Print(args ...interface{}) { + if !s.inDetail || s.printDetail { + fmt.Fprint((*state)(s), args...) + } +} + +func (s *printer) Printf(format string, args ...interface{}) { + if !s.inDetail || s.printDetail { + fmt.Fprintf((*state)(s), format, args...) + } +} + +func (s *printer) Detail() bool { + s.inDetail = true + return s.printDetail +} diff --git a/vendor/golang.org/x/xerrors/codereview.cfg b/vendor/golang.org/x/xerrors/codereview.cfg new file mode 100644 index 00000000..3f8b14b6 --- /dev/null +++ b/vendor/golang.org/x/xerrors/codereview.cfg @@ -0,0 +1 @@ +issuerepo: golang/go diff --git a/vendor/golang.org/x/xerrors/doc.go b/vendor/golang.org/x/xerrors/doc.go new file mode 100644 index 00000000..eef99d9d --- /dev/null +++ b/vendor/golang.org/x/xerrors/doc.go @@ -0,0 +1,22 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package xerrors implements functions to manipulate errors. +// +// This package is based on the Go 2 proposal for error values: +// https://golang.org/design/29934-error-values +// +// These functions were incorporated into the standard library's errors package +// in Go 1.13: +// - Is +// - As +// - Unwrap +// +// Also, Errorf's %w verb was incorporated into fmt.Errorf. +// +// Use this package to get equivalent behavior in all supported Go versions. +// +// No other features of this package were included in Go 1.13, and at present +// there are no plans to include any of them. +package xerrors // import "golang.org/x/xerrors" diff --git a/vendor/golang.org/x/xerrors/errors.go b/vendor/golang.org/x/xerrors/errors.go new file mode 100644 index 00000000..e88d3772 --- /dev/null +++ b/vendor/golang.org/x/xerrors/errors.go @@ -0,0 +1,33 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import "fmt" + +// errorString is a trivial implementation of error. +type errorString struct { + s string + frame Frame +} + +// New returns an error that formats as the given text. +// +// The returned error contains a Frame set to the caller's location and +// implements Formatter to show this information when printed with details. +func New(text string) error { + return &errorString{text, Caller(1)} +} + +func (e *errorString) Error() string { + return e.s +} + +func (e *errorString) Format(s fmt.State, v rune) { FormatError(e, s, v) } + +func (e *errorString) FormatError(p Printer) (next error) { + p.Print(e.s) + e.frame.Format(p) + return nil +} diff --git a/vendor/golang.org/x/xerrors/fmt.go b/vendor/golang.org/x/xerrors/fmt.go new file mode 100644 index 00000000..829862dd --- /dev/null +++ b/vendor/golang.org/x/xerrors/fmt.go @@ -0,0 +1,187 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import ( + "fmt" + "strings" + "unicode" + "unicode/utf8" + + "golang.org/x/xerrors/internal" +) + +const percentBangString = "%!" + +// Errorf formats according to a format specifier and returns the string as a +// value that satisfies error. +// +// The returned error includes the file and line number of the caller when +// formatted with additional detail enabled. If the last argument is an error +// the returned error's Format method will return it if the format string ends +// with ": %s", ": %v", or ": %w". If the last argument is an error and the +// format string ends with ": %w", the returned error implements an Unwrap +// method returning it. +// +// If the format specifier includes a %w verb with an error operand in a +// position other than at the end, the returned error will still implement an +// Unwrap method returning the operand, but the error's Format method will not +// return the wrapped error. +// +// It is invalid to include more than one %w verb or to supply it with an +// operand that does not implement the error interface. The %w verb is otherwise +// a synonym for %v. +func Errorf(format string, a ...interface{}) error { + format = formatPlusW(format) + // Support a ": %[wsv]" suffix, which works well with xerrors.Formatter. + wrap := strings.HasSuffix(format, ": %w") + idx, format2, ok := parsePercentW(format) + percentWElsewhere := !wrap && idx >= 0 + if !percentWElsewhere && (wrap || strings.HasSuffix(format, ": %s") || strings.HasSuffix(format, ": %v")) { + err := errorAt(a, len(a)-1) + if err == nil { + return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)} + } + // TODO: this is not entirely correct. The error value could be + // printed elsewhere in format if it mixes numbered with unnumbered + // substitutions. With relatively small changes to doPrintf we can + // have it optionally ignore extra arguments and pass the argument + // list in its entirety. + msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...) + frame := Frame{} + if internal.EnableTrace { + frame = Caller(1) + } + if wrap { + return &wrapError{msg, err, frame} + } + return &noWrapError{msg, err, frame} + } + // Support %w anywhere. + // TODO: don't repeat the wrapped error's message when %w occurs in the middle. + msg := fmt.Sprintf(format2, a...) + if idx < 0 { + return &noWrapError{msg, nil, Caller(1)} + } + err := errorAt(a, idx) + if !ok || err == nil { + // Too many %ws or argument of %w is not an error. Approximate the Go + // 1.13 fmt.Errorf message. + return &noWrapError{fmt.Sprintf("%sw(%s)", percentBangString, msg), nil, Caller(1)} + } + frame := Frame{} + if internal.EnableTrace { + frame = Caller(1) + } + return &wrapError{msg, err, frame} +} + +func errorAt(args []interface{}, i int) error { + if i < 0 || i >= len(args) { + return nil + } + err, ok := args[i].(error) + if !ok { + return nil + } + return err +} + +// formatPlusW is used to avoid the vet check that will barf at %w. +func formatPlusW(s string) string { + return s +} + +// Return the index of the only %w in format, or -1 if none. +// Also return a rewritten format string with %w replaced by %v, and +// false if there is more than one %w. +// TODO: handle "%[N]w". +func parsePercentW(format string) (idx int, newFormat string, ok bool) { + // Loosely copied from golang.org/x/tools/go/analysis/passes/printf/printf.go. + idx = -1 + ok = true + n := 0 + sz := 0 + var isW bool + for i := 0; i < len(format); i += sz { + if format[i] != '%' { + sz = 1 + continue + } + // "%%" is not a format directive. + if i+1 < len(format) && format[i+1] == '%' { + sz = 2 + continue + } + sz, isW = parsePrintfVerb(format[i:]) + if isW { + if idx >= 0 { + ok = false + } else { + idx = n + } + // "Replace" the last character, the 'w', with a 'v'. + p := i + sz - 1 + format = format[:p] + "v" + format[p+1:] + } + n++ + } + return idx, format, ok +} + +// Parse the printf verb starting with a % at s[0]. +// Return how many bytes it occupies and whether the verb is 'w'. +func parsePrintfVerb(s string) (int, bool) { + // Assume only that the directive is a sequence of non-letters followed by a single letter. + sz := 0 + var r rune + for i := 1; i < len(s); i += sz { + r, sz = utf8.DecodeRuneInString(s[i:]) + if unicode.IsLetter(r) { + return i + sz, r == 'w' + } + } + return len(s), false +} + +type noWrapError struct { + msg string + err error + frame Frame +} + +func (e *noWrapError) Error() string { + return fmt.Sprint(e) +} + +func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } + +func (e *noWrapError) FormatError(p Printer) (next error) { + p.Print(e.msg) + e.frame.Format(p) + return e.err +} + +type wrapError struct { + msg string + err error + frame Frame +} + +func (e *wrapError) Error() string { + return fmt.Sprint(e) +} + +func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } + +func (e *wrapError) FormatError(p Printer) (next error) { + p.Print(e.msg) + e.frame.Format(p) + return e.err +} + +func (e *wrapError) Unwrap() error { + return e.err +} diff --git a/vendor/golang.org/x/xerrors/format.go b/vendor/golang.org/x/xerrors/format.go new file mode 100644 index 00000000..1bc9c26b --- /dev/null +++ b/vendor/golang.org/x/xerrors/format.go @@ -0,0 +1,34 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +// A Formatter formats error messages. +type Formatter interface { + error + + // FormatError prints the receiver's first error and returns the next error in + // the error chain, if any. + FormatError(p Printer) (next error) +} + +// A Printer formats error messages. +// +// The most common implementation of Printer is the one provided by package fmt +// during Printf (as of Go 1.13). Localization packages such as golang.org/x/text/message +// typically provide their own implementations. +type Printer interface { + // Print appends args to the message output. + Print(args ...interface{}) + + // Printf writes a formatted string. + Printf(format string, args ...interface{}) + + // Detail reports whether error detail is requested. + // After the first call to Detail, all text written to the Printer + // is formatted as additional detail, or ignored when + // detail has not been requested. + // If Detail returns false, the caller can avoid printing the detail at all. + Detail() bool +} diff --git a/vendor/golang.org/x/xerrors/frame.go b/vendor/golang.org/x/xerrors/frame.go new file mode 100644 index 00000000..0de628ec --- /dev/null +++ b/vendor/golang.org/x/xerrors/frame.go @@ -0,0 +1,56 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import ( + "runtime" +) + +// A Frame contains part of a call stack. +type Frame struct { + // Make room for three PCs: the one we were asked for, what it called, + // and possibly a PC for skipPleaseUseCallersFrames. See: + // https://go.googlesource.com/go/+/032678e0fb/src/runtime/extern.go#169 + frames [3]uintptr +} + +// Caller returns a Frame that describes a frame on the caller's stack. +// The argument skip is the number of frames to skip over. +// Caller(0) returns the frame for the caller of Caller. +func Caller(skip int) Frame { + var s Frame + runtime.Callers(skip+1, s.frames[:]) + return s +} + +// location reports the file, line, and function of a frame. +// +// The returned function may be "" even if file and line are not. +func (f Frame) location() (function, file string, line int) { + frames := runtime.CallersFrames(f.frames[:]) + if _, ok := frames.Next(); !ok { + return "", "", 0 + } + fr, ok := frames.Next() + if !ok { + return "", "", 0 + } + return fr.Function, fr.File, fr.Line +} + +// Format prints the stack as error detail. +// It should be called from an error's Format implementation +// after printing any other error detail. +func (f Frame) Format(p Printer) { + if p.Detail() { + function, file, line := f.location() + if function != "" { + p.Printf("%s\n ", function) + } + if file != "" { + p.Printf("%s:%d\n", file, line) + } + } +} diff --git a/vendor/golang.org/x/xerrors/go.mod b/vendor/golang.org/x/xerrors/go.mod new file mode 100644 index 00000000..870d4f61 --- /dev/null +++ b/vendor/golang.org/x/xerrors/go.mod @@ -0,0 +1,3 @@ +module golang.org/x/xerrors + +go 1.11 diff --git a/vendor/golang.org/x/xerrors/internal/internal.go b/vendor/golang.org/x/xerrors/internal/internal.go new file mode 100644 index 00000000..89f4eca5 --- /dev/null +++ b/vendor/golang.org/x/xerrors/internal/internal.go @@ -0,0 +1,8 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package internal + +// EnableTrace indicates whether stack information should be recorded in errors. +var EnableTrace = true diff --git a/vendor/golang.org/x/xerrors/wrap.go b/vendor/golang.org/x/xerrors/wrap.go new file mode 100644 index 00000000..9a3b5103 --- /dev/null +++ b/vendor/golang.org/x/xerrors/wrap.go @@ -0,0 +1,106 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import ( + "reflect" +) + +// A Wrapper provides context around another error. +type Wrapper interface { + // Unwrap returns the next error in the error chain. + // If there is no next error, Unwrap returns nil. + Unwrap() error +} + +// Opaque returns an error with the same error formatting as err +// but that does not match err and cannot be unwrapped. +func Opaque(err error) error { + return noWrapper{err} +} + +type noWrapper struct { + error +} + +func (e noWrapper) FormatError(p Printer) (next error) { + if f, ok := e.error.(Formatter); ok { + return f.FormatError(p) + } + p.Print(e.error) + return nil +} + +// Unwrap returns the result of calling the Unwrap method on err, if err implements +// Unwrap. Otherwise, Unwrap returns nil. +func Unwrap(err error) error { + u, ok := err.(Wrapper) + if !ok { + return nil + } + return u.Unwrap() +} + +// Is reports whether any error in err's chain matches target. +// +// An error is considered to match a target if it is equal to that target or if +// it implements a method Is(error) bool such that Is(target) returns true. +func Is(err, target error) bool { + if target == nil { + return err == target + } + + isComparable := reflect.TypeOf(target).Comparable() + for { + if isComparable && err == target { + return true + } + if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) { + return true + } + // TODO: consider supporing target.Is(err). This would allow + // user-definable predicates, but also may allow for coping with sloppy + // APIs, thereby making it easier to get away with them. + if err = Unwrap(err); err == nil { + return false + } + } +} + +// As finds the first error in err's chain that matches the type to which target +// points, and if so, sets the target to its value and returns true. An error +// matches a type if it is assignable to the target type, or if it has a method +// As(interface{}) bool such that As(target) returns true. As will panic if target +// is not a non-nil pointer to a type which implements error or is of interface type. +// +// The As method should set the target to its value and return true if err +// matches the type to which target points. +func As(err error, target interface{}) bool { + if target == nil { + panic("errors: target cannot be nil") + } + val := reflect.ValueOf(target) + typ := val.Type() + if typ.Kind() != reflect.Ptr || val.IsNil() { + panic("errors: target must be a non-nil pointer") + } + if e := typ.Elem(); e.Kind() != reflect.Interface && !e.Implements(errorType) { + panic("errors: *target must be interface or implement error") + } + targetType := typ.Elem() + for err != nil { + if reflect.TypeOf(err).AssignableTo(targetType) { + val.Elem().Set(reflect.ValueOf(err)) + return true + } + if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) { + return true + } + err = Unwrap(err) + } + return false +} + +var errorType = reflect.TypeOf((*error)(nil)).Elem() diff --git a/vendor/modules.txt b/vendor/modules.txt index 6476820a..f047e82b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,3 +1,8 @@ +# github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 +## explicit +github.com/aquasecurity/go-version/pkg/part +github.com/aquasecurity/go-version/pkg/prerelease +github.com/aquasecurity/go-version/pkg/version # github.com/coreos/go-semver v0.3.0 github.com/coreos/go-semver/semver # github.com/coreos/go-systemd/v22 v22.3.2 @@ -171,6 +176,9 @@ golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm # golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac golang.org/x/time/rate +# golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 +golang.org/x/xerrors +golang.org/x/xerrors/internal # google.golang.org/appengine v1.6.7 google.golang.org/appengine/internal google.golang.org/appengine/internal/base @@ -436,6 +444,7 @@ k8s.io/client-go/util/workqueue k8s.io/component-base/config k8s.io/component-base/config/v1alpha1 # k8s.io/klog/v2 v2.10.0 +## explicit k8s.io/klog/v2 # k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e k8s.io/kube-openapi/pkg/util/proto From 61cfbb3a312891267cfd6c78443ef048e4271b4f Mon Sep 17 00:00:00 2001 From: Francesco Romani Date: Sun, 13 Feb 2022 11:09:11 +0100 Subject: [PATCH 3/7] validator: check MemoryManager policy We expect the MemoryManager to be enabled with the Static policy, so we add an explicit check to verify this behaviour. Signed-off-by: Francesco Romani --- pkg/validator/kubeletconfig.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pkg/validator/kubeletconfig.go b/pkg/validator/kubeletconfig.go index a0b6bef6..111cde69 100644 --- a/pkg/validator/kubeletconfig.go +++ b/pkg/validator/kubeletconfig.go @@ -31,6 +31,7 @@ const ( ComponentConfiguration = "configuration" ComponentFeatureGates = "feature gates" ComponentCPUManager = "CPU manager" + ComponentMemoryManager = "memory manager" ComponentTopologyManager = "topology manager" ) @@ -43,6 +44,7 @@ const ( const ( ExpectedPodResourcesFeatureGate = "KubeletPodResourcesGetAllocatable" ExpectedCPUManagerPolicy = "static" + ExpectedMemoryManagerPolicy = "Static" // we need uppercase "S" ExpectedTopologyManagerPolicy = "single-numa-node" ) @@ -157,6 +159,17 @@ func ValidateClusterNodeKubeletConfig(nodeName string, nodeVersion *version.Info }) } + if kubeletConf.MemoryManagerPolicy != ExpectedMemoryManagerPolicy { + vrs = append(vrs, ValidationResult{ + Node: nodeName, + Area: AreaKubelet, + Component: ComponentMemoryManager, + Setting: "policy", + Expected: ExpectedMemoryManagerPolicy, + Detected: kubeletConf.MemoryManagerPolicy, + }) + } + if kubeletConf.TopologyManagerPolicy != "single-numa-node" { vrs = append(vrs, ValidationResult{ Node: nodeName, From 5679ac471eddea3ae3fdf25ede08dae94941c943 Mon Sep 17 00:00:00 2001 From: Francesco Romani Date: Sun, 13 Feb 2022 11:09:27 +0100 Subject: [PATCH 4/7] validate: use constants for expected values It's a bit silly to use a constant for the expected value in the reporting but not in the actual check, so let's fix this using the named constant in both places. Signed-off-by: Francesco Romani --- pkg/validator/kubeletconfig.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/validator/kubeletconfig.go b/pkg/validator/kubeletconfig.go index 111cde69..b5edc3a4 100644 --- a/pkg/validator/kubeletconfig.go +++ b/pkg/validator/kubeletconfig.go @@ -136,7 +136,7 @@ func ValidateClusterNodeKubeletConfig(nodeName string, nodeVersion *version.Info } } - if kubeletConf.CPUManagerPolicy != "static" { + if kubeletConf.CPUManagerPolicy != ExpectedCPUManagerPolicy { vrs = append(vrs, ValidationResult{ Node: nodeName, Area: AreaKubelet, @@ -170,7 +170,7 @@ func ValidateClusterNodeKubeletConfig(nodeName string, nodeVersion *version.Info }) } - if kubeletConf.TopologyManagerPolicy != "single-numa-node" { + if kubeletConf.TopologyManagerPolicy != ExpectedTopologyManagerPolicy { vrs = append(vrs, ValidationResult{ Node: nodeName, Area: AreaKubelet, From 4557b45406709c436a646e205de94b09a5d6d2da Mon Sep 17 00:00:00 2001 From: Francesco Romani Date: Sun, 13 Feb 2022 11:13:56 +0100 Subject: [PATCH 5/7] validate: check for reserved resources Add validation check to verify _some_ resources are reserved. We don't want yet to validate the amount of resources nor their NUMA topology. so we just check _something_ has been reserved. Signed-off-by: Francesco Romani --- pkg/validator/kubeletconfig.go | 21 +++++ pkg/validator/kubeletconfig_test.go | 123 ++++++++++++++++++++++++++++ test/e2e/e2e_test.go | 10 +++ test/e2e/positive.go | 4 +- 4 files changed, 156 insertions(+), 2 deletions(-) diff --git a/pkg/validator/kubeletconfig.go b/pkg/validator/kubeletconfig.go index b5edc3a4..a7a64b88 100644 --- a/pkg/validator/kubeletconfig.go +++ b/pkg/validator/kubeletconfig.go @@ -158,6 +158,16 @@ func ValidateClusterNodeKubeletConfig(nodeName string, nodeVersion *version.Info Detected: fmt.Sprintf("%v", kubeletConf.CPUManagerReconcilePeriod.Duration), }) } + if kubeletConf.ReservedSystemCPUs == "" { + vrs = append(vrs, ValidationResult{ + Node: nodeName, + Area: AreaKubelet, + Component: ComponentConfiguration, + Setting: "CPU", + Expected: "reserved some CPU cores", + Detected: "no reserved CPU cores", + }) + } if kubeletConf.MemoryManagerPolicy != ExpectedMemoryManagerPolicy { vrs = append(vrs, ValidationResult{ @@ -170,6 +180,17 @@ func ValidateClusterNodeKubeletConfig(nodeName string, nodeVersion *version.Info }) } + if len(kubeletConf.ReservedMemory) == 0 { + vrs = append(vrs, ValidationResult{ + Node: nodeName, + Area: AreaKubelet, + Component: ComponentConfiguration, + Setting: "memory", + Expected: "reserved memory blocks", + Detected: "no reserved memory blocks", + }) + } + if kubeletConf.TopologyManagerPolicy != ExpectedTopologyManagerPolicy { vrs = append(vrs, ValidationResult{ Node: nodeName, diff --git a/pkg/validator/kubeletconfig_test.go b/pkg/validator/kubeletconfig_test.go index 37b842ce..753029bb 100644 --- a/pkg/validator/kubeletconfig_test.go +++ b/pkg/validator/kubeletconfig_test.go @@ -23,6 +23,7 @@ import ( "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/version" kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1" ) @@ -32,6 +33,7 @@ func TestKubeletValidations(t *testing.T) { type testCase struct { name string kubeletConf *kubeletconfigv1beta1.KubeletConfiguration + nodeVersion *version.Info expected []ValidationResult } @@ -68,6 +70,24 @@ func TestKubeletValidations(t *testing.T) { Component: ComponentCPUManager, Setting: "reconcile period", }, + { + Node: nodeName, + Area: AreaKubelet, + Component: ComponentConfiguration, + Setting: "CPU", + }, + { + Node: nodeName, + Area: AreaKubelet, + Component: ComponentMemoryManager, + Setting: "policy", + }, + { + Node: nodeName, + Area: AreaKubelet, + Component: ComponentConfiguration, + Setting: "memory", + }, { Node: nodeName, Area: AreaKubelet, @@ -86,6 +106,13 @@ func TestKubeletValidations(t *testing.T) { CPUManagerReconcilePeriod: metav1.Duration{ Duration: 5 * time.Second, }, + MemoryManagerPolicy: ExpectedMemoryManagerPolicy, + ReservedMemory: []kubeletconfigv1beta1.MemoryReservation{ + { + NumaNode: 1, + }, + }, + ReservedSystemCPUs: "0,1", TopologyManagerPolicy: ExpectedTopologyManagerPolicy, }, expected: []ValidationResult{}, @@ -97,6 +124,13 @@ func TestKubeletValidations(t *testing.T) { CPUManagerReconcilePeriod: metav1.Duration{ Duration: 5 * time.Second, }, + MemoryManagerPolicy: ExpectedMemoryManagerPolicy, + ReservedMemory: []kubeletconfigv1beta1.MemoryReservation{ + { + NumaNode: 1, + }, + }, + ReservedSystemCPUs: "0,1", TopologyManagerPolicy: ExpectedTopologyManagerPolicy, }, expected: []ValidationResult{ @@ -117,6 +151,13 @@ func TestKubeletValidations(t *testing.T) { CPUManagerReconcilePeriod: metav1.Duration{ Duration: 5 * time.Second, }, + MemoryManagerPolicy: ExpectedMemoryManagerPolicy, + ReservedMemory: []kubeletconfigv1beta1.MemoryReservation{ + { + NumaNode: 1, + }, + }, + ReservedSystemCPUs: "0,1", }, expected: []ValidationResult{ { @@ -137,6 +178,13 @@ func TestKubeletValidations(t *testing.T) { CPUManagerReconcilePeriod: metav1.Duration{ Duration: 5 * time.Second, }, + MemoryManagerPolicy: ExpectedMemoryManagerPolicy, + ReservedMemory: []kubeletconfigv1beta1.MemoryReservation{ + { + NumaNode: 1, + }, + }, + ReservedSystemCPUs: "0,1", TopologyManagerPolicy: "restricted", }, expected: []ValidationResult{ @@ -154,6 +202,12 @@ func TestKubeletValidations(t *testing.T) { FeatureGates: map[string]bool{ ExpectedPodResourcesFeatureGate: true, }, + MemoryManagerPolicy: ExpectedMemoryManagerPolicy, + ReservedMemory: []kubeletconfigv1beta1.MemoryReservation{ + { + NumaNode: 1, + }, + }, TopologyManagerPolicy: ExpectedTopologyManagerPolicy, }, expected: []ValidationResult{ @@ -170,6 +224,12 @@ func TestKubeletValidations(t *testing.T) { Component: ComponentCPUManager, Setting: "reconcile period", }, + { + Node: nodeName, + Area: AreaKubelet, + Component: ComponentConfiguration, + Setting: "CPU", + }, }, }, { @@ -182,6 +242,13 @@ func TestKubeletValidations(t *testing.T) { CPUManagerReconcilePeriod: metav1.Duration{ Duration: 30 * time.Second, }, + MemoryManagerPolicy: ExpectedMemoryManagerPolicy, + ReservedMemory: []kubeletconfigv1beta1.MemoryReservation{ + { + NumaNode: 1, + }, + }, + ReservedSystemCPUs: "0,1", TopologyManagerPolicy: ExpectedTopologyManagerPolicy, }, expected: []ValidationResult{ @@ -194,6 +261,62 @@ func TestKubeletValidations(t *testing.T) { }, }, }, + { + // CAUTION: I'm not actually sure k8s <= 1.20 had all these + // fields in the KubeletConfig, so we're bending the rules a bit here + name: "version too old, no feature gate", + kubeletConf: &kubeletconfigv1beta1.KubeletConfiguration{ + FeatureGates: map[string]bool{}, + CPUManagerPolicy: ExpectedCPUManagerPolicy, + CPUManagerReconcilePeriod: metav1.Duration{ + Duration: 5 * time.Second, + }, + MemoryManagerPolicy: ExpectedMemoryManagerPolicy, + ReservedMemory: []kubeletconfigv1beta1.MemoryReservation{ + { + NumaNode: 1, + }, + }, + ReservedSystemCPUs: "0,1", + TopologyManagerPolicy: ExpectedTopologyManagerPolicy, + }, + nodeVersion: &version.Info{ + Major: "1", + Minor: "20", + GitVersion: "v1.20.5", + }, + expected: []ValidationResult{ + { + Node: nodeName, + Area: AreaKubelet, + Component: ComponentFeatureGates, + }, + }, + }, + { + name: "version recent enough, no feature gate", + kubeletConf: &kubeletconfigv1beta1.KubeletConfiguration{ + FeatureGates: map[string]bool{}, + CPUManagerPolicy: ExpectedCPUManagerPolicy, + CPUManagerReconcilePeriod: metav1.Duration{ + Duration: 5 * time.Second, + }, + MemoryManagerPolicy: ExpectedMemoryManagerPolicy, + ReservedMemory: []kubeletconfigv1beta1.MemoryReservation{ + { + NumaNode: 1, + }, + }, + ReservedSystemCPUs: "0,1", + TopologyManagerPolicy: ExpectedTopologyManagerPolicy, + }, + nodeVersion: &version.Info{ + Major: "1", + Minor: "23", + GitVersion: "v1.23.1", + }, + expected: []ValidationResult{}, + }, } vd := Validator{ diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 4478bc22..17096341 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -20,6 +20,7 @@ import ( "fmt" "path/filepath" "runtime" + "strings" "testing" "github.com/onsi/ginkgo" @@ -55,6 +56,15 @@ type validationOutput struct { Errors []validator.ValidationResult `json:"errors,omitempty"` } +func (vo validationOutput) String() string { + var sb strings.Builder + fmt.Fprintf(&sb, "validation: success=%t\n", vo.Success) + for _, vErr := range vo.Errors { + fmt.Fprintf(&sb, "validation: error: %s\n", vErr.String()) + } + return sb.String() +} + type detectionOutput struct { AutoDetected platform.Platform `json:"auto_detected"` UserSupplied platform.Platform `json:"user_supplied"` diff --git a/test/e2e/positive.go b/test/e2e/positive.go index e099f613..be58b193 100644 --- a/test/e2e/positive.go +++ b/test/e2e/positive.go @@ -173,8 +173,8 @@ var _ = ginkgo.Describe("[PositiveFlow] Deployer validation", func() { if err := json.Unmarshal(out, &vo); err != nil { ginkgo.Fail(fmt.Sprintf("Error unmarshalling output %q: %v", out, err)) } - gomega.Expect(vo.Success).To(gomega.BeTrue()) - gomega.Expect(vo.Errors).To(gomega.BeEmpty()) + gomega.Expect(vo.Errors).To(gomega.BeEmpty(), "unexpected validation: %s", vo.String()) + gomega.Expect(vo.Success).To(gomega.BeTrue(), "unexpected validation: %s", vo.String()) }) }) }) From cfc442ac34290c69a60e3721f16e918b58561d72 Mon Sep 17 00:00:00 2001 From: Francesco Romani Date: Sun, 13 Feb 2022 14:10:56 +0100 Subject: [PATCH 6/7] e2e: hack: add memory manager config Complete the kubelet configuration with the memory manager parameters; makes the config more correct and should also let the deployer validation pass. Signed-off-by: Francesco Romani --- hack/kind-config-e2e-positive.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/hack/kind-config-e2e-positive.yaml b/hack/kind-config-e2e-positive.yaml index 82655b06..1b348bde 100644 --- a/hack/kind-config-e2e-positive.yaml +++ b/hack/kind-config-e2e-positive.yaml @@ -6,6 +6,17 @@ kubeadmConfigPatches: cpuManagerPolicy: "static" topologyManagerPolicy: "single-numa-node" reservedSystemCPUs: "0" + memoryManagerPolicy: "Static" + evictionHard: + memory.available: "100Mi" + kubeReserved: + memory: "256Mi" + reservedMemory: + - numaNode: 0 + limits: + memory: "612Mi" + systemReserved: + memory: "256Mi" featureGates: KubeletPodResourcesGetAllocatable: true nodes: From 2a97f277a56f961c53c9c47bbf788c92bf232640 Mon Sep 17 00:00:00 2001 From: Francesco Romani Date: Sun, 13 Feb 2022 15:41:36 +0100 Subject: [PATCH 7/7] validate: kubeletconfig: use k8s constants Use k8s constants, where available, for the expected values. Signed-off-by: Francesco Romani --- pkg/validator/kubeletconfig.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/validator/kubeletconfig.go b/pkg/validator/kubeletconfig.go index a7a64b88..ff74ad14 100644 --- a/pkg/validator/kubeletconfig.go +++ b/pkg/validator/kubeletconfig.go @@ -44,8 +44,8 @@ const ( const ( ExpectedPodResourcesFeatureGate = "KubeletPodResourcesGetAllocatable" ExpectedCPUManagerPolicy = "static" - ExpectedMemoryManagerPolicy = "Static" // we need uppercase "S" - ExpectedTopologyManagerPolicy = "single-numa-node" + ExpectedMemoryManagerPolicy = kubeletconfigv1beta1.StaticMemoryManagerPolicy + ExpectedTopologyManagerPolicy = kubeletconfigv1beta1.SingleNumaNodeTopologyManagerPolicy ) const (