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/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: 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 { 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..ff74ad14 100644 --- a/pkg/validator/kubeletconfig.go +++ b/pkg/validator/kubeletconfig.go @@ -21,20 +21,17 @@ 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" ComponentCPUManager = "CPU manager" + ComponentMemoryManager = "memory manager" ComponentTopologyManager = "topology manager" ) @@ -47,10 +44,15 @@ const ( const ( ExpectedPodResourcesFeatureGate = "KubeletPodResourcesGetAllocatable" ExpectedCPUManagerPolicy = "static" - ExpectedTopologyManagerPolicy = "single-numa-node" + ExpectedMemoryManagerPolicy = kubeletconfigv1beta1.StaticMemoryManagerPolicy + ExpectedTopologyManagerPolicy = kubeletconfigv1beta1.SingleNumaNodeTopologyManagerPolicy ) -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 +70,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 +80,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,29 +112,31 @@ 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", + }) + } } } - if kubeletConf.CPUManagerPolicy != "static" { + if kubeletConf.CPUManagerPolicy != ExpectedCPUManagerPolicy { vrs = append(vrs, ValidationResult{ Node: nodeName, Area: AreaKubelet, @@ -146,8 +158,40 @@ func (vd Validator) ValidateNodeKubeletConfig(nodeName string, kubeletConf *kube 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.TopologyManagerPolicy != "single-numa-node" { + if kubeletConf.MemoryManagerPolicy != ExpectedMemoryManagerPolicy { + vrs = append(vrs, ValidationResult{ + Node: nodeName, + Area: AreaKubelet, + Component: ComponentMemoryManager, + Setting: "policy", + Expected: ExpectedMemoryManagerPolicy, + Detected: kubeletConf.MemoryManagerPolicy, + }) + } + + 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, Area: AreaKubelet, @@ -157,10 +201,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..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{ @@ -201,13 +324,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/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()) }) }) }) 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