diff --git a/go.mod b/go.mod index 12a17dade6d5..65f42f28e19b 100644 --- a/go.mod +++ b/go.mod @@ -67,10 +67,10 @@ require ( gopkg.in/cheggaaa/pb.v1 v1.0.28 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.23.5 - k8s.io/apiextensions-apiserver v0.23.1 + k8s.io/apiextensions-apiserver v0.23.5 k8s.io/apimachinery v0.23.5 k8s.io/client-go v12.0.0+incompatible - k8s.io/kube-aggregator v0.23.1 + k8s.io/kube-aggregator v0.23.5 k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf k8s.io/kubectl v0.0.0-00010101000000-000000000000 k8s.io/utils v0.0.0-20211116205334-6203023598ed @@ -168,32 +168,32 @@ replace ( github.com/openshift/client-go => github.com/openshift/client-go v0.0.0-20210112165513-ebc401615f47 github.com/operator-framework/operator-lifecycle-manager => github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190128024246-5eb7ae5bdb7a - k8s.io/api => k8s.io/api v0.23.1 - k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.1 - k8s.io/apimachinery => k8s.io/apimachinery v0.23.1 - k8s.io/apiserver => k8s.io/apiserver v0.23.1 - k8s.io/cli-runtime => k8s.io/cli-runtime v0.23.1 - k8s.io/client-go => k8s.io/client-go v0.23.1 - k8s.io/cloud-provider => k8s.io/cloud-provider v0.23.1 - k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.23.1 - k8s.io/code-generator => k8s.io/code-generator v0.23.1 - k8s.io/component-base => k8s.io/component-base v0.23.1 - k8s.io/cri-api => k8s.io/cri-api v0.23.1 - k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.23.1 + k8s.io/api => k8s.io/api v0.23.5 + k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.5 + k8s.io/apimachinery => k8s.io/apimachinery v0.23.5 + k8s.io/apiserver => k8s.io/apiserver v0.23.5 + k8s.io/cli-runtime => k8s.io/cli-runtime v0.23.5 + k8s.io/client-go => k8s.io/client-go v0.23.5 + k8s.io/cloud-provider => k8s.io/cloud-provider v0.23.5 + k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.23.5 + k8s.io/code-generator => k8s.io/code-generator v0.23.5 + k8s.io/component-base => k8s.io/component-base v0.23.5 + k8s.io/cri-api => k8s.io/cri-api v0.23.5 + k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.23.5 k8s.io/klog => k8s.io/klog v0.4.0 - k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.23.1 - k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.23.1 + k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.23.5 + k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.23.5 k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20210113233702-8566a335510f - k8s.io/kube-proxy => k8s.io/kube-proxy v0.23.1 - k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.23.1 - k8s.io/kubectl => k8s.io/kubectl v0.23.1 - k8s.io/kubelet => k8s.io/kubelet v0.23.1 - k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.23.1 - k8s.io/metrics => k8s.io/metrics v0.23.1 - k8s.io/node-api => k8s.io/node-api v0.23.1 - k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.23.1 - k8s.io/sample-cli-plugin => k8s.io/sample-cli-plugin v0.23.1 - k8s.io/sample-controller => k8s.io/sample-controller v0.23.1 + k8s.io/kube-proxy => k8s.io/kube-proxy v0.23.5 + k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.23.5 + k8s.io/kubectl => k8s.io/kubectl v0.23.5 + k8s.io/kubelet => k8s.io/kubelet v0.23.5 + k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.23.5 + k8s.io/metrics => k8s.io/metrics v0.23.5 + k8s.io/node-api => k8s.io/node-api v0.23.5 + k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.23.5 + k8s.io/sample-cli-plugin => k8s.io/sample-cli-plugin v0.23.5 + k8s.io/sample-controller => k8s.io/sample-controller v0.23.5 kubevirt.io/api => ./staging/src/kubevirt.io/api kubevirt.io/client-go => ./staging/src/kubevirt.io/client-go diff --git a/go.sum b/go.sum index 22c990ff19e1..81a7be122361 100644 --- a/go.sum +++ b/go.sum @@ -2033,21 +2033,21 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= -k8s.io/api v0.23.1 h1:ncu/qfBfUoClqwkTGbeRqqOqBCRoUAflMuOaOD7J0c8= -k8s.io/api v0.23.1/go.mod h1:WfXnOnwSqNtG62Y1CdjoMxh7r7u9QXGCkA1u0na2jgo= -k8s.io/apiextensions-apiserver v0.23.1 h1:xxE0q1vLOVZiWORu1KwNRQFsGWtImueOrqSl13sS5EU= -k8s.io/apiextensions-apiserver v0.23.1/go.mod h1:0qz4fPaHHsVhRApbtk3MGXNn2Q9M/cVWWhfHdY2SxiM= -k8s.io/apimachinery v0.23.1 h1:sfBjlDFwj2onG0Ijx5C+SrAoeUscPrmghm7wHP+uXlo= -k8s.io/apimachinery v0.23.1/go.mod h1:SADt2Kl8/sttJ62RRsi9MIV4o8f5S3coArm0Iu3fBno= -k8s.io/apiserver v0.23.1/go.mod h1:Bqt0gWbeM2NefS8CjWswwd2VNAKN6lUKR85Ft4gippY= -k8s.io/cli-runtime v0.23.1/go.mod h1:r9r8H/qfXo9w+69vwUL7LokKlLRKW5D6A8vUKCx+YL0= -k8s.io/client-go v0.23.1 h1:Ma4Fhf/p07Nmj9yAB1H7UwbFHEBrSPg8lviR24U2GiQ= -k8s.io/client-go v0.23.1/go.mod h1:6QSI8fEuqD4zgFK0xbdwfB/PthBsIxCJMa3s17WlcO0= -k8s.io/cluster-bootstrap v0.23.1/go.mod h1:p2732QxwSa13WPemmyIeykk16qVw15W7lgNRB6x7NpY= -k8s.io/code-generator v0.23.1/go.mod h1:V7yn6VNTCWW8GqodYCESVo95fuiEg713S8B7WacWZDA= -k8s.io/component-base v0.23.1/go.mod h1:6llmap8QtJIXGDd4uIWJhAq0Op8AtQo6bDW2RrNMTeo= -k8s.io/component-helpers v0.23.1/go.mod h1:ZK24U+2oXnBPcas2KolLigVVN9g5zOzaHLkHiQMFGr0= -k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= +k8s.io/api v0.23.5 h1:zno3LUiMubxD/V1Zw3ijyKO3wxrhbUF1Ck+VjBvfaoA= +k8s.io/api v0.23.5/go.mod h1:Na4XuKng8PXJ2JsploYYrivXrINeTaycCGcYgF91Xm8= +k8s.io/apiextensions-apiserver v0.23.5 h1:5SKzdXyvIJKu+zbfPc3kCbWpbxi+O+zdmAJBm26UJqI= +k8s.io/apiextensions-apiserver v0.23.5/go.mod h1:ntcPWNXS8ZPKN+zTXuzYMeg731CP0heCTl6gYBxLcuQ= +k8s.io/apimachinery v0.23.5 h1:Va7dwhp8wgkUPWsEXk6XglXWU4IKYLKNlv8VkX7SDM0= +k8s.io/apimachinery v0.23.5/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= +k8s.io/apiserver v0.23.5/go.mod h1:7wvMtGJ42VRxzgVI7jkbKvMbuCbVbgsWFT7RyXiRNTw= +k8s.io/cli-runtime v0.23.5/go.mod h1:oY6QDF2qo9xndSq32tqcmRp2UyXssdGrLfjAVymgbx4= +k8s.io/client-go v0.23.5 h1:zUXHmEuqx0RY4+CsnkOn5l0GU+skkRXKGJrhmE2SLd8= +k8s.io/client-go v0.23.5/go.mod h1:flkeinTO1CirYgzMPRWxUCnV0G4Fbu2vLhYCObnt/r4= +k8s.io/cluster-bootstrap v0.23.5/go.mod h1:8/Gz6VTOMmEDDhn8U/nx0McnQR4YETAqiYXIlqR8hdQ= +k8s.io/code-generator v0.23.5/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= +k8s.io/component-base v0.23.5/go.mod h1:c5Nq44KZyt1aLl0IpHX82fhsn84Sb0jjzwjpcA42bY0= +k8s.io/component-helpers v0.23.5/go.mod h1:5riXJgjTIs+ZB8xnf5M2anZ8iQuq37a0B/0BgoPQuSM= +k8s.io/cri-api v0.23.5/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= k8s.io/gengo v0.0.0-20181113154421-fd15ee9cc2f7/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190907103519-ebc107f98eab/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= @@ -2064,21 +2064,20 @@ k8s.io/klog/v2 v2.10.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.40.1 h1:P4RRucWk/lFOlDdkAr3mc7iWFkgKrZY9qZMAgek06S4= k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-aggregator v0.23.1 h1:w05VLh3ji05gYQglMKKrwafgqjgIxZoBusxdSWS9d/4= -k8s.io/kube-aggregator v0.23.1/go.mod h1:1SPZXYD/je2gKxxLBkYyG3yFxSCUWI5QTyjqP2ZxRDI= +k8s.io/kube-aggregator v0.23.5 h1:UZ+qE3hGo6DcgKySf27Jg7d3X9/6JQkVLUiHZAoAfCY= +k8s.io/kube-aggregator v0.23.5/go.mod h1:3ynYx07Co6dzjpKPgipM+1/Mt2Jcm7dY++cRlKLr5s8= k8s.io/kube-openapi v0.0.0-20210113233702-8566a335510f h1:ZcWbnalfwIst131Zff7dGd1HQdt+NA9q7z9Fi0vbsHY= k8s.io/kube-openapi v0.0.0-20210113233702-8566a335510f/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= -k8s.io/kubectl v0.23.1 h1:gmscOiV4Y4XIRIn14gQBBADoyyVrDZPbxRCTDga4RSA= -k8s.io/kubectl v0.23.1/go.mod h1:Ui7dJKdUludF8yWAOSN7JZEkOuYixX5yF6E6NjoukKE= +k8s.io/kubectl v0.23.5 h1:DmDULqCaF4qstj0Im143XmncvqWtJxHzK8IrW2BzlU0= +k8s.io/kubectl v0.23.5/go.mod h1:lLgw7cVY8xbd7o637vOXPca/w6HC205KsPCRDYRCxwE= k8s.io/kubernetes v1.11.8-beta.0.0.20190124204751-3a10094374f2/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/kubernetes v1.14.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/metrics v0.23.1/go.mod h1:qXvsM1KANrc+ZZeFwj6Phvf0NLiC+d3RwcsLcdGc+xs= +k8s.io/metrics v0.23.5/go.mod h1:WNAtV2a5BYbmDS8+7jSqYYV6E3efuGTpIwJ8PTD1wgs= k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20191114200735-6ca3b61696b6/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20211116205334-6203023598ed h1:ck1fRPWPJWsMd8ZRFsWc6mh/zHp5fZ/shhbrgPUxDAE= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= kubevirt.io/containerized-data-importer v1.42.0 h1:go4Qua1cBAtL7OHRMN2SeoukMnItk8OKPl+K4VZ9YBQ= @@ -2101,7 +2100,7 @@ mvdan.cc/sh/v3 v3.1.1/go.mod h1:F+Vm4ZxPJxDKExMLhvjuI50oPnedVXpfjNSrusiTOno= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.25/go.mod h1:Mlj9PNLmG9bZ6BHFwFKDo5afkpWyUISkb9Me0GnK66I= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= sigs.k8s.io/controller-runtime v0.6.2 h1:jkAnfdTYBpFwlmBn3pS5HFO06SfxvnTZ1p5PeEF/zAA= sigs.k8s.io/controller-runtime v0.6.2/go.mod h1:vhcq/rlnENJ09SIRp3EveTaZ0yqH526hjf9iJdbUJ/E= sigs.k8s.io/controller-tools v0.2.4/go.mod h1:m/ztfQNocGYBgTTCmFdnK94uVvgxeZeE3LtJvd/jIzA= @@ -2115,7 +2114,6 @@ sigs.k8s.io/kustomize/cmd/config v0.10.2/go.mod h1:K2aW7nXJ0AaT+VA/eO0/dzFLxmpFc sigs.k8s.io/kustomize/kustomize/v4 v4.4.1/go.mod h1:qOKJMMz2mBP+vcS7vK+mNz4HBLjaQSWRY22EF6Tb7Io= sigs.k8s.io/kustomize/kyaml v0.13.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/pkg/virt-api/webhooks/validating-webhook/admitters/BUILD.bazel b/pkg/virt-api/webhooks/validating-webhook/admitters/BUILD.bazel index 86793c2142d2..9c970e0510e8 100644 --- a/pkg/virt-api/webhooks/validating-webhook/admitters/BUILD.bazel +++ b/pkg/virt-api/webhooks/validating-webhook/admitters/BUILD.bazel @@ -10,6 +10,7 @@ go_library( "pod-eviction-admitter.go", "preference-admitter.go", "status-admitter.go", + "validate-k8s-utils.go", "vmclone-admitter.go", "vmexport-admitter.go", "vmi-create-admitter.go", @@ -53,7 +54,9 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", diff --git a/pkg/virt-api/webhooks/validating-webhook/admitters/validate-k8s-utils.go b/pkg/virt-api/webhooks/validating-webhook/admitters/validate-k8s-utils.go new file mode 100644 index 000000000000..55248ad7f533 --- /dev/null +++ b/pkg/virt-api/webhooks/validating-webhook/admitters/validate-k8s-utils.go @@ -0,0 +1,259 @@ +/* +Copyright 2014 The Kubernetes Authors. +Copyright 2022 The KubeVirt Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* + +The code here is taken from https://github.com/kubernetes/kubernetes/blob/29fb8e8b5a41b2a7d760190284bae7f2829312d3/pkg/apis/core/validation/validation.go#L3288 +The "core" package is change to exported package "k8s.io/api/core/v1" in +order to avoid dependency on kubernetes/kubernetes + +https://github.com/kubernetes/kubernetes/blame/29fb8e8b5a41b2a7d760190284bae7f2829312d3/pkg/apis/core/validation/validation.go#L3288 +the code hardly changes all of the changes have been atleast a few years older +this makes it easier to copy and maintain instead of vendoring in kubernetes or +creating dry runs of the pod object during admission validation. +*/ + +package admitters + +import ( + core "k8s.io/api/core/v1" + apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// ValidateNamespaceName can be used to check whether the given namespace name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateNamespaceName = apimachineryvalidation.ValidateNamespaceName + +// ValidateNodeName can be used to check whether the given node name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateNodeName = apimachineryvalidation.NameIsDNSSubdomain + +var nodeFieldSelectorValidators = map[string]func(string, bool) []string{ + metav1.ObjectNameField: ValidateNodeName, +} + +// validateAffinity checks if given affinities are valid +func validateAffinity(affinity *core.Affinity, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if affinity != nil { + if affinity.NodeAffinity != nil { + allErrs = append(allErrs, validateNodeAffinity(affinity.NodeAffinity, fldPath.Child("nodeAffinity"))...) + } + if affinity.PodAffinity != nil { + allErrs = append(allErrs, validatePodAffinity(affinity.PodAffinity, fldPath.Child("podAffinity"))...) + } + if affinity.PodAntiAffinity != nil { + allErrs = append(allErrs, validatePodAntiAffinity(affinity.PodAntiAffinity, fldPath.Child("podAntiAffinity"))...) + } + } + + return allErrs +} + +// validateNodeAffinity tests that the specified nodeAffinity fields have valid data +func validateNodeAffinity(na *core.NodeAffinity, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + // TODO: Uncomment the next three lines once RequiredDuringSchedulingRequiredDuringExecution is implemented. + // if na.RequiredDuringSchedulingRequiredDuringExecution != nil { + // allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingRequiredDuringExecution, fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...) + // } + if na.RequiredDuringSchedulingIgnoredDuringExecution != nil { + allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingIgnoredDuringExecution, fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) + } + if len(na.PreferredDuringSchedulingIgnoredDuringExecution) > 0 { + allErrs = append(allErrs, ValidatePreferredSchedulingTerms(na.PreferredDuringSchedulingIgnoredDuringExecution, fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...) + } + return allErrs +} + +// ValidatePreferredSchedulingTerms tests that the specified SoftNodeAffinity fields has valid data +func ValidatePreferredSchedulingTerms(terms []core.PreferredSchedulingTerm, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + for i, term := range terms { + if term.Weight <= 0 || term.Weight > 100 { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("weight"), term.Weight, "must be in the range 1-100")) + } + + allErrs = append(allErrs, ValidateNodeSelectorTerm(term.Preference, fldPath.Index(i).Child("preference"))...) + } + return allErrs +} + +// validatePodAffinity tests that the specified podAffinity fields have valid data +func validatePodAffinity(podAffinity *core.PodAffinity, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + // TODO:Uncomment below code once RequiredDuringSchedulingRequiredDuringExecution is implemented. + // if podAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil { + // allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingRequiredDuringExecution, false, + // fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...) + //} + if podAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil { + allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingIgnoredDuringExecution, + fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) + } + if podAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil { + allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAffinity.PreferredDuringSchedulingIgnoredDuringExecution, + fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...) + } + return allErrs +} + +// validateWeightedPodAffinityTerms tests that the specified weightedPodAffinityTerms fields have valid data +func validateWeightedPodAffinityTerms(weightedPodAffinityTerms []core.WeightedPodAffinityTerm, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for j, weightedTerm := range weightedPodAffinityTerms { + if weightedTerm.Weight <= 0 || weightedTerm.Weight > 100 { + allErrs = append(allErrs, field.Invalid(fldPath.Index(j).Child("weight"), weightedTerm.Weight, "must be in the range 1-100")) + } + allErrs = append(allErrs, validatePodAffinityTerm(weightedTerm.PodAffinityTerm, fldPath.Index(j).Child("podAffinityTerm"))...) + } + return allErrs +} + +// validatePodAffinityTerm tests that the specified podAffinityTerm fields have valid data +func validatePodAffinityTerm(podAffinityTerm core.PodAffinityTerm, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(podAffinityTerm.LabelSelector, fldPath.Child("labelSelector"))...) + allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(podAffinityTerm.NamespaceSelector, fldPath.Child("namespaceSelector"))...) + + for _, name := range podAffinityTerm.Namespaces { + for _, msg := range ValidateNamespaceName(name, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), name, msg)) + } + } + if len(podAffinityTerm.TopologyKey) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("topologyKey"), "can not be empty")) + } + return append(allErrs, unversionedvalidation.ValidateLabelName(podAffinityTerm.TopologyKey, fldPath.Child("topologyKey"))...) +} + +// validatePodAntiAffinity tests that the specified podAntiAffinity fields have valid data +func validatePodAntiAffinity(podAntiAffinity *core.PodAntiAffinity, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + // TODO:Uncomment below code once RequiredDuringSchedulingRequiredDuringExecution is implemented. + // if podAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil { + // allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution, false, + // fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...) + //} + if podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil { + allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution, + fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) + } + if podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil { + allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution, + fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...) + } + return allErrs +} + +// ValidateNodeSelector tests that the specified nodeSelector fields has valid data +func ValidateNodeSelector(nodeSelector *core.NodeSelector, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + termFldPath := fldPath.Child("nodeSelectorTerms") + if len(nodeSelector.NodeSelectorTerms) == 0 { + return append(allErrs, field.Required(termFldPath, "must have at least one node selector term")) + } + + for i, term := range nodeSelector.NodeSelectorTerms { + allErrs = append(allErrs, ValidateNodeSelectorTerm(term, termFldPath.Index(i))...) + } + + return allErrs +} + +// validatePodAffinityTerms tests that the specified podAffinityTerms fields have valid data +func validatePodAffinityTerms(podAffinityTerms []core.PodAffinityTerm, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for i, podAffinityTerm := range podAffinityTerms { + allErrs = append(allErrs, validatePodAffinityTerm(podAffinityTerm, fldPath.Index(i))...) + } + return allErrs +} + +// ValidateNodeSelectorTerm tests that the specified node selector term has valid data +func ValidateNodeSelectorTerm(term core.NodeSelectorTerm, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + for j, req := range term.MatchExpressions { + allErrs = append(allErrs, ValidateNodeSelectorRequirement(req, fldPath.Child("matchExpressions").Index(j))...) + } + + for j, req := range term.MatchFields { + allErrs = append(allErrs, ValidateNodeFieldSelectorRequirement(req, fldPath.Child("matchFields").Index(j))...) + } + + return allErrs +} + +// ValidateNodeSelectorRequirement tests that the specified NodeSelectorRequirement fields has valid data +func ValidateNodeSelectorRequirement(rq core.NodeSelectorRequirement, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + switch rq.Operator { + case core.NodeSelectorOpIn, core.NodeSelectorOpNotIn: + if len(rq.Values) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'")) + } + case core.NodeSelectorOpExists, core.NodeSelectorOpDoesNotExist: + if len(rq.Values) > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'")) + } + + case core.NodeSelectorOpGt, core.NodeSelectorOpLt: + if len(rq.Values) != 1 { + allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified single value when `operator` is 'Lt' or 'Gt'")) + } + default: + allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), rq.Operator, "not a valid selector operator")) + } + + allErrs = append(allErrs, unversionedvalidation.ValidateLabelName(rq.Key, fldPath.Child("key"))...) + + return allErrs +} + +// ValidateNodeFieldSelectorRequirement tests that the specified NodeSelectorRequirement fields has valid data +func ValidateNodeFieldSelectorRequirement(req core.NodeSelectorRequirement, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + switch req.Operator { + case core.NodeSelectorOpIn, core.NodeSelectorOpNotIn: + if len(req.Values) != 1 { + allErrs = append(allErrs, field.Required(fldPath.Child("values"), + "must be only one value when `operator` is 'In' or 'NotIn' for node field selector")) + } + default: + allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), req.Operator, "not a valid selector operator")) + } + + if vf, found := nodeFieldSelectorValidators[req.Key]; !found { + allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), req.Key, "not a valid field selector key")) + } else { + for i, v := range req.Values { + for _, msg := range vf(v, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("values").Index(i), v, msg)) + } + } + } + + return allErrs +} diff --git a/pkg/virt-api/webhooks/validating-webhook/admitters/vmi-create-admitter.go b/pkg/virt-api/webhooks/validating-webhook/admitters/vmi-create-admitter.go index 10a236a22b4b..766e39bb1c92 100644 --- a/pkg/virt-api/webhooks/validating-webhook/admitters/vmi-create-admitter.go +++ b/pkg/virt-api/webhooks/validating-webhook/admitters/vmi-create-admitter.go @@ -149,6 +149,7 @@ func ValidateVirtualMachineInstanceSpec(field *k8sfield.Path, spec *v1.VirtualMa causes = append(causes, validateCPUFeaturePolicies(field, spec)...) causes = append(causes, validateStartStrategy(field, spec)...) causes = append(causes, validateRealtime(field, spec)...) + causes = append(causes, validateSpecAffinity(field, spec)...) maxNumberOfInterfacesExceeded := len(spec.Domain.Devices.Interfaces) > arrayLenMax if maxNumberOfInterfacesExceeded { @@ -2484,3 +2485,24 @@ func validateKernelBoot(field *k8sfield.Path, kernelBoot *v1.KernelBoot) (causes return } + +// validateSpecAffinity is function that validate spec.affinity +// instead of bring in the whole kubernetes lib we simply copy it from kubernetes/pkg/apis/core/validation/validation.go +func validateSpecAffinity(field *k8sfield.Path, spec *v1.VirtualMachineInstanceSpec) (causes []metav1.StatusCause) { + if spec.Affinity == nil { + return + } + + errorList := validateAffinity(spec.Affinity, field) + + //convert errorList to []metav1.StatusCause + for _, validationErr := range errorList { + causes = append(causes, metav1.StatusCause{ + Type: metav1.CauseTypeFieldValueInvalid, + Message: validationErr.Error(), + Field: validationErr.Field, + }) + } + + return +} diff --git a/pkg/virt-api/webhooks/validating-webhook/admitters/vmi-create-admitter_test.go b/pkg/virt-api/webhooks/validating-webhook/admitters/vmi-create-admitter_test.go index 25db1f1a243a..65a58c46c619 100644 --- a/pkg/virt-api/webhooks/validating-webhook/admitters/vmi-create-admitter_test.go +++ b/pkg/virt-api/webhooks/validating-webhook/admitters/vmi-create-admitter_test.go @@ -3723,12 +3723,615 @@ var _ = Describe("Validating VMICreate Admitter", func() { vmi.Spec.Networks = []v1.Network{*v1.DefaultPodNetwork()} bootOrder := uint(1) vmi.Spec.Domain.Devices.Interfaces = []v1.Interface{ - v1.Interface{Name: vmi.Spec.Networks[0].Name, BootOrder: &bootOrder}, + {Name: vmi.Spec.Networks[0].Name, BootOrder: &bootOrder}, } causes := ValidateVirtualMachineInstanceSpec(k8sfield.NewPath("fake"), &vmi.Spec, config) Expect(causes).To(HaveLen(len(vmi.Spec.Domain.Devices.Interfaces))) }) }) + + Context("with affinity checks", func() { + var vmi *v1.VirtualMachineInstance + BeforeEach(func() { + vmi = api.NewMinimalVMI("testvmi") + vmi.Spec.Affinity = &k8sv1.Affinity{} + }) + It("Allow to create when spec.affinity set to nil", func() { + vmi.Spec.Affinity = nil + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeTrue()) + }) + + It("(PodAffinity) Allowed PreferredDuringSchedulingIgnoredDuringExecution and RequiredDuringSchedulingIgnoredDuringExecution both are not set", func() { + vmi.Spec.Affinity.PodAffinity = &k8sv1.PodAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: nil, + RequiredDuringSchedulingIgnoredDuringExecution: nil, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeTrue()) + }) + + It("(PodAffinity) Should reject when validation failed due to TopologyKey is set to empty", func() { + vmi.Spec.Affinity.PodAffinity = &k8sv1.PodAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []k8sv1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key1", + Operator: metav1.LabelSelectorOpExists, + }, + }, + }, + TopologyKey: "", + }, + }, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeFalse()) + Expect(resp.Result.Details.Causes).To(HaveLen(3)) + Expect(resp.Result.Details.Causes[0].Field).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[0].Message).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Required value: can not be empty")) + Expect(resp.Result.Details.Causes[1].Field).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[1].Message).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Invalid value: \"\": name part must be non-empty")) + Expect(resp.Result.Details.Causes[2].Field).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[2].Message).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Invalid value: \"\": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')")) + }) + + It("(PodAffinity) Should reject when validation failed due to first element of Values slice is set to empty as well as TopologyKey", func() { + vmi.Spec.Affinity.PodAffinity = &k8sv1.PodAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []k8sv1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key1", + Operator: metav1.LabelSelectorOpIn, + Values: []string{""}, + }, + }, + }, + TopologyKey: "", + }, + }, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeFalse()) + Expect(resp.Result.Details.Causes).To(HaveLen(3)) + Expect(resp.Result.Details.Causes[0].Field).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[0].Message).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Required value: can not be empty")) + Expect(resp.Result.Details.Causes[1].Field).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[1].Message).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Invalid value: \"\": name part must be non-empty")) + Expect(resp.Result.Details.Causes[2].Field).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[2].Message).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Invalid value: \"\": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')")) + }) + + It("(PodAffinity) Should reject when validation failed due to values of MatchExpressions is set to empty and TopologyKey value is not valid", func() { + vmi.Spec.Affinity.PodAffinity = &k8sv1.PodAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []k8sv1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key1", + Operator: metav1.LabelSelectorOpIn, + Values: nil, + }, + }, + }, + TopologyKey: "hostname=host1", + }, + }, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeFalse()) + Expect(resp.Result.Details.Causes).To(HaveLen(2)) + Expect(resp.Result.Details.Causes[0].Field).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].labelSelector.matchExpressions[0].values")) + Expect(resp.Result.Details.Causes[0].Message).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].labelSelector.matchExpressions[0].values: Required value: must be specified when `operator` is 'In' or 'NotIn'")) + Expect(resp.Result.Details.Causes[1].Field).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[1].Message).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Invalid value: \"hostname=host1\": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')")) + }) + + It("(PodAffinity) Should reject when validation failed due to values of MatchExpressions and TopologyKey are both set to empty", func() { + vmi.Spec.Affinity.PodAffinity = &k8sv1.PodAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []k8sv1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key1", + Operator: metav1.LabelSelectorOpIn, + Values: nil, + }, + }, + }, + TopologyKey: "", + }, + }, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeFalse()) + Expect(resp.Result.Details.Causes).To(HaveLen(4)) + Expect(resp.Result.Details.Causes[0].Field).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].labelSelector.matchExpressions[0].values")) + Expect(resp.Result.Details.Causes[0].Message).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].labelSelector.matchExpressions[0].values: Required value: must be specified when `operator` is 'In' or 'NotIn'")) + Expect(resp.Result.Details.Causes[1].Field).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[1].Message).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Required value: can not be empty")) + Expect(resp.Result.Details.Causes[2].Field).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[2].Message).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Invalid value: \"\": name part must be non-empty")) + Expect(resp.Result.Details.Causes[3].Field).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[3].Message).To(Equal("spec.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Invalid value: \"\": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')")) + }) + + It("(PodAffinity) Should reject when affinity.PodAffinity validation failed due to value of weight is not valid", func() { + vmi.Spec.Affinity.PodAffinity = &k8sv1.PodAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []k8sv1.WeightedPodAffinityTerm{ + { + Weight: 255, + PodAffinityTerm: k8sv1.PodAffinityTerm{ + LabelSelector: nil, + TopologyKey: "test", + }, + }, + }, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeFalse()) + Expect(resp.Result.Details.Causes).To(HaveLen(1)) + Expect(resp.Result.Details.Causes[0].Field).To(Equal("spec.podAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight")) + Expect(resp.Result.Details.Causes[0].Message).To(Equal("spec.podAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight: Invalid value: 255: must be in the range 1-100")) + }) + + It("(PodAntiAffinity) Allowed both RequiredDuringSchedulingIgnoredDuringExecution and PreferredDuringSchedulingIgnoredDuringExecution are set to empty", func() { + vmi.Spec.Affinity.PodAntiAffinity = &k8sv1.PodAntiAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: nil, + RequiredDuringSchedulingIgnoredDuringExecution: nil, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeTrue()) + }) + + It("(PodAntiAffinity) Should reject when scheduler validation failed due to TopologyKey is empty", func() { + vmi.Spec.Affinity.PodAntiAffinity = &k8sv1.PodAntiAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []k8sv1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key1", + Operator: metav1.LabelSelectorOpIn, + Values: []string{""}, + }, + }, + }, + TopologyKey: "", + }, + }, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeFalse()) + Expect(resp.Result.Details.Causes).To(HaveLen(3)) + Expect(resp.Result.Details.Causes[0].Field).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[0].Message).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Required value: can not be empty")) + Expect(resp.Result.Details.Causes[1].Field).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[1].Message).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Invalid value: \"\": name part must be non-empty")) + Expect(resp.Result.Details.Causes[2].Field).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[2].Message).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Invalid value: \"\": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')")) + }) + + It("(PodAntiAffinity) Should be ok with only PreferredDuringSchedulingIgnoredDuringExecution set with proper values", func() { + vmi.Spec.Affinity.PodAntiAffinity = &k8sv1.PodAntiAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []k8sv1.WeightedPodAffinityTerm{ + { + Weight: 86, + PodAffinityTerm: k8sv1.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key1", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"a"}, + }, + }, + }, + TopologyKey: "test", + }, + }, + }, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeTrue()) + }) + + It("(PodAntiAffinity) Should reject when validation failed due to values of MatchExpressions is set to empty and TopologyKey is not valid", func() { + vmi.Spec.Affinity.PodAntiAffinity = &k8sv1.PodAntiAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []k8sv1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key1", + Operator: metav1.LabelSelectorOpIn, + Values: nil, + }, + }, + }, + TopologyKey: "hostname=host1", + }, + }, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeFalse()) + Expect(resp.Result.Details.Causes).To(HaveLen(2)) + Expect(resp.Result.Details.Causes[0].Field).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].labelSelector.matchExpressions[0].values")) + Expect(resp.Result.Details.Causes[0].Message).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].labelSelector.matchExpressions[0].values: Required value: must be specified when `operator` is 'In' or 'NotIn'")) + Expect(resp.Result.Details.Causes[1].Field).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[1].Message).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Invalid value: \"hostname=host1\": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')")) + }) + + It("(PodAntiAffinity) Should reject when scheduler validation failed due to values of MatchExpressions and TopologyKey are both set to empty", func() { + vmi.Spec.Affinity.PodAntiAffinity = &k8sv1.PodAntiAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []k8sv1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "key1", + Operator: metav1.LabelSelectorOpIn, + Values: nil, + }, + }, + }, + TopologyKey: "", + }, + }, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeFalse()) + Expect(resp.Result.Details.Causes).To(HaveLen(4)) + Expect(resp.Result.Details.Causes[0].Field).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].labelSelector.matchExpressions[0].values")) + Expect(resp.Result.Details.Causes[0].Message).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].labelSelector.matchExpressions[0].values: Required value: must be specified when `operator` is 'In' or 'NotIn'")) + Expect(resp.Result.Details.Causes[1].Field).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[1].Message).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Required value: can not be empty")) + Expect(resp.Result.Details.Causes[2].Field).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[2].Message).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Invalid value: \"\": name part must be non-empty")) + Expect(resp.Result.Details.Causes[3].Field).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey")) + Expect(resp.Result.Details.Causes[3].Message).To(Equal("spec.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey: Invalid value: \"\": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')")) + }) + + It("(NodeAffinity) Allowed both RequiredDuringSchedulingIgnoredDuringExecution and PreferredDuringSchedulingIgnoredDuringExecution are set to empty", func() { + vmi.Spec.Affinity.NodeAffinity = &k8sv1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: nil, + PreferredDuringSchedulingIgnoredDuringExecution: nil, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeTrue()) + }) + + It("(NodeAffinity) Should reject when scheduler validation failed due to NodeSelectorTerms set to empty", func() { + vmi.Spec.Affinity.NodeAffinity = &k8sv1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &k8sv1.NodeSelector{ + NodeSelectorTerms: nil, + }, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeFalse()) + Expect(resp.Result.Details.Causes).To(HaveLen(1)) + // webhookutils.ValidateSchema will take over so result will be only a message + Expect(resp.Result.Details.Causes[0].Field).To(Equal("")) + Expect(resp.Result.Details.Causes[0].Message).To(Equal("spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms in body must be of type array: \"null\"")) + }) + + It("(NodeAffinity) Allowed both MatchExpressions and MatchFields are set to empty", func() { + vmi.Spec.Affinity.NodeAffinity = &k8sv1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &k8sv1.NodeSelector{ + NodeSelectorTerms: []k8sv1.NodeSelectorTerm{ + { + MatchExpressions: nil, + MatchFields: nil, + }, + }, + }, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeTrue()) + }) + + It("(NodeAffinity) Should be ok with only MatchExpressions set", func() { + vmi.Spec.Affinity.NodeAffinity = &k8sv1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &k8sv1.NodeSelector{ + NodeSelectorTerms: []k8sv1.NodeSelectorTerm{ + { + MatchExpressions: []k8sv1.NodeSelectorRequirement{ + { + Key: "key1", + Operator: k8sv1.NodeSelectorOpExists, + Values: nil, + }, + }, + }, + }, + }, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeTrue()) + }) + + It("(NodeAffinity) Should reject when scheduler validation failed due to NodeSelectorTerms value of key is not valid", func() { + vmi.Spec.Affinity.NodeAffinity = &k8sv1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &k8sv1.NodeSelector{ + NodeSelectorTerms: []k8sv1.NodeSelectorTerm{ + { + MatchFields: []k8sv1.NodeSelectorRequirement{ + { + Key: "key", + Operator: k8sv1.NodeSelectorOpIn, + Values: []string{"value1"}, + }, + }, + }, + }, + }, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeFalse()) + Expect(resp.Result.Details.Causes[0].Field).To(Equal("spec.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].key")) + Expect(resp.Result.Details.Causes[0].Message).To(Equal("spec.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].key: Invalid value: \"key\": not a valid field selector key")) + }) + + It("(NodeAffinity) Should reject when scheduler validation failed due no element in Values slice", func() { + vmi.Spec.Affinity.NodeAffinity = &k8sv1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &k8sv1.NodeSelector{ + NodeSelectorTerms: []k8sv1.NodeSelectorTerm{ + { + MatchFields: []k8sv1.NodeSelectorRequirement{ + { + Key: "metadata.name", + Operator: k8sv1.NodeSelectorOpIn, + Values: []string{""}, + }, + }, + }, + }, + }, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeFalse()) + Expect(resp.Result.Details.Causes).To(HaveLen(1)) + Expect(resp.Result.Details.Causes[0].Field).To(Equal("spec.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].values[0]")) + Expect(resp.Result.Details.Causes[0].Message).To(Equal("spec.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchFields[0].values[0]: Invalid value: \"\": a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')")) + }) + + It("(NodeAffinity) Should be ok with only PreferredDuringSchedulingIgnoredDuringExecution set with proper values", func() { + vmi.Spec.Affinity.NodeAffinity = &k8sv1.NodeAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []k8sv1.PreferredSchedulingTerm{ + { + Weight: 20, + Preference: k8sv1.NodeSelectorTerm{ + MatchExpressions: []k8sv1.NodeSelectorRequirement{ + { + Key: "key1", + Operator: k8sv1.NodeSelectorOpExists, + Values: nil, + }, + }, + }, + }, + }, + } + vmiBytes, _ := json.Marshal(&vmi) + + ar := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Resource: webhooks.VirtualMachineInstanceGroupVersionResource, + Object: runtime.RawExtension{ + Raw: vmiBytes, + }, + }, + } + + resp := vmiCreateAdmitter.Admit(ar) + Expect(resp.Allowed).To(BeTrue()) + }) + + }) }) var _ = Describe("Function getNumberOfPodInterfaces()", func() { @@ -4139,4 +4742,5 @@ var _ = Describe("Function getNumberOfPodInterfaces()", func() { causes := webhooks.ValidateVirtualMachineInstanceHypervFeatureDependencies(path, &vmi.Spec) Expect(causes).To(BeEmpty()) }) + }) diff --git a/staging/src/kubevirt.io/api/go.mod b/staging/src/kubevirt.io/api/go.mod index 5b8547e9e0c5..ae73ea867c32 100644 --- a/staging/src/kubevirt.io/api/go.mod +++ b/staging/src/kubevirt.io/api/go.mod @@ -5,7 +5,7 @@ go 1.17 require ( github.com/pborman/uuid v1.2.0 k8s.io/api v0.23.5 - k8s.io/apiextensions-apiserver v0.23.1 + k8s.io/apiextensions-apiserver v0.23.5 k8s.io/apimachinery v0.23.5 k8s.io/utils v0.0.0-20211116205334-6203023598ed kubevirt.io/containerized-data-importer-api v1.50.0 diff --git a/staging/src/kubevirt.io/api/go.sum b/staging/src/kubevirt.io/api/go.sum index d5196d5ba194..d78001544a77 100644 --- a/staging/src/kubevirt.io/api/go.sum +++ b/staging/src/kubevirt.io/api/go.sum @@ -896,21 +896,19 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.23.1/go.mod h1:WfXnOnwSqNtG62Y1CdjoMxh7r7u9QXGCkA1u0na2jgo= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= k8s.io/api v0.23.5 h1:zno3LUiMubxD/V1Zw3ijyKO3wxrhbUF1Ck+VjBvfaoA= k8s.io/api v0.23.5/go.mod h1:Na4XuKng8PXJ2JsploYYrivXrINeTaycCGcYgF91Xm8= -k8s.io/apiextensions-apiserver v0.23.1 h1:xxE0q1vLOVZiWORu1KwNRQFsGWtImueOrqSl13sS5EU= -k8s.io/apiextensions-apiserver v0.23.1/go.mod h1:0qz4fPaHHsVhRApbtk3MGXNn2Q9M/cVWWhfHdY2SxiM= -k8s.io/apimachinery v0.23.1/go.mod h1:SADt2Kl8/sttJ62RRsi9MIV4o8f5S3coArm0Iu3fBno= +k8s.io/apiextensions-apiserver v0.23.5 h1:5SKzdXyvIJKu+zbfPc3kCbWpbxi+O+zdmAJBm26UJqI= +k8s.io/apiextensions-apiserver v0.23.5/go.mod h1:ntcPWNXS8ZPKN+zTXuzYMeg731CP0heCTl6gYBxLcuQ= k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= k8s.io/apimachinery v0.23.5 h1:Va7dwhp8wgkUPWsEXk6XglXWU4IKYLKNlv8VkX7SDM0= k8s.io/apimachinery v0.23.5/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= -k8s.io/apiserver v0.23.1/go.mod h1:Bqt0gWbeM2NefS8CjWswwd2VNAKN6lUKR85Ft4gippY= -k8s.io/client-go v0.23.1/go.mod h1:6QSI8fEuqD4zgFK0xbdwfB/PthBsIxCJMa3s17WlcO0= -k8s.io/code-generator v0.23.1/go.mod h1:V7yn6VNTCWW8GqodYCESVo95fuiEg713S8B7WacWZDA= +k8s.io/apiserver v0.23.5/go.mod h1:7wvMtGJ42VRxzgVI7jkbKvMbuCbVbgsWFT7RyXiRNTw= +k8s.io/client-go v0.23.5/go.mod h1:flkeinTO1CirYgzMPRWxUCnV0G4Fbu2vLhYCObnt/r4= k8s.io/code-generator v0.23.3/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= -k8s.io/component-base v0.23.1/go.mod h1:6llmap8QtJIXGDd4uIWJhAq0Op8AtQo6bDW2RrNMTeo= +k8s.io/code-generator v0.23.5/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= +k8s.io/component-base v0.23.5/go.mod h1:c5Nq44KZyt1aLl0IpHX82fhsn84Sb0jjzwjpcA42bY0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= @@ -921,7 +919,6 @@ k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20211116205334-6203023598ed h1:ck1fRPWPJWsMd8ZRFsWc6mh/zHp5fZ/shhbrgPUxDAE= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= kubevirt.io/containerized-data-importer-api v1.50.0 h1:O01F8L5K8qRLnkYICIfmAu0dU0P48jdO42uFPElht38= @@ -931,11 +928,10 @@ kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.25/go.mod h1:Mlj9PNLmG9bZ6BHFwFKDo5afkpWyUISkb9Me0GnK66I= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/staging/src/kubevirt.io/client-go/go.mod b/staging/src/kubevirt.io/client-go/go.mod index 809131c11d61..6d0a3cd8b65d 100644 --- a/staging/src/kubevirt.io/client-go/go.mod +++ b/staging/src/kubevirt.io/client-go/go.mod @@ -17,7 +17,7 @@ require ( github.com/openshift/client-go v0.0.0-20210112165513-ebc401615f47 github.com/spf13/pflag v1.0.5 k8s.io/api v0.23.5 - k8s.io/apiextensions-apiserver v0.23.1 + k8s.io/apiextensions-apiserver v0.23.5 k8s.io/apimachinery v0.23.5 k8s.io/client-go v12.0.0+incompatible k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf @@ -73,32 +73,32 @@ require ( replace ( github.com/openshift/api => github.com/openshift/api v0.0.0-20210105115604-44119421ec6b - k8s.io/api => k8s.io/api v0.23.1 - k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.1 - k8s.io/apimachinery => k8s.io/apimachinery v0.23.1 - k8s.io/apiserver => k8s.io/apiserver v0.23.1 - k8s.io/cli-runtime => k8s.io/cli-runtime v0.23.1 - k8s.io/client-go => k8s.io/client-go v0.23.1 - k8s.io/cloud-provider => k8s.io/cloud-provider v0.23.1 - k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.23.1 - k8s.io/code-generator => k8s.io/code-generator v0.23.1 - k8s.io/component-base => k8s.io/component-base v0.23.1 - k8s.io/cri-api => k8s.io/cri-api v0.23.1 - k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.23.1 + k8s.io/api => k8s.io/api v0.23.5 + k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.5 + k8s.io/apimachinery => k8s.io/apimachinery v0.23.5 + k8s.io/apiserver => k8s.io/apiserver v0.23.5 + k8s.io/cli-runtime => k8s.io/cli-runtime v0.23.5 + k8s.io/client-go => k8s.io/client-go v0.23.5 + k8s.io/cloud-provider => k8s.io/cloud-provider v0.23.5 + k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.23.5 + k8s.io/code-generator => k8s.io/code-generator v0.23.5 + k8s.io/component-base => k8s.io/component-base v0.23.5 + k8s.io/cri-api => k8s.io/cri-api v0.23.5 + k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.23.5 k8s.io/klog => k8s.io/klog v0.4.0 - k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.23.1 - k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.23.1 + k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.23.5 + k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.23.5 k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20210113233702-8566a335510f - k8s.io/kube-proxy => k8s.io/kube-proxy v0.23.1 - k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.23.1 - k8s.io/kubectl => k8s.io/kubectl v0.23.1 - k8s.io/kubelet => k8s.io/kubelet v0.23.1 - k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.23.1 - k8s.io/metrics => k8s.io/metrics v0.23.1 - k8s.io/node-api => k8s.io/node-api v0.23.1 - k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.23.1 - k8s.io/sample-cli-plugin => k8s.io/sample-cli-plugin v0.23.1 - k8s.io/sample-controller => k8s.io/sample-controller v0.23.1 + k8s.io/kube-proxy => k8s.io/kube-proxy v0.23.5 + k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.23.5 + k8s.io/kubectl => k8s.io/kubectl v0.23.5 + k8s.io/kubelet => k8s.io/kubelet v0.23.5 + k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.23.5 + k8s.io/metrics => k8s.io/metrics v0.23.5 + k8s.io/node-api => k8s.io/node-api v0.23.5 + k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.23.5 + k8s.io/sample-cli-plugin => k8s.io/sample-cli-plugin v0.23.5 + k8s.io/sample-controller => k8s.io/sample-controller v0.23.5 kubevirt.io/api => ../api ) diff --git a/staging/src/kubevirt.io/client-go/go.sum b/staging/src/kubevirt.io/client-go/go.sum index f9a6e63ae7d6..1a782c037ca2 100644 --- a/staging/src/kubevirt.io/client-go/go.sum +++ b/staging/src/kubevirt.io/client-go/go.sum @@ -1267,17 +1267,17 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= -k8s.io/api v0.23.1 h1:ncu/qfBfUoClqwkTGbeRqqOqBCRoUAflMuOaOD7J0c8= -k8s.io/api v0.23.1/go.mod h1:WfXnOnwSqNtG62Y1CdjoMxh7r7u9QXGCkA1u0na2jgo= -k8s.io/apiextensions-apiserver v0.23.1 h1:xxE0q1vLOVZiWORu1KwNRQFsGWtImueOrqSl13sS5EU= -k8s.io/apiextensions-apiserver v0.23.1/go.mod h1:0qz4fPaHHsVhRApbtk3MGXNn2Q9M/cVWWhfHdY2SxiM= -k8s.io/apimachinery v0.23.1 h1:sfBjlDFwj2onG0Ijx5C+SrAoeUscPrmghm7wHP+uXlo= -k8s.io/apimachinery v0.23.1/go.mod h1:SADt2Kl8/sttJ62RRsi9MIV4o8f5S3coArm0Iu3fBno= -k8s.io/apiserver v0.23.1/go.mod h1:Bqt0gWbeM2NefS8CjWswwd2VNAKN6lUKR85Ft4gippY= -k8s.io/client-go v0.23.1 h1:Ma4Fhf/p07Nmj9yAB1H7UwbFHEBrSPg8lviR24U2GiQ= -k8s.io/client-go v0.23.1/go.mod h1:6QSI8fEuqD4zgFK0xbdwfB/PthBsIxCJMa3s17WlcO0= -k8s.io/code-generator v0.23.1/go.mod h1:V7yn6VNTCWW8GqodYCESVo95fuiEg713S8B7WacWZDA= -k8s.io/component-base v0.23.1/go.mod h1:6llmap8QtJIXGDd4uIWJhAq0Op8AtQo6bDW2RrNMTeo= +k8s.io/api v0.23.5 h1:zno3LUiMubxD/V1Zw3ijyKO3wxrhbUF1Ck+VjBvfaoA= +k8s.io/api v0.23.5/go.mod h1:Na4XuKng8PXJ2JsploYYrivXrINeTaycCGcYgF91Xm8= +k8s.io/apiextensions-apiserver v0.23.5 h1:5SKzdXyvIJKu+zbfPc3kCbWpbxi+O+zdmAJBm26UJqI= +k8s.io/apiextensions-apiserver v0.23.5/go.mod h1:ntcPWNXS8ZPKN+zTXuzYMeg731CP0heCTl6gYBxLcuQ= +k8s.io/apimachinery v0.23.5 h1:Va7dwhp8wgkUPWsEXk6XglXWU4IKYLKNlv8VkX7SDM0= +k8s.io/apimachinery v0.23.5/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= +k8s.io/apiserver v0.23.5/go.mod h1:7wvMtGJ42VRxzgVI7jkbKvMbuCbVbgsWFT7RyXiRNTw= +k8s.io/client-go v0.23.5 h1:zUXHmEuqx0RY4+CsnkOn5l0GU+skkRXKGJrhmE2SLd8= +k8s.io/client-go v0.23.5/go.mod h1:flkeinTO1CirYgzMPRWxUCnV0G4Fbu2vLhYCObnt/r4= +k8s.io/code-generator v0.23.5/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= +k8s.io/component-base v0.23.5/go.mod h1:c5Nq44KZyt1aLl0IpHX82fhsn84Sb0jjzwjpcA42bY0= k8s.io/gengo v0.0.0-20190907103519-ebc107f98eab/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= @@ -1294,7 +1294,6 @@ k8s.io/kube-openapi v0.0.0-20210113233702-8566a335510f h1:ZcWbnalfwIst131Zff7dGd k8s.io/kube-openapi v0.0.0-20210113233702-8566a335510f/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20191114200735-6ca3b61696b6/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20211116205334-6203023598ed h1:ck1fRPWPJWsMd8ZRFsWc6mh/zHp5fZ/shhbrgPUxDAE= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= kubevirt.io/containerized-data-importer-api v1.50.0 h1:O01F8L5K8qRLnkYICIfmAu0dU0P48jdO42uFPElht38= @@ -1304,12 +1303,11 @@ kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.25/go.mod h1:Mlj9PNLmG9bZ6BHFwFKDo5afkpWyUISkb9Me0GnK66I= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= sigs.k8s.io/controller-tools v0.2.4/go.mod h1:m/ztfQNocGYBgTTCmFdnK94uVvgxeZeE3LtJvd/jIzA= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/tests/migration_test.go b/tests/migration_test.go index 3864c9013ebe..c883b52d7cab 100644 --- a/tests/migration_test.go +++ b/tests/migration_test.go @@ -2839,7 +2839,7 @@ var _ = Describe("[rfe_id:393][crit:high][vendor:cnv-qe@redhat.com][level:system Context("with a host-model cpu", func() { getNodeHostModel := func(node *k8sv1.Node) (hostModel string) { - for key, _ := range node.Labels { + for key := range node.Labels { if strings.HasPrefix(key, v1.HostModelCPULabel) { hostModel = strings.TrimPrefix(key, v1.HostModelCPULabel) break @@ -2849,7 +2849,7 @@ var _ = Describe("[rfe_id:393][crit:high][vendor:cnv-qe@redhat.com][level:system return hostModel } getNodeHostRequiredFeatures := func(node *k8sv1.Node) (features []string) { - for key, _ := range node.Labels { + for key := range node.Labels { if strings.HasPrefix(key, v1.HostModelRequiredFeaturesLabel) { features = append(features, strings.TrimPrefix(key, v1.HostModelRequiredFeaturesLabel)) } @@ -2857,7 +2857,7 @@ var _ = Describe("[rfe_id:393][crit:high][vendor:cnv-qe@redhat.com][level:system return features } isModelSupportedOnNode := func(node *k8sv1.Node, model string) bool { - for key, _ := range node.Labels { + for key := range node.Labels { if strings.HasPrefix(key, v1.HostModelCPULabel) && strings.Contains(key, model) { return true } @@ -2866,7 +2866,7 @@ var _ = Describe("[rfe_id:393][crit:high][vendor:cnv-qe@redhat.com][level:system } expectFeatureToBeSupportedOnNode := func(node *k8sv1.Node, features []string) { isFeatureSupported := func(feature string) bool { - for key, _ := range node.Labels { + for key := range node.Labels { if strings.HasPrefix(key, v1.CPUFeatureLabel) && strings.Contains(key, feature) { return true } @@ -2985,7 +2985,7 @@ var _ = Describe("[rfe_id:393][crit:high][vendor:cnv-qe@redhat.com][level:system It("[test_id:7505]when no node is suited for host model", func() { By("Changing node labels to support fake host model") // Remove all supported host models - for key, _ := range node.Labels { + for key := range node.Labels { if strings.HasPrefix(key, v1.HostModelCPULabel) { libnode.RemoveLabelFromNode(node.Name, key) } @@ -3045,7 +3045,7 @@ var _ = Describe("[rfe_id:393][crit:high][vendor:cnv-qe@redhat.com][level:system updatedNode := resumeNodeLabeller(node.Name, virtClient) supportedHostModelLabelExists := false - for labelKey, _ := range updatedNode.Labels { + for labelKey := range updatedNode.Labels { if strings.HasPrefix(labelKey, v1.SupportedHostModelMigrationCPU) { supportedHostModelLabelExists = true break @@ -3061,7 +3061,7 @@ var _ = Describe("[rfe_id:393][crit:high][vendor:cnv-qe@redhat.com][level:system for _, node := range nodes { currNode, err := virtClient.CoreV1().Nodes().Get(context.Background(), node.Name, metav1.GetOptions{}) Expect(err).ShouldNot(HaveOccurred()) - for key, _ := range currNode.Labels { + for key := range currNode.Labels { if strings.HasPrefix(key, v1.SupportedHostModelMigrationCPU) { libnode.RemoveLabelFromNode(currNode.Name, key) } @@ -3508,7 +3508,7 @@ var _ = Describe("[rfe_id:393][crit:high][vendor:cnv-qe@redhat.com][level:system { Key: labelKey, Operator: metav1.LabelSelectorOpIn, - Values: []string{string("")}}, + Values: []string{""}}, }, }, TopologyKey: "kubernetes.io/hostname", @@ -4483,7 +4483,7 @@ func resumeNodeLabeller(nodeName string, virtClient kubecli.KubevirtClient) *k8s } foundHostModelLabel := false - for labelKey, _ := range node.Labels { + for labelKey := range node.Labels { if strings.HasPrefix(labelKey, v1.HostModelCPULabel) { foundHostModelLabel = true break diff --git a/tests/storage/storage.go b/tests/storage/storage.go index db46e625c3dd..1d503e821f2f 100644 --- a/tests/storage/storage.go +++ b/tests/storage/storage.go @@ -1261,7 +1261,7 @@ var _ = SIGDescribe("Storage", func() { { Key: labelKey, Operator: metav1.LabelSelectorOpIn, - Values: []string{string("")}}, + Values: []string{""}}, }, }, TopologyKey: "kubernetes.io/hostname", @@ -1310,7 +1310,7 @@ var _ = SIGDescribe("Storage", func() { { Key: labelKey, Operator: metav1.LabelSelectorOpIn, - Values: []string{string("")}}, + Values: []string{""}}, }, }, TopologyKey: "kubernetes.io/hostname", diff --git a/vendor/k8s.io/api/policy/v1beta1/generated.proto b/vendor/k8s.io/api/policy/v1beta1/generated.proto index 133ba0493c69..8a2824b51bf1 100644 --- a/vendor/k8s.io/api/policy/v1beta1/generated.proto +++ b/vendor/k8s.io/api/policy/v1beta1/generated.proto @@ -144,7 +144,6 @@ message PodDisruptionBudgetSpec { // A null selector selects no pods. // An empty selector ({}) also selects no pods, which differs from standard behavior of selecting all pods. // In policy/v1, an empty selector will select all pods in the namespace. - // +patchStrategy=replace // +optional optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 2; diff --git a/vendor/k8s.io/api/policy/v1beta1/types.go b/vendor/k8s.io/api/policy/v1beta1/types.go index 553cb316dcf1..486f93461afb 100644 --- a/vendor/k8s.io/api/policy/v1beta1/types.go +++ b/vendor/k8s.io/api/policy/v1beta1/types.go @@ -36,9 +36,8 @@ type PodDisruptionBudgetSpec struct { // A null selector selects no pods. // An empty selector ({}) also selects no pods, which differs from standard behavior of selecting all pods. // In policy/v1, an empty selector will select all pods in the namespace. - // +patchStrategy=replace // +optional - Selector *metav1.LabelSelector `json:"selector,omitempty" patchStrategy:"replace" protobuf:"bytes,2,opt,name=selector"` + Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,opt,name=selector"` // An eviction is allowed if at most "maxUnavailable" pods selected by // "selector" are unavailable after the eviction, i.e. even in absence of diff --git a/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/deepcopy.go b/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/deepcopy.go index 761e27cc42e2..2bd5d529310c 100644 --- a/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/deepcopy.go +++ b/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/deepcopy.go @@ -290,5 +290,11 @@ func (in *JSONSchemaProps) DeepCopy() *JSONSchemaProps { **out = **in } + if in.XValidations != nil { + in, out := &in.XValidations, &out.XValidations + *out = make([]ValidationRule, len(*in)) + copy(*out, *in) + } + return out } diff --git a/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/conversion.go b/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/conversion.go index 9bcbe50267bb..4d29ff8235d8 100644 --- a/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/conversion.go +++ b/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/conversion.go @@ -18,6 +18,7 @@ package v1 import ( "bytes" + unsafe "unsafe" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiequality "k8s.io/apimachinery/pkg/api/equality" @@ -207,3 +208,8 @@ func Convert_apiextensions_CustomResourceConversion_To_v1_CustomResourceConversi } return nil } + +func Convert_apiextensions_ValidationRules_To_v1_ValidationRules(in *apiextensions.ValidationRules, out *ValidationRules, s conversion.Scope) error { + *out = *(*ValidationRules)(unsafe.Pointer(in)) + return nil +} diff --git a/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/deepcopy.go b/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/deepcopy.go index 84dda976b261..28dfb99f18ee 100644 --- a/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/deepcopy.go +++ b/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/deepcopy.go @@ -250,5 +250,11 @@ func (in *JSONSchemaProps) DeepCopy() *JSONSchemaProps { **out = **in } + if in.XValidations != nil { + in, out := &in.XValidations, &out.XValidations + *out = make([]ValidationRule, len(*in)) + copy(*out, *in) + } + return out } diff --git a/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/zz_generated.conversion.go b/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/zz_generated.conversion.go index 77d22c16cec4..95a58529b117 100644 --- a/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/zz_generated.conversion.go +++ b/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/zz_generated.conversion.go @@ -242,6 +242,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*apiextensions.ValidationRules)(nil), (*ValidationRules)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_apiextensions_ValidationRules_To_v1_ValidationRules(a.(*apiextensions.ValidationRules), b.(*ValidationRules), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*CustomResourceConversion)(nil), (*apiextensions.CustomResourceConversion)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_CustomResourceConversion_To_apiextensions_CustomResourceConversion(a.(*CustomResourceConversion), b.(*apiextensions.CustomResourceConversion), scope) }); err != nil { diff --git a/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/deepcopy.go b/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/deepcopy.go index 857beac4ab51..9f64585dad28 100644 --- a/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/deepcopy.go +++ b/vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/deepcopy.go @@ -266,5 +266,11 @@ func (in *JSONSchemaProps) DeepCopy() *JSONSchemaProps { **out = **in } + if in.XValidations != nil { + in, out := &in.XValidations, &out.XValidations + *out = make([]ValidationRule, len(*in)) + copy(*out, *in) + } + return out } diff --git a/vendor/k8s.io/apimachinery/pkg/api/validation/BUILD.bazel b/vendor/k8s.io/apimachinery/pkg/api/validation/BUILD.bazel new file mode 100644 index 000000000000..07d6cfa35165 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/validation/BUILD.bazel @@ -0,0 +1,23 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "generic.go", + "objectmeta.go", + ], + importmap = "kubevirt.io/kubevirt/vendor/k8s.io/apimachinery/pkg/api/validation", + importpath = "k8s.io/apimachinery/pkg/api/validation", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + ], +) diff --git a/vendor/k8s.io/apimachinery/pkg/api/validation/doc.go b/vendor/k8s.io/apimachinery/pkg/api/validation/doc.go new file mode 100644 index 000000000000..9f20152e4523 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/validation/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package validation contains generic api type validation functions. +package validation // import "k8s.io/apimachinery/pkg/api/validation" diff --git a/vendor/k8s.io/apimachinery/pkg/api/validation/generic.go b/vendor/k8s.io/apimachinery/pkg/api/validation/generic.go new file mode 100644 index 000000000000..e0b5b14900d9 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/validation/generic.go @@ -0,0 +1,88 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validation + +import ( + "strings" + + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// IsNegativeErrorMsg is a error message for value must be greater than or equal to 0. +const IsNegativeErrorMsg string = `must be greater than or equal to 0` + +// ValidateNameFunc validates that the provided name is valid for a given resource type. +// Not all resources have the same validation rules for names. Prefix is true +// if the name will have a value appended to it. If the name is not valid, +// this returns a list of descriptions of individual characteristics of the +// value that were not valid. Otherwise this returns an empty list or nil. +type ValidateNameFunc func(name string, prefix bool) []string + +// NameIsDNSSubdomain is a ValidateNameFunc for names that must be a DNS subdomain. +func NameIsDNSSubdomain(name string, prefix bool) []string { + if prefix { + name = maskTrailingDash(name) + } + return validation.IsDNS1123Subdomain(name) +} + +// NameIsDNSLabel is a ValidateNameFunc for names that must be a DNS 1123 label. +func NameIsDNSLabel(name string, prefix bool) []string { + if prefix { + name = maskTrailingDash(name) + } + return validation.IsDNS1123Label(name) +} + +// NameIsDNS1035Label is a ValidateNameFunc for names that must be a DNS 952 label. +func NameIsDNS1035Label(name string, prefix bool) []string { + if prefix { + name = maskTrailingDash(name) + } + return validation.IsDNS1035Label(name) +} + +// ValidateNamespaceName can be used to check whether the given namespace name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateNamespaceName = NameIsDNSLabel + +// ValidateServiceAccountName can be used to check whether the given service account name is valid. +// Prefix indicates this name will be used as part of generation, in which case +// trailing dashes are allowed. +var ValidateServiceAccountName = NameIsDNSSubdomain + +// maskTrailingDash replaces the final character of a string with a subdomain safe +// value if it is a dash and if the length of this string is greater than 1. Note that +// this is used when a value could be appended to the string, see ValidateNameFunc +// for more info. +func maskTrailingDash(name string) string { + if len(name) > 1 && strings.HasSuffix(name, "-") { + return name[:len(name)-2] + "a" + } + return name +} + +// ValidateNonnegativeField validates that given value is not negative. +func ValidateNonnegativeField(value int64, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if value < 0 { + allErrs = append(allErrs, field.Invalid(fldPath, value, IsNegativeErrorMsg)) + } + return allErrs +} diff --git a/vendor/k8s.io/apimachinery/pkg/api/validation/objectmeta.go b/vendor/k8s.io/apimachinery/pkg/api/validation/objectmeta.go new file mode 100644 index 000000000000..66e8d677a4e3 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/validation/objectmeta.go @@ -0,0 +1,272 @@ +/* +Copyright 2014 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validation + +import ( + "fmt" + "strings" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// FieldImmutableErrorMsg is a error message for field is immutable. +const FieldImmutableErrorMsg string = `field is immutable` + +const TotalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB + +// BannedOwners is a black list of object that are not allowed to be owners. +var BannedOwners = map[schema.GroupVersionKind]struct{}{ + {Group: "", Version: "v1", Kind: "Event"}: {}, +} + +// ValidateClusterName can be used to check whether the given cluster name is valid. +var ValidateClusterName = NameIsDNS1035Label + +// ValidateAnnotations validates that a set of annotations are correctly defined. +func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for k := range annotations { + for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) { + allErrs = append(allErrs, field.Invalid(fldPath, k, msg)) + } + } + if err := ValidateAnnotationsSize(annotations); err != nil { + allErrs = append(allErrs, field.TooLong(fldPath, "", TotalAnnotationSizeLimitB)) + } + return allErrs +} + +func ValidateAnnotationsSize(annotations map[string]string) error { + var totalSize int64 + for k, v := range annotations { + totalSize += (int64)(len(k)) + (int64)(len(v)) + } + if totalSize > (int64)(TotalAnnotationSizeLimitB) { + return fmt.Errorf("annotations size %d is larger than limit %d", totalSize, TotalAnnotationSizeLimitB) + } + return nil +} + +func validateOwnerReference(ownerReference metav1.OwnerReference, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + gvk := schema.FromAPIVersionAndKind(ownerReference.APIVersion, ownerReference.Kind) + // gvk.Group is empty for the legacy group. + if len(gvk.Version) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("apiVersion"), ownerReference.APIVersion, "version must not be empty")) + } + if len(gvk.Kind) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ownerReference.Kind, "kind must not be empty")) + } + if len(ownerReference.Name) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ownerReference.Name, "name must not be empty")) + } + if len(ownerReference.UID) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), ownerReference.UID, "uid must not be empty")) + } + if _, ok := BannedOwners[gvk]; ok { + allErrs = append(allErrs, field.Invalid(fldPath, ownerReference, fmt.Sprintf("%s is disallowed from being an owner", gvk))) + } + return allErrs +} + +// ValidateOwnerReferences validates that a set of owner references are correctly defined. +func ValidateOwnerReferences(ownerReferences []metav1.OwnerReference, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + controllerName := "" + for _, ref := range ownerReferences { + allErrs = append(allErrs, validateOwnerReference(ref, fldPath)...) + if ref.Controller != nil && *ref.Controller { + if controllerName != "" { + allErrs = append(allErrs, field.Invalid(fldPath, ownerReferences, + fmt.Sprintf("Only one reference can have Controller set to true. Found \"true\" in references for %v and %v", controllerName, ref.Name))) + } else { + controllerName = ref.Name + } + } + } + return allErrs +} + +// ValidateFinalizerName validates finalizer names. +func ValidateFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for _, msg := range validation.IsQualifiedName(stringValue) { + allErrs = append(allErrs, field.Invalid(fldPath, stringValue, msg)) + } + + return allErrs +} + +// ValidateNoNewFinalizers validates the new finalizers has no new finalizers compare to old finalizers. +func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + extra := sets.NewString(newFinalizers...).Difference(sets.NewString(oldFinalizers...)) + if len(extra) != 0 { + allErrs = append(allErrs, field.Forbidden(fldPath, fmt.Sprintf("no new finalizers can be added if the object is being deleted, found new finalizers %#v", extra.List()))) + } + return allErrs +} + +// ValidateImmutableField validates the new value and the old value are deeply equal. +func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if !apiequality.Semantic.DeepEqual(oldVal, newVal) { + allErrs = append(allErrs, field.Invalid(fldPath, newVal, FieldImmutableErrorMsg)) + } + return allErrs +} + +// ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already +// been performed. +// It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before. +func ValidateObjectMeta(objMeta *metav1.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList { + metadata, err := meta.Accessor(objMeta) + if err != nil { + var allErrs field.ErrorList + allErrs = append(allErrs, field.Invalid(fldPath, objMeta, err.Error())) + return allErrs + } + return ValidateObjectMetaAccessor(metadata, requiresNamespace, nameFn, fldPath) +} + +// ValidateObjectMetaAccessor validates an object's metadata on creation. It expects that name generation has already +// been performed. +// It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before. +func ValidateObjectMetaAccessor(meta metav1.Object, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + + if len(meta.GetGenerateName()) != 0 { + for _, msg := range nameFn(meta.GetGenerateName(), true) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("generateName"), meta.GetGenerateName(), msg)) + } + } + // If the generated name validates, but the calculated value does not, it's a problem with generation, and we + // report it here. This may confuse users, but indicates a programming bug and still must be validated. + // If there are multiple fields out of which one is required then add an or as a separator + if len(meta.GetName()) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name or generateName is required")) + } else { + for _, msg := range nameFn(meta.GetName(), false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), meta.GetName(), msg)) + } + } + if requiresNamespace { + if len(meta.GetNamespace()) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "")) + } else { + for _, msg := range ValidateNamespaceName(meta.GetNamespace(), false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), meta.GetNamespace(), msg)) + } + } + } else { + if len(meta.GetNamespace()) != 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("namespace"), "not allowed on this type")) + } + } + if len(meta.GetClusterName()) != 0 { + for _, msg := range ValidateClusterName(meta.GetClusterName(), false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("clusterName"), meta.GetClusterName(), msg)) + } + } + + allErrs = append(allErrs, ValidateNonnegativeField(meta.GetGeneration(), fldPath.Child("generation"))...) + allErrs = append(allErrs, v1validation.ValidateLabels(meta.GetLabels(), fldPath.Child("labels"))...) + allErrs = append(allErrs, ValidateAnnotations(meta.GetAnnotations(), fldPath.Child("annotations"))...) + allErrs = append(allErrs, ValidateOwnerReferences(meta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...) + allErrs = append(allErrs, ValidateFinalizers(meta.GetFinalizers(), fldPath.Child("finalizers"))...) + allErrs = append(allErrs, v1validation.ValidateManagedFields(meta.GetManagedFields(), fldPath.Child("managedFields"))...) + return allErrs +} + +// ValidateFinalizers tests if the finalizers name are valid, and if there are conflicting finalizers. +func ValidateFinalizers(finalizers []string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + hasFinalizerOrphanDependents := false + hasFinalizerDeleteDependents := false + for _, finalizer := range finalizers { + allErrs = append(allErrs, ValidateFinalizerName(finalizer, fldPath)...) + if finalizer == metav1.FinalizerOrphanDependents { + hasFinalizerOrphanDependents = true + } + if finalizer == metav1.FinalizerDeleteDependents { + hasFinalizerDeleteDependents = true + } + } + if hasFinalizerDeleteDependents && hasFinalizerOrphanDependents { + allErrs = append(allErrs, field.Invalid(fldPath, finalizers, fmt.Sprintf("finalizer %s and %s cannot be both set", metav1.FinalizerOrphanDependents, metav1.FinalizerDeleteDependents))) + } + return allErrs +} + +// ValidateObjectMetaUpdate validates an object's metadata when updated. +func ValidateObjectMetaUpdate(newMeta, oldMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList { + newMetadata, err := meta.Accessor(newMeta) + if err != nil { + allErrs := field.ErrorList{} + allErrs = append(allErrs, field.Invalid(fldPath, newMeta, err.Error())) + return allErrs + } + oldMetadata, err := meta.Accessor(oldMeta) + if err != nil { + allErrs := field.ErrorList{} + allErrs = append(allErrs, field.Invalid(fldPath, oldMeta, err.Error())) + return allErrs + } + return ValidateObjectMetaAccessorUpdate(newMetadata, oldMetadata, fldPath) +} + +// ValidateObjectMetaAccessorUpdate validates an object's metadata when updated. +func ValidateObjectMetaAccessorUpdate(newMeta, oldMeta metav1.Object, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + + // Finalizers cannot be added if the object is already being deleted. + if oldMeta.GetDeletionTimestamp() != nil { + allErrs = append(allErrs, ValidateNoNewFinalizers(newMeta.GetFinalizers(), oldMeta.GetFinalizers(), fldPath.Child("finalizers"))...) + } + + // Reject updates that don't specify a resource version + if len(newMeta.GetResourceVersion()) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceVersion"), newMeta.GetResourceVersion(), "must be specified for an update")) + } + + // Generation shouldn't be decremented + if newMeta.GetGeneration() < oldMeta.GetGeneration() { + allErrs = append(allErrs, field.Invalid(fldPath.Child("generation"), newMeta.GetGeneration(), "must not be decremented")) + } + + allErrs = append(allErrs, ValidateImmutableField(newMeta.GetName(), oldMeta.GetName(), fldPath.Child("name"))...) + allErrs = append(allErrs, ValidateImmutableField(newMeta.GetNamespace(), oldMeta.GetNamespace(), fldPath.Child("namespace"))...) + allErrs = append(allErrs, ValidateImmutableField(newMeta.GetUID(), oldMeta.GetUID(), fldPath.Child("uid"))...) + allErrs = append(allErrs, ValidateImmutableField(newMeta.GetCreationTimestamp(), oldMeta.GetCreationTimestamp(), fldPath.Child("creationTimestamp"))...) + allErrs = append(allErrs, ValidateImmutableField(newMeta.GetDeletionTimestamp(), oldMeta.GetDeletionTimestamp(), fldPath.Child("deletionTimestamp"))...) + allErrs = append(allErrs, ValidateImmutableField(newMeta.GetDeletionGracePeriodSeconds(), oldMeta.GetDeletionGracePeriodSeconds(), fldPath.Child("deletionGracePeriodSeconds"))...) + allErrs = append(allErrs, ValidateImmutableField(newMeta.GetClusterName(), oldMeta.GetClusterName(), fldPath.Child("clusterName"))...) + + allErrs = append(allErrs, v1validation.ValidateLabels(newMeta.GetLabels(), fldPath.Child("labels"))...) + allErrs = append(allErrs, ValidateAnnotations(newMeta.GetAnnotations(), fldPath.Child("annotations"))...) + allErrs = append(allErrs, ValidateOwnerReferences(newMeta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...) + allErrs = append(allErrs, v1validation.ValidateManagedFields(newMeta.GetManagedFields(), fldPath.Child("managedFields"))...) + + return allErrs +} diff --git a/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation/BUILD.bazel b/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation/BUILD.bazel new file mode 100644 index 000000000000..d8cff99ef0bb --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation/BUILD.bazel @@ -0,0 +1,16 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["validation.go"], + importmap = "kubevirt.io/kubevirt/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation", + importpath = "k8s.io/apimachinery/pkg/apis/meta/v1/validation", + visibility = ["//visibility:public"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", + ], +) diff --git a/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation.go b/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation.go new file mode 100644 index 000000000000..4c09898b8b93 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/validation/validation.go @@ -0,0 +1,286 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validation + +import ( + "fmt" + "regexp" + "unicode" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func ValidateLabelSelector(ps *metav1.LabelSelector, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if ps == nil { + return allErrs + } + allErrs = append(allErrs, ValidateLabels(ps.MatchLabels, fldPath.Child("matchLabels"))...) + for i, expr := range ps.MatchExpressions { + allErrs = append(allErrs, ValidateLabelSelectorRequirement(expr, fldPath.Child("matchExpressions").Index(i))...) + } + return allErrs +} + +func ValidateLabelSelectorRequirement(sr metav1.LabelSelectorRequirement, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + switch sr.Operator { + case metav1.LabelSelectorOpIn, metav1.LabelSelectorOpNotIn: + if len(sr.Values) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'")) + } + case metav1.LabelSelectorOpExists, metav1.LabelSelectorOpDoesNotExist: + if len(sr.Values) > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'")) + } + default: + allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), sr.Operator, "not a valid selector operator")) + } + allErrs = append(allErrs, ValidateLabelName(sr.Key, fldPath.Child("key"))...) + return allErrs +} + +// ValidateLabelName validates that the label name is correctly defined. +func ValidateLabelName(labelName string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for _, msg := range validation.IsQualifiedName(labelName) { + allErrs = append(allErrs, field.Invalid(fldPath, labelName, msg)) + } + return allErrs +} + +// ValidateLabels validates that a set of labels are correctly defined. +func ValidateLabels(labels map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for k, v := range labels { + allErrs = append(allErrs, ValidateLabelName(k, fldPath)...) + for _, msg := range validation.IsValidLabelValue(v) { + allErrs = append(allErrs, field.Invalid(fldPath, v, msg)) + } + } + return allErrs +} + +func ValidateDeleteOptions(options *metav1.DeleteOptions) field.ErrorList { + allErrs := field.ErrorList{} + //lint:file-ignore SA1019 Keep validation for deprecated OrphanDependents option until it's being removed + if options.OrphanDependents != nil && options.PropagationPolicy != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("propagationPolicy"), options.PropagationPolicy, "orphanDependents and deletionPropagation cannot be both set")) + } + if options.PropagationPolicy != nil && + *options.PropagationPolicy != metav1.DeletePropagationForeground && + *options.PropagationPolicy != metav1.DeletePropagationBackground && + *options.PropagationPolicy != metav1.DeletePropagationOrphan { + allErrs = append(allErrs, field.NotSupported(field.NewPath("propagationPolicy"), options.PropagationPolicy, []string{string(metav1.DeletePropagationForeground), string(metav1.DeletePropagationBackground), string(metav1.DeletePropagationOrphan), "nil"})) + } + allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...) + return allErrs +} + +func ValidateCreateOptions(options *metav1.CreateOptions) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...) + allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...) + allErrs = append(allErrs, ValidateFieldValidation(field.NewPath("fieldValidation"), options.FieldValidation)...) + return allErrs +} + +func ValidateUpdateOptions(options *metav1.UpdateOptions) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...) + allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...) + allErrs = append(allErrs, ValidateFieldValidation(field.NewPath("fieldValidation"), options.FieldValidation)...) + return allErrs +} + +func ValidatePatchOptions(options *metav1.PatchOptions, patchType types.PatchType) field.ErrorList { + allErrs := field.ErrorList{} + if patchType != types.ApplyPatchType { + if options.Force != nil { + allErrs = append(allErrs, field.Forbidden(field.NewPath("force"), "may not be specified for non-apply patch")) + } + } else { + if options.FieldManager == "" { + // This field is defaulted to "kubectl" by kubectl, but HAS TO be explicitly set by controllers. + allErrs = append(allErrs, field.Required(field.NewPath("fieldManager"), "is required for apply patch")) + } + } + allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...) + allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...) + allErrs = append(allErrs, ValidateFieldValidation(field.NewPath("fieldValidation"), options.FieldValidation)...) + return allErrs +} + +var FieldManagerMaxLength = 128 + +// ValidateFieldManager valides that the fieldManager is the proper length and +// only has printable characters. +func ValidateFieldManager(fieldManager string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + // the field can not be set as a `*string`, so a empty string ("") is + // considered as not set and is defaulted by the rest of the process + // (unless apply is used, in which case it is required). + if len(fieldManager) > FieldManagerMaxLength { + allErrs = append(allErrs, field.TooLong(fldPath, fieldManager, FieldManagerMaxLength)) + } + // Verify that all characters are printable. + for i, r := range fieldManager { + if !unicode.IsPrint(r) { + allErrs = append(allErrs, field.Invalid(fldPath, fieldManager, fmt.Sprintf("invalid character %#U (at position %d)", r, i))) + } + } + + return allErrs +} + +var allowedDryRunValues = sets.NewString(metav1.DryRunAll) + +// ValidateDryRun validates that a dryRun query param only contains allowed values. +func ValidateDryRun(fldPath *field.Path, dryRun []string) field.ErrorList { + allErrs := field.ErrorList{} + if !allowedDryRunValues.HasAll(dryRun...) { + allErrs = append(allErrs, field.NotSupported(fldPath, dryRun, allowedDryRunValues.List())) + } + return allErrs +} + +var allowedFieldValidationValues = sets.NewString("", metav1.FieldValidationIgnore, metav1.FieldValidationWarn, metav1.FieldValidationStrict) + +// ValidateFieldValidation validates that a fieldValidation query param only contains allowed values. +func ValidateFieldValidation(fldPath *field.Path, fieldValidation string) field.ErrorList { + allErrs := field.ErrorList{} + if !allowedFieldValidationValues.Has(fieldValidation) { + allErrs = append(allErrs, field.NotSupported(fldPath, fieldValidation, allowedFieldValidationValues.List())) + } + return allErrs + +} + +const UninitializedStatusUpdateErrorMsg string = `must not update status when the object is uninitialized` + +// ValidateTableOptions returns any invalid flags on TableOptions. +func ValidateTableOptions(opts *metav1.TableOptions) field.ErrorList { + var allErrs field.ErrorList + switch opts.IncludeObject { + case metav1.IncludeMetadata, metav1.IncludeNone, metav1.IncludeObject, "": + default: + allErrs = append(allErrs, field.Invalid(field.NewPath("includeObject"), opts.IncludeObject, "must be 'Metadata', 'Object', 'None', or empty")) + } + return allErrs +} + +const MaxSubresourceNameLength = 256 + +func ValidateManagedFields(fieldsList []metav1.ManagedFieldsEntry, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + for i, fields := range fieldsList { + fldPath := fldPath.Index(i) + switch fields.Operation { + case metav1.ManagedFieldsOperationApply, metav1.ManagedFieldsOperationUpdate: + default: + allErrs = append(allErrs, field.Invalid(fldPath.Child("operation"), fields.Operation, "must be `Apply` or `Update`")) + } + if len(fields.FieldsType) > 0 && fields.FieldsType != "FieldsV1" { + allErrs = append(allErrs, field.Invalid(fldPath.Child("fieldsType"), fields.FieldsType, "must be `FieldsV1`")) + } + allErrs = append(allErrs, ValidateFieldManager(fields.Manager, fldPath.Child("manager"))...) + + if len(fields.Subresource) > MaxSubresourceNameLength { + allErrs = append(allErrs, field.TooLong(fldPath.Child("subresource"), fields.Subresource, MaxSubresourceNameLength)) + } + } + return allErrs +} + +func ValidateConditions(conditions []metav1.Condition, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + + conditionTypeToFirstIndex := map[string]int{} + for i, condition := range conditions { + if _, ok := conditionTypeToFirstIndex[condition.Type]; ok { + allErrs = append(allErrs, field.Duplicate(fldPath.Index(i).Child("type"), condition.Type)) + } else { + conditionTypeToFirstIndex[condition.Type] = i + } + + allErrs = append(allErrs, ValidateCondition(condition, fldPath.Index(i))...) + } + + return allErrs +} + +// validConditionStatuses is used internally to check validity and provide a good message +var validConditionStatuses = sets.NewString(string(metav1.ConditionTrue), string(metav1.ConditionFalse), string(metav1.ConditionUnknown)) + +const ( + maxReasonLen = 1 * 1024 + maxMessageLen = 32 * 1024 +) + +func ValidateCondition(condition metav1.Condition, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + + // type is set and is a valid format + allErrs = append(allErrs, ValidateLabelName(condition.Type, fldPath.Child("type"))...) + + // status is set and is an accepted value + if !validConditionStatuses.Has(string(condition.Status)) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("status"), condition.Status, validConditionStatuses.List())) + } + + if condition.ObservedGeneration < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("observedGeneration"), condition.ObservedGeneration, "must be greater than or equal to zero")) + } + + if condition.LastTransitionTime.IsZero() { + allErrs = append(allErrs, field.Required(fldPath.Child("lastTransitionTime"), "must be set")) + } + + if len(condition.Reason) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("reason"), "must be set")) + } else { + for _, currErr := range isValidConditionReason(condition.Reason) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("reason"), condition.Reason, currErr)) + } + if len(condition.Reason) > maxReasonLen { + allErrs = append(allErrs, field.TooLong(fldPath.Child("reason"), condition.Reason, maxReasonLen)) + } + } + + if len(condition.Message) > maxMessageLen { + allErrs = append(allErrs, field.TooLong(fldPath.Child("message"), condition.Message, maxMessageLen)) + } + + return allErrs +} + +const conditionReasonFmt string = "[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?" +const conditionReasonErrMsg string = "a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_'" + +var conditionReasonRegexp = regexp.MustCompile("^" + conditionReasonFmt + "$") + +// isValidConditionReason tests for a string that conforms to rules for condition reasons. This checks the format, but not the length. +func isValidConditionReason(value string) []string { + if !conditionReasonRegexp.MatchString(value) { + return []string{validation.RegexError(conditionReasonErrMsg, conditionReasonFmt, "my_name", "MY_NAME", "MyName", "ReasonA,ReasonB", "ReasonA:ReasonB")} + } + return nil +} diff --git a/vendor/k8s.io/client-go/tools/pager/pager.go b/vendor/k8s.io/client-go/tools/pager/pager.go index f6c6a01298dc..805859e092bc 100644 --- a/vendor/k8s.io/client-go/tools/pager/pager.go +++ b/vendor/k8s.io/client-go/tools/pager/pager.go @@ -78,6 +78,7 @@ func (p *ListPager) List(ctx context.Context, options metav1.ListOptions) (runti options.Limit = p.PageSize } requestedResourceVersion := options.ResourceVersion + requestedResourceVersionMatch := options.ResourceVersionMatch var list *metainternalversion.List paginatedResult := false @@ -102,6 +103,7 @@ func (p *ListPager) List(ctx context.Context, options metav1.ListOptions) (runti options.Limit = 0 options.Continue = "" options.ResourceVersion = requestedResourceVersion + options.ResourceVersionMatch = requestedResourceVersionMatch result, err := p.PageFn(ctx, options) return result, paginatedResult, err } @@ -135,10 +137,11 @@ func (p *ListPager) List(ctx context.Context, options metav1.ListOptions) (runti // set the next loop up options.Continue = m.GetContinue() - // Clear the ResourceVersion on the subsequent List calls to avoid the + // Clear the ResourceVersion(Match) on the subsequent List calls to avoid the // `specifying resource version is not allowed when using continue` error. // See https://github.com/kubernetes/kubernetes/issues/85221#issuecomment-553748143. options.ResourceVersion = "" + options.ResourceVersionMatch = "" // At this point, result is already paginated. paginatedResult = true } diff --git a/vendor/modules.txt b/vendor/modules.txt index 6e79cef29cf9..1715ca50a3cf 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -673,7 +673,7 @@ gopkg.in/yaml.v2 # gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ## explicit gopkg.in/yaml.v3 -# k8s.io/api v0.23.5 => k8s.io/api v0.23.1 +# k8s.io/api v0.23.5 => k8s.io/api v0.23.5 ## explicit; go 1.16 k8s.io/api/admission/v1 k8s.io/api/admissionregistration/v1 @@ -721,7 +721,7 @@ k8s.io/api/scheduling/v1beta1 k8s.io/api/storage/v1 k8s.io/api/storage/v1alpha1 k8s.io/api/storage/v1beta1 -# k8s.io/apiextensions-apiserver v0.23.1 => k8s.io/apiextensions-apiserver v0.23.1 +# k8s.io/apiextensions-apiserver v0.23.5 => k8s.io/apiextensions-apiserver v0.23.5 ## explicit; go 1.16 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1 @@ -733,15 +733,17 @@ k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextension k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1/fake k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1 k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1/fake -# k8s.io/apimachinery v0.23.5 => k8s.io/apimachinery v0.23.1 +# k8s.io/apimachinery v0.23.5 => k8s.io/apimachinery v0.23.5 ## explicit; go 1.16 k8s.io/apimachinery/pkg/api/equality k8s.io/apimachinery/pkg/api/errors k8s.io/apimachinery/pkg/api/meta k8s.io/apimachinery/pkg/api/resource +k8s.io/apimachinery/pkg/api/validation k8s.io/apimachinery/pkg/apis/meta/internalversion k8s.io/apimachinery/pkg/apis/meta/v1 k8s.io/apimachinery/pkg/apis/meta/v1/unstructured +k8s.io/apimachinery/pkg/apis/meta/v1/validation k8s.io/apimachinery/pkg/apis/meta/v1beta1 k8s.io/apimachinery/pkg/conversion k8s.io/apimachinery/pkg/conversion/queryparams @@ -785,7 +787,7 @@ k8s.io/apimachinery/pkg/watch k8s.io/apimachinery/third_party/forked/golang/json k8s.io/apimachinery/third_party/forked/golang/netutil k8s.io/apimachinery/third_party/forked/golang/reflect -# k8s.io/client-go v12.0.0+incompatible => k8s.io/client-go v0.23.1 +# k8s.io/client-go v12.0.0+incompatible => k8s.io/client-go v0.23.5 ## explicit; go 1.16 k8s.io/client-go/applyconfigurations/admissionregistration/v1 k8s.io/client-go/applyconfigurations/admissionregistration/v1beta1 @@ -1079,7 +1081,7 @@ k8s.io/client-go/util/workqueue # k8s.io/klog/v2 v2.40.1 ## explicit; go 1.13 k8s.io/klog/v2 -# k8s.io/kube-aggregator v0.23.1 => k8s.io/kube-aggregator v0.23.1 +# k8s.io/kube-aggregator v0.23.5 => k8s.io/kube-aggregator v0.23.5 ## explicit; go 1.16 k8s.io/kube-aggregator/pkg/apis/apiregistration k8s.io/kube-aggregator/pkg/apis/apiregistration/v1 @@ -1095,7 +1097,7 @@ k8s.io/kube-openapi/pkg/common k8s.io/kube-openapi/pkg/schemaconv k8s.io/kube-openapi/pkg/util k8s.io/kube-openapi/pkg/util/proto -# k8s.io/kubectl v0.0.0-00010101000000-000000000000 => k8s.io/kubectl v0.23.1 +# k8s.io/kubectl v0.0.0-00010101000000-000000000000 => k8s.io/kubectl v0.23.5 ## explicit; go 1.16 k8s.io/kubectl/pkg/cmd/util/podcmd # k8s.io/utils v0.0.0-20211116205334-6203023598ed @@ -1223,32 +1225,32 @@ sigs.k8s.io/yaml # github.com/openshift/api => github.com/openshift/api v0.0.0-20191219222812-2987a591a72c # github.com/openshift/client-go => github.com/openshift/client-go v0.0.0-20210112165513-ebc401615f47 # github.com/operator-framework/operator-lifecycle-manager => github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190128024246-5eb7ae5bdb7a -# k8s.io/api => k8s.io/api v0.23.1 -# k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.1 -# k8s.io/apimachinery => k8s.io/apimachinery v0.23.1 -# k8s.io/apiserver => k8s.io/apiserver v0.23.1 -# k8s.io/cli-runtime => k8s.io/cli-runtime v0.23.1 -# k8s.io/client-go => k8s.io/client-go v0.23.1 -# k8s.io/cloud-provider => k8s.io/cloud-provider v0.23.1 -# k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.23.1 -# k8s.io/code-generator => k8s.io/code-generator v0.23.1 -# k8s.io/component-base => k8s.io/component-base v0.23.1 -# k8s.io/cri-api => k8s.io/cri-api v0.23.1 -# k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.23.1 +# k8s.io/api => k8s.io/api v0.23.5 +# k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.5 +# k8s.io/apimachinery => k8s.io/apimachinery v0.23.5 +# k8s.io/apiserver => k8s.io/apiserver v0.23.5 +# k8s.io/cli-runtime => k8s.io/cli-runtime v0.23.5 +# k8s.io/client-go => k8s.io/client-go v0.23.5 +# k8s.io/cloud-provider => k8s.io/cloud-provider v0.23.5 +# k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.23.5 +# k8s.io/code-generator => k8s.io/code-generator v0.23.5 +# k8s.io/component-base => k8s.io/component-base v0.23.5 +# k8s.io/cri-api => k8s.io/cri-api v0.23.5 +# k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.23.5 # k8s.io/klog => k8s.io/klog v0.4.0 -# k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.23.1 -# k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.23.1 +# k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.23.5 +# k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.23.5 # k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20210113233702-8566a335510f -# k8s.io/kube-proxy => k8s.io/kube-proxy v0.23.1 -# k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.23.1 -# k8s.io/kubectl => k8s.io/kubectl v0.23.1 -# k8s.io/kubelet => k8s.io/kubelet v0.23.1 -# k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.23.1 -# k8s.io/metrics => k8s.io/metrics v0.23.1 -# k8s.io/node-api => k8s.io/node-api v0.23.1 -# k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.23.1 -# k8s.io/sample-cli-plugin => k8s.io/sample-cli-plugin v0.23.1 -# k8s.io/sample-controller => k8s.io/sample-controller v0.23.1 +# k8s.io/kube-proxy => k8s.io/kube-proxy v0.23.5 +# k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.23.5 +# k8s.io/kubectl => k8s.io/kubectl v0.23.5 +# k8s.io/kubelet => k8s.io/kubelet v0.23.5 +# k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.23.5 +# k8s.io/metrics => k8s.io/metrics v0.23.5 +# k8s.io/node-api => k8s.io/node-api v0.23.5 +# k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.23.5 +# k8s.io/sample-cli-plugin => k8s.io/sample-cli-plugin v0.23.5 +# k8s.io/sample-controller => k8s.io/sample-controller v0.23.5 # kubevirt.io/api => ./staging/src/kubevirt.io/api # kubevirt.io/client-go => ./staging/src/kubevirt.io/client-go # kubevirt.io/containerized-data-importer => kubevirt.io/containerized-data-importer v1.42.0