diff --git a/pkg/api/pod/warnings.go b/pkg/api/pod/warnings.go new file mode 100644 index 00000000000..e1f93e85dec --- /dev/null +++ b/pkg/api/pod/warnings.go @@ -0,0 +1,257 @@ +/* +Copyright 2021 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 pod + +import ( + "context" + "fmt" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/apis/core/pods" +) + +func GetWarningsForPod(ctx context.Context, pod, oldPod *api.Pod) []string { + if pod == nil { + return nil + } + + var ( + oldSpec *api.PodSpec + oldMeta *metav1.ObjectMeta + ) + if oldPod != nil { + oldSpec = &oldPod.Spec + oldMeta = &oldPod.ObjectMeta + } + return warningsForPodSpecAndMeta(nil, &pod.Spec, &pod.ObjectMeta, oldSpec, oldMeta) +} + +func GetWarningsForPodTemplate(ctx context.Context, fieldPath *field.Path, podTemplate, oldPodTemplate *api.PodTemplateSpec) []string { + if podTemplate == nil { + return nil + } + + var ( + oldSpec *api.PodSpec + oldMeta *metav1.ObjectMeta + ) + if oldPodTemplate != nil { + oldSpec = &oldPodTemplate.Spec + oldMeta = &oldPodTemplate.ObjectMeta + } + return warningsForPodSpecAndMeta(fieldPath, &podTemplate.Spec, &podTemplate.ObjectMeta, oldSpec, oldMeta) +} + +var deprecatedNodeLabels = map[string]string{ + `beta.kubernetes.io/arch`: `deprecated since v1.14; use "kubernetes.io/arch" instead`, + `beta.kubernetes.io/os`: `deprecated since v1.14; use "kubernetes.io/os" instead`, + `failure-domain.beta.kubernetes.io/region`: `deprecated since v1.17; use "topology.kubernetes.io/region" instead`, + `failure-domain.beta.kubernetes.io/zone`: `deprecated since v1.17; use "topology.kubernetes.io/zone" instead`, + `beta.kubernetes.io/instance-type`: `deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`, +} + +var deprecatedAnnotations = []struct { + key string + prefix string + message string +}{ + { + key: `scheduler.alpha.kubernetes.io/critical-pod`, + message: `non-functional in v1.16+; use the "priorityClassName" field instead`, + }, + { + key: `seccomp.security.alpha.kubernetes.io/pod`, + prefix: `container.seccomp.security.alpha.kubernetes.io/`, + message: `deprecated since v1.19; use the "seccompProfile" field instead`, + }, + { + key: `security.alpha.kubernetes.io/sysctls`, + message: `non-functional in v1.11+; use the "sysctls" field instead`, + }, + { + key: `security.alpha.kubernetes.io/unsafe-sysctls`, + message: `non-functional in v1.11+; use the "sysctls" field instead`, + }, +} + +func warningsForPodSpecAndMeta(fieldPath *field.Path, podSpec *api.PodSpec, meta *metav1.ObjectMeta, oldPodSpec *api.PodSpec, oldMeta *metav1.ObjectMeta) []string { + var warnings []string + + // use of deprecated node labels in selectors/affinity/topology + for k := range podSpec.NodeSelector { + if msg, deprecated := deprecatedNodeLabels[k]; deprecated { + warnings = append(warnings, fmt.Sprintf("%s: %s", fieldPath.Child("spec", "nodeSelector").Key(k), msg)) + } + } + if podSpec.Affinity != nil && podSpec.Affinity.NodeAffinity != nil { + n := podSpec.Affinity.NodeAffinity + if n.RequiredDuringSchedulingIgnoredDuringExecution != nil { + for i, t := range n.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms { + for j, e := range t.MatchExpressions { + if msg, deprecated := deprecatedNodeLabels[e.Key]; deprecated { + warnings = append( + warnings, + fmt.Sprintf( + "%s: %s is %s", + fieldPath.Child("spec", "affinity", "nodeAffinity", "requiredDuringSchedulingIgnoredDuringExecution", "nodeSelectorTerms").Index(i). + Child("matchExpressions").Index(j). + Child("key"), + e.Key, + msg, + ), + ) + } + } + } + } + for i, t := range n.PreferredDuringSchedulingIgnoredDuringExecution { + for j, e := range t.Preference.MatchExpressions { + if msg, deprecated := deprecatedNodeLabels[e.Key]; deprecated { + warnings = append( + warnings, + fmt.Sprintf( + "%s: %s is %s", + fieldPath.Child("spec", "affinity", "nodeAffinity", "preferredDuringSchedulingIgnoredDuringExecution").Index(i). + Child("preference"). + Child("matchExpressions").Index(j). + Child("key"), + e.Key, + msg, + ), + ) + } + } + } + } + for i, t := range podSpec.TopologySpreadConstraints { + if msg, deprecated := deprecatedNodeLabels[t.TopologyKey]; deprecated { + warnings = append(warnings, fmt.Sprintf( + "%s: %s is %s", + fieldPath.Child("spec", "topologySpreadConstraints").Index(i).Child("topologyKey"), + t.TopologyKey, + msg, + )) + } + } + + // use of deprecated annotations + for _, deprecated := range deprecatedAnnotations { + if _, exists := meta.Annotations[deprecated.key]; exists { + warnings = append(warnings, fmt.Sprintf("%s: %s", fieldPath.Child("metadata", "annotations").Key(deprecated.key), deprecated.message)) + } + if len(deprecated.prefix) > 0 { + for k := range meta.Annotations { + if strings.HasPrefix(k, deprecated.prefix) { + warnings = append(warnings, fmt.Sprintf("%s: %s", fieldPath.Child("metadata", "annotations").Key(k), deprecated.message)) + break + } + } + } + } + + // removed volume plugins + for i, v := range podSpec.Volumes { + if v.PhotonPersistentDisk != nil { + warnings = append(warnings, fmt.Sprintf("%s: non-functional in v1.16+", fieldPath.Child("spec", "volumes").Index(i).Child("photonPersistentDisk"))) + } + } + + // duplicate hostAliases (#91670, #58477) + if len(podSpec.HostAliases) > 1 { + items := sets.NewString() + for i, item := range podSpec.HostAliases { + if items.Has(item.IP) { + warnings = append(warnings, fmt.Sprintf("%s: duplicate ip %q", fieldPath.Child("spec", "hostAliases").Index(i).Child("ip"), item.IP)) + } else { + items.Insert(item.IP) + } + } + } + + // duplicate imagePullSecrets (#91629, #58477) + if len(podSpec.ImagePullSecrets) > 1 { + items := sets.NewString() + for i, item := range podSpec.ImagePullSecrets { + if items.Has(item.Name) { + warnings = append(warnings, fmt.Sprintf("%s: duplicate name %q", fieldPath.Child("spec", "imagePullSecrets").Index(i).Child("name"), item.Name)) + } else { + items.Insert(item.Name) + } + } + } + // imagePullSecrets with empty name (#99454#issuecomment-787838112) + for i, item := range podSpec.ImagePullSecrets { + if len(item.Name) == 0 { + warnings = append(warnings, fmt.Sprintf("%s: invalid empty name %q", fieldPath.Child("spec", "imagePullSecrets").Index(i).Child("name"), item.Name)) + } + } + + // duplicate volume names (#78266, #58477) + if len(podSpec.Volumes) > 1 { + items := sets.NewString() + for i, item := range podSpec.Volumes { + if items.Has(item.Name) { + warnings = append(warnings, fmt.Sprintf("%s: duplicate name %q", fieldPath.Child("spec", "volumes").Index(i).Child("name"), item.Name)) + } else { + items.Insert(item.Name) + } + } + } + + // fractional memory/ephemeral-storage requests/limits (#79950, #49442, #18538) + if value, ok := podSpec.Overhead[api.ResourceMemory]; ok && value.MilliValue()%int64(1000) != int64(0) { + warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", fieldPath.Child("spec", "overhead").Key(string(api.ResourceMemory)), value.String())) + } + if value, ok := podSpec.Overhead[api.ResourceEphemeralStorage]; ok && value.MilliValue()%int64(1000) != int64(0) { + warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", fieldPath.Child("spec", "overhead").Key(string(api.ResourceEphemeralStorage)), value.String())) + } + + pods.VisitContainersWithPath(podSpec, fieldPath.Child("spec"), func(c *api.Container, p *field.Path) bool { + // fractional memory/ephemeral-storage requests/limits (#79950, #49442, #18538) + if value, ok := c.Resources.Limits[api.ResourceMemory]; ok && value.MilliValue()%int64(1000) != int64(0) { + warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", p.Child("resources", "limits").Key(string(api.ResourceMemory)), value.String())) + } + if value, ok := c.Resources.Requests[api.ResourceMemory]; ok && value.MilliValue()%int64(1000) != int64(0) { + warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", p.Child("resources", "requests").Key(string(api.ResourceMemory)), value.String())) + } + if value, ok := c.Resources.Limits[api.ResourceEphemeralStorage]; ok && value.MilliValue()%int64(1000) != int64(0) { + warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", p.Child("resources", "limits").Key(string(api.ResourceEphemeralStorage)), value.String())) + } + if value, ok := c.Resources.Requests[api.ResourceEphemeralStorage]; ok && value.MilliValue()%int64(1000) != int64(0) { + warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", p.Child("resources", "requests").Key(string(api.ResourceEphemeralStorage)), value.String())) + } + + // duplicate containers[*].env (#86163, #93266, #58477) + if len(c.Env) > 1 { + items := sets.NewString() + for i, item := range c.Env { + if items.Has(item.Name) { + warnings = append(warnings, fmt.Sprintf("%s: duplicate name %q", p.Child("env").Index(i).Child("name"), item.Name)) + } else { + items.Insert(item.Name) + } + } + } + return true + }) + + return warnings +} diff --git a/pkg/api/pod/warnings_test.go b/pkg/api/pod/warnings_test.go new file mode 100644 index 00000000000..bfb59dfeb9a --- /dev/null +++ b/pkg/api/pod/warnings_test.go @@ -0,0 +1,416 @@ +/* +Copyright 2021 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 pod + +import ( + "context" + "testing" + + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + api "k8s.io/kubernetes/pkg/apis/core" +) + +func BenchmarkNoWarnings(b *testing.B) { + ctx := context.TODO() + resources := api.ResourceList{ + api.ResourceCPU: resource.MustParse("100m"), + api.ResourceMemory: resource.MustParse("4M"), + api.ResourceEphemeralStorage: resource.MustParse("4G"), + } + env := []api.EnvVar{ + {Name: "a"}, + {Name: "b"}, + } + pod := &api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{`foo`: `bar`}, + }, + Spec: api.PodSpec{ + NodeSelector: map[string]string{"foo": "bar", "baz": "quux"}, + Affinity: &api.Affinity{ + NodeAffinity: &api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{ + NodeSelectorTerms: []api.NodeSelectorTerm{ + {MatchExpressions: []api.NodeSelectorRequirement{{Key: `foo`}}}, + }, + }, + PreferredDuringSchedulingIgnoredDuringExecution: []api.PreferredSchedulingTerm{ + {Preference: api.NodeSelectorTerm{MatchExpressions: []api.NodeSelectorRequirement{{Key: `foo`}}}}, + }, + }, + }, + TopologySpreadConstraints: []api.TopologySpreadConstraint{ + {TopologyKey: `foo`}, + }, + HostAliases: []api.HostAlias{ + {IP: "1.1.1.1"}, + {IP: "2.2.2.2"}, + }, + ImagePullSecrets: []api.LocalObjectReference{ + {Name: "secret1"}, + {Name: "secret2"}, + }, + InitContainers: []api.Container{ + {Name: "init1", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}}, + {Name: "init2", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}}, + }, + Containers: []api.Container{ + {Name: "container1", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}}, + {Name: "container2", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}}, + }, + Overhead: resources, + Volumes: []api.Volume{ + {Name: "a"}, + {Name: "b"}, + }, + }, + } + oldPod := &api.Pod{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + w := GetWarningsForPod(ctx, pod, oldPod) + if len(w) > 0 { + b.Fatalf("expected 0 warnings, got %q", w) + } + } +} + +func BenchmarkWarnings(b *testing.B) { + ctx := context.TODO() + resources := api.ResourceList{ + api.ResourceCPU: resource.MustParse("100m"), + api.ResourceMemory: resource.MustParse("4m"), + api.ResourceEphemeralStorage: resource.MustParse("4m"), + } + env := []api.EnvVar{ + {Name: "a"}, + {Name: "a"}, + } + pod := &api.Pod{ + Spec: api.PodSpec{ + HostAliases: []api.HostAlias{ + {IP: "1.1.1.1"}, + {IP: "1.1.1.1"}, + }, + ImagePullSecrets: []api.LocalObjectReference{ + {Name: "secret1"}, + {Name: "secret1"}, + {Name: ""}, + }, + InitContainers: []api.Container{ + {Name: "init1", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}}, + {Name: "init2", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}}, + }, + Containers: []api.Container{ + {Name: "container1", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}}, + {Name: "container2", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}}, + }, + Overhead: resources, + Volumes: []api.Volume{ + {Name: "a"}, + {Name: "a"}, + }, + }, + } + oldPod := &api.Pod{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + GetWarningsForPod(ctx, pod, oldPod) + } +} + +func TestWarnings(t *testing.T) { + resources := api.ResourceList{ + api.ResourceCPU: resource.MustParse("100m"), + api.ResourceMemory: resource.MustParse("4m"), + api.ResourceEphemeralStorage: resource.MustParse("4m"), + } + testcases := []struct { + name string + template *api.PodTemplateSpec + expected []string + }{ + { + name: "null", + template: nil, + expected: nil, + }, + { + name: "photon", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + Volumes: []api.Volume{ + {Name: "p", VolumeSource: api.VolumeSource{PhotonPersistentDisk: &api.PhotonPersistentDiskVolumeSource{}}}, + }}, + }, + expected: []string{`spec.volumes[0].photonPersistentDisk: non-functional in v1.16+`}, + }, + { + name: "duplicate hostAlias", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + HostAliases: []api.HostAlias{ + {IP: "1.1.1.1"}, + {IP: "1.1.1.1"}, + {IP: "1.1.1.1"}, + }}, + }, + expected: []string{ + `spec.hostAliases[1].ip: duplicate ip "1.1.1.1"`, + `spec.hostAliases[2].ip: duplicate ip "1.1.1.1"`, + }, + }, + { + name: "duplicate imagePullSecret", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + ImagePullSecrets: []api.LocalObjectReference{ + {Name: "a"}, + {Name: "a"}, + {Name: "a"}, + }}, + }, + expected: []string{ + `spec.imagePullSecrets[1].name: duplicate name "a"`, + `spec.imagePullSecrets[2].name: duplicate name "a"`, + }, + }, + { + name: "empty imagePullSecret", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + ImagePullSecrets: []api.LocalObjectReference{ + {Name: ""}, + }}, + }, + expected: []string{ + `spec.imagePullSecrets[0].name: invalid empty name ""`, + }, + }, + { + name: "duplicate volume", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + Volumes: []api.Volume{ + {Name: "a"}, + {Name: "a"}, + {Name: "a"}, + }}, + }, + expected: []string{ + `spec.volumes[1].name: duplicate name "a"`, + `spec.volumes[2].name: duplicate name "a"`, + }, + }, + { + name: "duplicate env", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + InitContainers: []api.Container{{Env: []api.EnvVar{ + {Name: "a"}, + {Name: "a"}, + {Name: "a"}, + }}}, + Containers: []api.Container{{Env: []api.EnvVar{ + {Name: "b"}, + {Name: "b"}, + {Name: "b"}, + }}}, + }}, + expected: []string{ + `spec.initContainers[0].env[1].name: duplicate name "a"`, + `spec.initContainers[0].env[2].name: duplicate name "a"`, + `spec.containers[0].env[1].name: duplicate name "b"`, + `spec.containers[0].env[2].name: duplicate name "b"`, + }, + }, + { + name: "fractional resources", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + InitContainers: []api.Container{{ + Resources: api.ResourceRequirements{Requests: resources, Limits: resources}, + }}, + Containers: []api.Container{{ + Resources: api.ResourceRequirements{Requests: resources, Limits: resources}, + }}, + Overhead: resources, + }}, + expected: []string{ + `spec.initContainers[0].resources.requests[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`, + `spec.initContainers[0].resources.requests[memory]: fractional byte value "4m" is invalid, must be an integer`, + `spec.initContainers[0].resources.limits[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`, + `spec.initContainers[0].resources.limits[memory]: fractional byte value "4m" is invalid, must be an integer`, + `spec.containers[0].resources.requests[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`, + `spec.containers[0].resources.requests[memory]: fractional byte value "4m" is invalid, must be an integer`, + `spec.containers[0].resources.limits[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`, + `spec.containers[0].resources.limits[memory]: fractional byte value "4m" is invalid, must be an integer`, + `spec.overhead[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`, + `spec.overhead[memory]: fractional byte value "4m" is invalid, must be an integer`, + }, + }, + { + name: "node labels in nodeSelector", + template: &api.PodTemplateSpec{Spec: api.PodSpec{ + NodeSelector: map[string]string{ + `beta.kubernetes.io/arch`: `true`, + `beta.kubernetes.io/os`: `true`, + `failure-domain.beta.kubernetes.io/region`: `true`, + `failure-domain.beta.kubernetes.io/zone`: `true`, + `beta.kubernetes.io/instance-type`: `true`, + }, + }}, + expected: []string{ + `spec.nodeSelector[beta.kubernetes.io/arch]: deprecated since v1.14; use "kubernetes.io/arch" instead`, + `spec.nodeSelector[beta.kubernetes.io/instance-type]: deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`, + `spec.nodeSelector[beta.kubernetes.io/os]: deprecated since v1.14; use "kubernetes.io/os" instead`, + `spec.nodeSelector[failure-domain.beta.kubernetes.io/region]: deprecated since v1.17; use "topology.kubernetes.io/region" instead`, + `spec.nodeSelector[failure-domain.beta.kubernetes.io/zone]: deprecated since v1.17; use "topology.kubernetes.io/zone" instead`, + }, + }, + { + name: "node labels in affinity requiredDuringSchedulingIgnoredDuringExecution", + template: &api.PodTemplateSpec{ + Spec: api.PodSpec{ + Affinity: &api.Affinity{ + NodeAffinity: &api.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{ + NodeSelectorTerms: []api.NodeSelectorTerm{ + { + MatchExpressions: []api.NodeSelectorRequirement{ + {Key: `foo`}, + {Key: `beta.kubernetes.io/arch`}, + {Key: `beta.kubernetes.io/os`}, + {Key: `failure-domain.beta.kubernetes.io/region`}, + {Key: `failure-domain.beta.kubernetes.io/zone`}, + {Key: `beta.kubernetes.io/instance-type`}, + }, + }, + }, + }, + }, + }, + }, + }, + expected: []string{ + `spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[1].key: beta.kubernetes.io/arch is deprecated since v1.14; use "kubernetes.io/arch" instead`, + `spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[2].key: beta.kubernetes.io/os is deprecated since v1.14; use "kubernetes.io/os" instead`, + `spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[3].key: failure-domain.beta.kubernetes.io/region is deprecated since v1.17; use "topology.kubernetes.io/region" instead`, + `spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[4].key: failure-domain.beta.kubernetes.io/zone is deprecated since v1.17; use "topology.kubernetes.io/zone" instead`, + `spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[5].key: beta.kubernetes.io/instance-type is deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`, + }, + }, + { + name: "node labels in affinity preferredDuringSchedulingIgnoredDuringExecution", + template: &api.PodTemplateSpec{ + Spec: api.PodSpec{ + Affinity: &api.Affinity{ + NodeAffinity: &api.NodeAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []api.PreferredSchedulingTerm{ + { + Preference: api.NodeSelectorTerm{ + MatchExpressions: []api.NodeSelectorRequirement{ + {Key: `foo`}, + {Key: `beta.kubernetes.io/arch`}, + {Key: `beta.kubernetes.io/os`}, + {Key: `failure-domain.beta.kubernetes.io/region`}, + {Key: `failure-domain.beta.kubernetes.io/zone`}, + {Key: `beta.kubernetes.io/instance-type`}, + }, + }, + }, + }, + }, + }, + }, + }, + expected: []string{ + `spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[1].key: beta.kubernetes.io/arch is deprecated since v1.14; use "kubernetes.io/arch" instead`, + `spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[2].key: beta.kubernetes.io/os is deprecated since v1.14; use "kubernetes.io/os" instead`, + `spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[3].key: failure-domain.beta.kubernetes.io/region is deprecated since v1.17; use "topology.kubernetes.io/region" instead`, + `spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[4].key: failure-domain.beta.kubernetes.io/zone is deprecated since v1.17; use "topology.kubernetes.io/zone" instead`, + `spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[5].key: beta.kubernetes.io/instance-type is deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`, + }, + }, + { + name: "node labels in topologySpreadConstraints", + template: &api.PodTemplateSpec{ + Spec: api.PodSpec{ + TopologySpreadConstraints: []api.TopologySpreadConstraint{ + {TopologyKey: `foo`}, + {TopologyKey: `beta.kubernetes.io/arch`}, + {TopologyKey: `beta.kubernetes.io/os`}, + {TopologyKey: `failure-domain.beta.kubernetes.io/region`}, + {TopologyKey: `failure-domain.beta.kubernetes.io/zone`}, + {TopologyKey: `beta.kubernetes.io/instance-type`}, + }, + }, + }, + expected: []string{ + `spec.topologySpreadConstraints[1].topologyKey: beta.kubernetes.io/arch is deprecated since v1.14; use "kubernetes.io/arch" instead`, + `spec.topologySpreadConstraints[2].topologyKey: beta.kubernetes.io/os is deprecated since v1.14; use "kubernetes.io/os" instead`, + `spec.topologySpreadConstraints[3].topologyKey: failure-domain.beta.kubernetes.io/region is deprecated since v1.17; use "topology.kubernetes.io/region" instead`, + `spec.topologySpreadConstraints[4].topologyKey: failure-domain.beta.kubernetes.io/zone is deprecated since v1.17; use "topology.kubernetes.io/zone" instead`, + `spec.topologySpreadConstraints[5].topologyKey: beta.kubernetes.io/instance-type is deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`, + }, + }, + { + name: "annotations", + template: &api.PodTemplateSpec{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{ + `foo`: `bar`, + `scheduler.alpha.kubernetes.io/critical-pod`: `true`, + `seccomp.security.alpha.kubernetes.io/pod`: `default`, + `container.seccomp.security.alpha.kubernetes.io/foo`: `default`, + `security.alpha.kubernetes.io/sysctls`: `a,b,c`, + `security.alpha.kubernetes.io/unsafe-sysctls`: `d,e,f`, + }}}, + expected: []string{ + `metadata.annotations[scheduler.alpha.kubernetes.io/critical-pod]: non-functional in v1.16+; use the "priorityClassName" field instead`, + `metadata.annotations[seccomp.security.alpha.kubernetes.io/pod]: deprecated since v1.19; use the "seccompProfile" field instead`, + `metadata.annotations[container.seccomp.security.alpha.kubernetes.io/foo]: deprecated since v1.19; use the "seccompProfile" field instead`, + `metadata.annotations[security.alpha.kubernetes.io/sysctls]: non-functional in v1.11+; use the "sysctls" field instead`, + `metadata.annotations[security.alpha.kubernetes.io/unsafe-sysctls]: non-functional in v1.11+; use the "sysctls" field instead`, + }, + }, + } + + for _, tc := range testcases { + t.Run("podspec_"+tc.name, func(t *testing.T) { + actual := sets.NewString(GetWarningsForPodTemplate(context.TODO(), nil, tc.template, &api.PodTemplateSpec{})...) + expected := sets.NewString(tc.expected...) + for _, missing := range expected.Difference(actual).List() { + t.Errorf("missing: %s", missing) + } + for _, extra := range actual.Difference(expected).List() { + t.Errorf("extra: %s", extra) + } + }) + + t.Run("pod_"+tc.name, func(t *testing.T) { + var pod *api.Pod + if tc.template != nil { + pod = &api.Pod{ + ObjectMeta: tc.template.ObjectMeta, + Spec: tc.template.Spec, + } + } + actual := sets.NewString(GetWarningsForPod(context.TODO(), pod, &api.Pod{})...) + expected := sets.NewString(tc.expected...) + for _, missing := range expected.Difference(actual).List() { + t.Errorf("missing: %s", missing) + } + for _, extra := range actual.Difference(expected).List() { + t.Errorf("extra: %s", extra) + } + }) + } +} diff --git a/pkg/registry/admissionregistration/mutatingwebhookconfiguration/strategy.go b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/strategy.go index 6f5ea4aeadb..e5bea2cf791 100644 --- a/pkg/registry/admissionregistration/mutatingwebhookconfiguration/strategy.go +++ b/pkg/registry/admissionregistration/mutatingwebhookconfiguration/strategy.go @@ -50,6 +50,11 @@ func (mutatingWebhookConfigurationStrategy) PrepareForCreate(ctx context.Context ic.Generation = 1 } +// WarningsOnCreate returns warnings for the creation of the given object. +func (mutatingWebhookConfigurationStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // PrepareForUpdate clears fields that are not allowed to be set by end users on update. func (mutatingWebhookConfigurationStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { newIC := obj.(*admissionregistration.MutatingWebhookConfiguration) @@ -93,6 +98,11 @@ func (mutatingWebhookConfigurationStrategy) ValidateUpdate(ctx context.Context, return validation.ValidateMutatingWebhookConfigurationUpdate(obj.(*admissionregistration.MutatingWebhookConfiguration), old.(*admissionregistration.MutatingWebhookConfiguration), groupVersion) } +// WarningsOnUpdate returns warnings for the given update. +func (mutatingWebhookConfigurationStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // AllowUnconditionalUpdate is the default update policy for mutatingWebhookConfiguration objects. Status update should // only be allowed if version match. func (mutatingWebhookConfigurationStrategy) AllowUnconditionalUpdate() bool { diff --git a/pkg/registry/admissionregistration/validatingwebhookconfiguration/strategy.go b/pkg/registry/admissionregistration/validatingwebhookconfiguration/strategy.go index 8f22af2b978..3b91fc6612b 100644 --- a/pkg/registry/admissionregistration/validatingwebhookconfiguration/strategy.go +++ b/pkg/registry/admissionregistration/validatingwebhookconfiguration/strategy.go @@ -73,6 +73,11 @@ func (validatingWebhookConfigurationStrategy) Validate(ctx context.Context, obj return validation.ValidateValidatingWebhookConfiguration(obj.(*admissionregistration.ValidatingWebhookConfiguration), groupVersion) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (validatingWebhookConfigurationStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (validatingWebhookConfigurationStrategy) Canonicalize(obj runtime.Object) { } @@ -92,6 +97,11 @@ func (validatingWebhookConfigurationStrategy) ValidateUpdate(ctx context.Context return validation.ValidateValidatingWebhookConfigurationUpdate(obj.(*admissionregistration.ValidatingWebhookConfiguration), old.(*admissionregistration.ValidatingWebhookConfiguration), groupVersion) } +// WarningsOnUpdate returns warnings for the given update. +func (validatingWebhookConfigurationStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // AllowUnconditionalUpdate is the default update policy for validatingWebhookConfiguration objects. Status update should // only be allowed if version match. func (validatingWebhookConfigurationStrategy) AllowUnconditionalUpdate() bool { diff --git a/pkg/registry/apiserverinternal/storageversion/strategy.go b/pkg/registry/apiserverinternal/storageversion/strategy.go index 535d31d142c..9fcf9a8d8ca 100644 --- a/pkg/registry/apiserverinternal/storageversion/strategy.go +++ b/pkg/registry/apiserverinternal/storageversion/strategy.go @@ -73,6 +73,11 @@ func (storageVersionStrategy) Validate(ctx context.Context, obj runtime.Object) return validation.ValidateStorageVersion(sv) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (storageVersionStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (storageVersionStrategy) Canonicalize(obj runtime.Object) { } @@ -90,6 +95,11 @@ func (storageVersionStrategy) ValidateUpdate(ctx context.Context, obj, old runti return validationErrorList } +// WarningsOnUpdate returns warnings for the given update. +func (storageVersionStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // AllowUnconditionalUpdate is the default update policy for storageVersion objects. Status update should // only be allowed if version match. func (storageVersionStrategy) AllowUnconditionalUpdate() bool { @@ -127,3 +137,8 @@ func (storageVersionStatusStrategy) PrepareForUpdate(ctx context.Context, obj, o func (storageVersionStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { return validation.ValidateStorageVersionStatusUpdate(obj.(*apiserverinternal.StorageVersion), old.(*apiserverinternal.StorageVersion)) } + +// WarningsOnUpdate returns warnings for the given update. +func (storageVersionStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} diff --git a/pkg/registry/apps/controllerrevision/strategy.go b/pkg/registry/apps/controllerrevision/strategy.go index a47f8587caa..250f0ed9eba 100644 --- a/pkg/registry/apps/controllerrevision/strategy.go +++ b/pkg/registry/apps/controllerrevision/strategy.go @@ -65,6 +65,9 @@ func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorLis return validation.ValidateControllerRevision(revision) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + func (strategy) PrepareForUpdate(ctx context.Context, newObj, oldObj runtime.Object) { _ = oldObj.(*apps.ControllerRevision) _ = newObj.(*apps.ControllerRevision) @@ -78,3 +81,6 @@ func (strategy) ValidateUpdate(ctx context.Context, newObj, oldObj runtime.Objec oldRevision, newRevision := oldObj.(*apps.ControllerRevision), newObj.(*apps.ControllerRevision) return validation.ValidateControllerRevisionUpdate(newRevision, oldRevision) } + +// WarningsOnUpdate returns warnings for the given update. +func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { return nil } diff --git a/pkg/registry/apps/daemonset/strategy.go b/pkg/registry/apps/daemonset/strategy.go index cd60ad02495..edcdd0423dc 100644 --- a/pkg/registry/apps/daemonset/strategy.go +++ b/pkg/registry/apps/daemonset/strategy.go @@ -166,6 +166,12 @@ func (daemonSetStrategy) Validate(ctx context.Context, obj runtime.Object) field return validation.ValidateDaemonSet(daemonSet, opts) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (daemonSetStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + newDaemonSet := obj.(*apps.DaemonSet) + return pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), &newDaemonSet.Spec.Template, nil) +} + // Canonicalize normalizes the object after validation. func (daemonSetStrategy) Canonicalize(obj runtime.Object) { } @@ -203,6 +209,17 @@ func (daemonSetStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Ob return allErrs } +// WarningsOnUpdate returns warnings for the given update. +func (daemonSetStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + var warnings []string + newDaemonSet := obj.(*apps.DaemonSet) + oldDaemonSet := old.(*apps.DaemonSet) + if newDaemonSet.Spec.TemplateGeneration != oldDaemonSet.Spec.TemplateGeneration { + warnings = pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), &newDaemonSet.Spec.Template, &oldDaemonSet.Spec.Template) + } + return warnings +} + // AllowUnconditionalUpdate is the default update policy for daemon set objects. func (daemonSetStrategy) AllowUnconditionalUpdate() bool { return true @@ -234,3 +251,8 @@ func (daemonSetStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old ru func (daemonSetStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { return validation.ValidateDaemonSetStatusUpdate(obj.(*apps.DaemonSet), old.(*apps.DaemonSet)) } + +// WarningsOnUpdate returns warnings for the given update. +func (daemonSetStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} diff --git a/pkg/registry/apps/deployment/strategy.go b/pkg/registry/apps/deployment/strategy.go index 15a115e09ab..43ca93f7d8e 100644 --- a/pkg/registry/apps/deployment/strategy.go +++ b/pkg/registry/apps/deployment/strategy.go @@ -96,6 +96,12 @@ func (deploymentStrategy) Validate(ctx context.Context, obj runtime.Object) fiel return validation.ValidateDeployment(deployment, opts) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (deploymentStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + newDeployment := obj.(*apps.Deployment) + return pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), &newDeployment.Spec.Template, nil) +} + // Canonicalize normalizes the object after validation. func (deploymentStrategy) Canonicalize(obj runtime.Object) { } @@ -149,6 +155,17 @@ func (deploymentStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.O return allErrs } +// WarningsOnUpdate returns warnings for the given update. +func (deploymentStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + var warnings []string + newDeployment := obj.(*apps.Deployment) + oldDeployment := old.(*apps.Deployment) + if newDeployment.Generation != oldDeployment.Generation { + warnings = pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), &newDeployment.Spec.Template, &oldDeployment.Spec.Template) + } + return warnings +} + func (deploymentStrategy) AllowUnconditionalUpdate() bool { return true } @@ -183,3 +200,8 @@ func (deploymentStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old r func (deploymentStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { return validation.ValidateDeploymentStatusUpdate(obj.(*apps.Deployment), old.(*apps.Deployment)) } + +// WarningsOnUpdate returns warnings for the given update. +func (deploymentStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} diff --git a/pkg/registry/apps/replicaset/strategy.go b/pkg/registry/apps/replicaset/strategy.go index 0f54eca3992..c8729d63cac 100644 --- a/pkg/registry/apps/replicaset/strategy.go +++ b/pkg/registry/apps/replicaset/strategy.go @@ -125,6 +125,12 @@ func (rsStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorL return validation.ValidateReplicaSet(rs, opts) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (rsStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + newRS := obj.(*apps.ReplicaSet) + return pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), &newRS.Spec.Template, nil) +} + // Canonicalize normalizes the object after validation. func (rsStrategy) Canonicalize(obj runtime.Object) { } @@ -162,6 +168,17 @@ func (rsStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) f return allErrs } +// WarningsOnUpdate returns warnings for the given update. +func (rsStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + var warnings []string + newReplicaSet := obj.(*apps.ReplicaSet) + oldReplicaSet := old.(*apps.ReplicaSet) + if newReplicaSet.Generation != oldReplicaSet.Generation { + warnings = pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), &newReplicaSet.Spec.Template, &oldReplicaSet.Spec.Template) + } + return warnings +} + func (rsStrategy) AllowUnconditionalUpdate() bool { return true } @@ -222,3 +239,8 @@ func (rsStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.O func (rsStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { return validation.ValidateReplicaSetStatusUpdate(obj.(*apps.ReplicaSet), old.(*apps.ReplicaSet)) } + +// WarningsOnUpdate returns warnings for the given update. +func (rsStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} diff --git a/pkg/registry/apps/statefulset/strategy.go b/pkg/registry/apps/statefulset/strategy.go index b718746415a..c122b3b30e2 100644 --- a/pkg/registry/apps/statefulset/strategy.go +++ b/pkg/registry/apps/statefulset/strategy.go @@ -113,6 +113,12 @@ func (statefulSetStrategy) Validate(ctx context.Context, obj runtime.Object) fie return validation.ValidateStatefulSet(statefulSet, opts) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (statefulSetStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + newStatefulSet := obj.(*apps.StatefulSet) + return pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), &newStatefulSet.Spec.Template, nil) +} + // Canonicalize normalizes the object after validation. func (statefulSetStrategy) Canonicalize(obj runtime.Object) { } @@ -133,6 +139,17 @@ func (statefulSetStrategy) ValidateUpdate(ctx context.Context, obj, old runtime. return append(validationErrorList, updateErrorList...) } +// WarningsOnUpdate returns warnings for the given update. +func (statefulSetStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + var warnings []string + newStatefulSet := obj.(*apps.StatefulSet) + oldStatefulSet := old.(*apps.StatefulSet) + if newStatefulSet.Generation != oldStatefulSet.Generation { + warnings = pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), &newStatefulSet.Spec.Template, &oldStatefulSet.Spec.Template) + } + return warnings +} + // AllowUnconditionalUpdate is the default update policy for StatefulSet objects. func (statefulSetStrategy) AllowUnconditionalUpdate() bool { return true @@ -168,3 +185,8 @@ func (statefulSetStatusStrategy) ValidateUpdate(ctx context.Context, obj, old ru // TODO: Validate status updates. return validation.ValidateStatefulSetStatusUpdate(obj.(*apps.StatefulSet), old.(*apps.StatefulSet)) } + +// WarningsOnUpdate returns warnings for the given update. +func (statefulSetStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} diff --git a/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go b/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go index edddda37dcb..f311181966a 100644 --- a/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go +++ b/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go @@ -81,6 +81,11 @@ func (autoscalerStrategy) Validate(ctx context.Context, obj runtime.Object) fiel return validation.ValidateHorizontalPodAutoscaler(autoscaler) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (autoscalerStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (autoscalerStrategy) Canonicalize(obj runtime.Object) { } @@ -123,6 +128,11 @@ func (autoscalerStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.O return validation.ValidateHorizontalPodAutoscalerUpdate(obj.(*autoscaling.HorizontalPodAutoscaler), old.(*autoscaling.HorizontalPodAutoscaler)) } +// WarningsOnUpdate returns warnings for the given update. +func (autoscalerStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (autoscalerStrategy) AllowUnconditionalUpdate() bool { return true } @@ -162,3 +172,8 @@ func (autoscalerStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old r func (autoscalerStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { return validation.ValidateHorizontalPodAutoscalerStatusUpdate(obj.(*autoscaling.HorizontalPodAutoscaler), old.(*autoscaling.HorizontalPodAutoscaler)) } + +// WarningsOnUpdate returns warnings for the given update. +func (autoscalerStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} diff --git a/pkg/registry/batch/cronjob/strategy.go b/pkg/registry/batch/cronjob/strategy.go index 46e7d5b6797..ce77a9ae557 100644 --- a/pkg/registry/batch/cronjob/strategy.go +++ b/pkg/registry/batch/cronjob/strategy.go @@ -20,6 +20,7 @@ import ( "context" batchv1beta1 "k8s.io/api/batch/v1beta1" + apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" @@ -83,6 +84,8 @@ func (cronJobStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) cronJob := obj.(*batch.CronJob) cronJob.Status = batch.CronJobStatus{} + cronJob.Generation = 1 + pod.DropDisabledTemplateFields(&cronJob.Spec.JobTemplate.Spec.Template, nil) } @@ -93,6 +96,12 @@ func (cronJobStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Ob newCronJob.Status = oldCronJob.Status pod.DropDisabledTemplateFields(&newCronJob.Spec.JobTemplate.Spec.Template, &oldCronJob.Spec.JobTemplate.Spec.Template) + + // Any changes to the spec increment the generation number. + // See metav1.ObjectMeta description for more information on Generation. + if !apiequality.Semantic.DeepEqual(newCronJob.Spec, oldCronJob.Spec) { + newCronJob.Generation = oldCronJob.Generation + 1 + } } // Validate validates a new scheduled job. @@ -102,6 +111,12 @@ func (cronJobStrategy) Validate(ctx context.Context, obj runtime.Object) field.E return validation.ValidateCronJob(cronJob, opts) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (cronJobStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + newCronJob := obj.(*batch.CronJob) + return pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "jobTemplate", "spec", "template"), &newCronJob.Spec.JobTemplate.Spec.Template, nil) +} + // Canonicalize normalizes the object after validation. func (cronJobStrategy) Canonicalize(obj runtime.Object) { } @@ -124,6 +139,17 @@ func (cronJobStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Obje return validation.ValidateCronJobUpdate(newCronJob, oldCronJob, opts) } +// WarningsOnUpdate returns warnings for the given update. +func (cronJobStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + var warnings []string + newCronJob := obj.(*batch.CronJob) + oldCronJob := old.(*batch.CronJob) + if newCronJob.Generation != oldCronJob.Generation { + warnings = pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "jobTemplate", "spec", "template"), &newCronJob.Spec.JobTemplate.Spec.Template, &oldCronJob.Spec.JobTemplate.Spec.Template) + } + return warnings +} + type cronJobStatusStrategy struct { cronJobStrategy } @@ -153,3 +179,8 @@ func (cronJobStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runt func (cronJobStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { return field.ErrorList{} } + +// WarningsOnUpdate returns warnings for the given update. +func (cronJobStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} diff --git a/pkg/registry/batch/cronjob/strategy_test.go b/pkg/registry/batch/cronjob/strategy_test.go index 5ba88b28e56..f33c5a2e429 100644 --- a/pkg/registry/batch/cronjob/strategy_test.go +++ b/pkg/registry/batch/cronjob/strategy_test.go @@ -44,8 +44,9 @@ func TestCronJobStrategy(t *testing.T) { } cronJob := &batch.CronJob{ ObjectMeta: metav1.ObjectMeta{ - Name: "mycronjob", - Namespace: metav1.NamespaceDefault, + Name: "mycronjob", + Namespace: metav1.NamespaceDefault, + Generation: 999, }, Spec: batch.CronJobSpec{ Schedule: "* * * * ?", @@ -62,11 +63,23 @@ func TestCronJobStrategy(t *testing.T) { if len(cronJob.Status.Active) != 0 { t.Errorf("CronJob does not allow setting status on create") } + if cronJob.Generation != 1 { + t.Errorf("expected Generation=1, got %d", cronJob.Generation) + } errs := Strategy.Validate(ctx, cronJob) if len(errs) != 0 { t.Errorf("Unexpected error validating %v", errs) } now := metav1.Now() + + // ensure we do not change generation for non-spec updates + updatedLabelCronJob := cronJob.DeepCopy() + updatedLabelCronJob.Labels = map[string]string{"a": "true"} + Strategy.PrepareForUpdate(ctx, updatedLabelCronJob, cronJob) + if updatedLabelCronJob.Generation != 1 { + t.Errorf("expected Generation=1, got %d", updatedLabelCronJob.Generation) + } + updatedCronJob := &batch.CronJob{ ObjectMeta: metav1.ObjectMeta{Name: "bar", ResourceVersion: "4"}, Spec: batch.CronJobSpec{ @@ -82,6 +95,9 @@ func TestCronJobStrategy(t *testing.T) { if updatedCronJob.Status.Active != nil { t.Errorf("PrepareForUpdate should have preserved prior version status") } + if updatedCronJob.Generation != 2 { + t.Errorf("expected Generation=2, got %d", updatedCronJob.Generation) + } errs = Strategy.ValidateUpdate(ctx, updatedCronJob, cronJob) if len(errs) == 0 { t.Errorf("Expected a validation error") diff --git a/pkg/registry/batch/job/strategy.go b/pkg/registry/batch/job/strategy.go index 6aa8ec1548f..ead85e8ffd1 100644 --- a/pkg/registry/batch/job/strategy.go +++ b/pkg/registry/batch/job/strategy.go @@ -22,6 +22,7 @@ import ( "strconv" batchv1 "k8s.io/api/batch/v1" + apiequality "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" @@ -89,6 +90,8 @@ func (jobStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { job := obj.(*batch.Job) job.Status = batch.JobStatus{} + job.Generation = 1 + if !utilfeature.DefaultFeatureGate.Enabled(features.TTLAfterFinished) { job.Spec.TTLSecondsAfterFinished = nil } @@ -129,6 +132,12 @@ func (jobStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object } pod.DropDisabledTemplateFields(&newJob.Spec.Template, &oldJob.Spec.Template) + + // Any changes to the spec increment the generation number. + // See metav1.ObjectMeta description for more information on Generation. + if !apiequality.Semantic.DeepEqual(newJob.Spec, oldJob.Spec) { + newJob.Generation = oldJob.Generation + 1 + } } // Validate validates a new job. @@ -142,6 +151,12 @@ func (jobStrategy) Validate(ctx context.Context, obj runtime.Object) field.Error return validation.ValidateJob(job, opts) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (jobStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + newJob := obj.(*batch.Job) + return pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), &newJob.Spec.Template, nil) +} + // generateSelector adds a selector to a job and labels to its template // which can be used to uniquely identify the pods created by that job, // if the user has requested this behavior. @@ -216,6 +231,17 @@ func (jobStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) return append(validationErrorList, updateErrorList...) } +// WarningsOnUpdate returns warnings for the given update. +func (jobStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + var warnings []string + newJob := obj.(*batch.Job) + oldJob := old.(*batch.Job) + if newJob.Generation != oldJob.Generation { + warnings = pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), &newJob.Spec.Template, &oldJob.Spec.Template) + } + return warnings +} + type jobStatusStrategy struct { jobStrategy } @@ -242,6 +268,11 @@ func (jobStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Ob return validation.ValidateJobUpdateStatus(obj.(*batch.Job), old.(*batch.Job)) } +// WarningsOnUpdate returns warnings for the given update. +func (jobStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // JobSelectableFields returns a field set that represents the object for matching purposes. func JobToSelectableFields(job *batch.Job) fields.Set { objectMetaFieldsSet := generic.ObjectMetaFieldsSet(&job.ObjectMeta, true) diff --git a/pkg/registry/batch/job/strategy_test.go b/pkg/registry/batch/job/strategy_test.go index 00f0082015d..d0852b55a1a 100644 --- a/pkg/registry/batch/job/strategy_test.go +++ b/pkg/registry/batch/job/strategy_test.go @@ -110,6 +110,9 @@ func testJobStrategy(t *testing.T) { if job.Status.Active != 0 { t.Errorf("Job does not allow setting status on create") } + if job.Generation != 1 { + t.Errorf("expected Generation=1, got %d", job.Generation) + } errs := Strategy.Validate(ctx, job) if len(errs) != 0 { t.Errorf("Unexpected error validating %v", errs) @@ -125,6 +128,15 @@ func testJobStrategy(t *testing.T) { } parallelism := int32(10) + + // ensure we do not change generation for non-spec updates + updatedLabelJob := job.DeepCopy() + updatedLabelJob.Labels = map[string]string{"a": "true"} + Strategy.PrepareForUpdate(ctx, updatedLabelJob, job) + if updatedLabelJob.Generation != 1 { + t.Errorf("expected Generation=1, got %d", updatedLabelJob.Generation) + } + updatedJob := &batch.Job{ ObjectMeta: metav1.ObjectMeta{Name: "bar", ResourceVersion: "4"}, Spec: batch.JobSpec{ @@ -144,6 +156,9 @@ func testJobStrategy(t *testing.T) { if updatedJob.Status.Active != 10 { t.Errorf("PrepareForUpdate should have preserved prior version status") } + if updatedJob.Generation != 2 { + t.Errorf("expected Generation=2, got %d", updatedJob.Generation) + } if ttlEnabled != (updatedJob.Spec.TTLSecondsAfterFinished != nil) { t.Errorf("Job should only allow updating .spec.ttlSecondsAfterFinished when %v feature is enabled", features.TTLAfterFinished) } diff --git a/pkg/registry/certificates/certificates/strategy.go b/pkg/registry/certificates/certificates/strategy.go index c88ff0fdce9..076f5a59442 100644 --- a/pkg/registry/certificates/certificates/strategy.go +++ b/pkg/registry/certificates/certificates/strategy.go @@ -117,6 +117,9 @@ func (csrStrategy) Validate(ctx context.Context, obj runtime.Object) field.Error return validation.ValidateCertificateSigningRequestCreate(csr, requestGroupVersion(ctx)) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (csrStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + // Canonicalize normalizes the object after validation (which includes a signature check). func (csrStrategy) Canonicalize(obj runtime.Object) {} @@ -127,6 +130,11 @@ func (csrStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) return validation.ValidateCertificateSigningRequestUpdate(newCSR, oldCSR, requestGroupVersion(ctx)) } +// WarningsOnUpdate returns warnings for the given update. +func (csrStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // If AllowUnconditionalUpdate() is true and the object specified by // the user does not have a resource version, then generic Update() // populates it with the latest version. Else, it checks that the @@ -244,6 +252,11 @@ func (csrStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Ob return validation.ValidateCertificateSigningRequestStatusUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest), requestGroupVersion(ctx)) } +// WarningsOnUpdate returns warnings for the given update. +func (csrStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (csrStatusStrategy) Canonicalize(obj runtime.Object) { } @@ -291,6 +304,11 @@ func (csrApprovalStrategy) ValidateUpdate(ctx context.Context, obj, old runtime. return validation.ValidateCertificateSigningRequestApprovalUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest), requestGroupVersion(ctx)) } +// WarningsOnUpdate returns warnings for the given update. +func (csrApprovalStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // GetAttrs returns labels and fields of a given object for filtering purposes. func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { csr, ok := obj.(*certificates.CertificateSigningRequest) diff --git a/pkg/registry/coordination/lease/strategy.go b/pkg/registry/coordination/lease/strategy.go index dd3df56e08c..042a3a9d7f8 100644 --- a/pkg/registry/coordination/lease/strategy.go +++ b/pkg/registry/coordination/lease/strategy.go @@ -55,6 +55,9 @@ func (leaseStrategy) Validate(ctx context.Context, obj runtime.Object) field.Err return validation.ValidateLease(lease) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (leaseStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + // Canonicalize normalizes the object after validation. func (leaseStrategy) Canonicalize(obj runtime.Object) { } @@ -69,6 +72,11 @@ func (leaseStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object return validation.ValidateLeaseUpdate(obj.(*coordination.Lease), old.(*coordination.Lease)) } +// WarningsOnUpdate returns warnings for the given update. +func (leaseStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // AllowUnconditionalUpdate is the default update policy for Lease objects. func (leaseStrategy) AllowUnconditionalUpdate() bool { return false diff --git a/pkg/registry/core/configmap/strategy.go b/pkg/registry/core/configmap/strategy.go index 12f021ce40c..a78bc5d4cec 100644 --- a/pkg/registry/core/configmap/strategy.go +++ b/pkg/registry/core/configmap/strategy.go @@ -64,6 +64,9 @@ func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorLis return validation.ValidateConfigMap(cfg) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + // Canonicalize normalizes the object after validation. func (strategy) Canonicalize(obj runtime.Object) { } @@ -84,6 +87,9 @@ func (strategy) ValidateUpdate(ctx context.Context, newObj, oldObj runtime.Objec return validation.ValidateConfigMapUpdate(newCfg, oldCfg) } +// WarningsOnUpdate returns warnings for the given update. +func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { return nil } + func dropDisabledFields(configMap *api.ConfigMap, oldConfigMap *api.ConfigMap) { } diff --git a/pkg/registry/core/endpoint/strategy.go b/pkg/registry/core/endpoint/strategy.go index 2d1c7501917..3e0f5d30218 100644 --- a/pkg/registry/core/endpoint/strategy.go +++ b/pkg/registry/core/endpoint/strategy.go @@ -55,6 +55,11 @@ func (endpointsStrategy) Validate(ctx context.Context, obj runtime.Object) field return validation.ValidateEndpointsCreate(obj.(*api.Endpoints)) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (endpointsStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (endpointsStrategy) Canonicalize(obj runtime.Object) { } @@ -69,6 +74,11 @@ func (endpointsStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Ob return validation.ValidateEndpointsUpdate(obj.(*api.Endpoints), old.(*api.Endpoints)) } +// WarningsOnUpdate returns warnings for the given update. +func (endpointsStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (endpointsStrategy) AllowUnconditionalUpdate() bool { return true } diff --git a/pkg/registry/core/event/strategy.go b/pkg/registry/core/event/strategy.go index bc57da36809..634bb71845c 100644 --- a/pkg/registry/core/event/strategy.go +++ b/pkg/registry/core/event/strategy.go @@ -64,6 +64,9 @@ func (eventStrategy) Validate(ctx context.Context, obj runtime.Object) field.Err return validation.ValidateEventCreate(event, groupVersion) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (eventStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + // Canonicalize normalizes the object after validation. func (eventStrategy) Canonicalize(obj runtime.Object) { } @@ -79,6 +82,11 @@ func (eventStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object return validation.ValidateEventUpdate(event, oldEvent, groupVersion) } +// WarningsOnUpdate returns warnings for the given update. +func (eventStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (eventStrategy) AllowUnconditionalUpdate() bool { return true } diff --git a/pkg/registry/core/limitrange/strategy.go b/pkg/registry/core/limitrange/strategy.go index d9af7051a37..479d958ca78 100644 --- a/pkg/registry/core/limitrange/strategy.go +++ b/pkg/registry/core/limitrange/strategy.go @@ -56,6 +56,11 @@ func (limitrangeStrategy) Validate(ctx context.Context, obj runtime.Object) fiel return validation.ValidateLimitRange(limitRange) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (limitrangeStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (limitrangeStrategy) Canonicalize(obj runtime.Object) { } @@ -69,6 +74,11 @@ func (limitrangeStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.O return validation.ValidateLimitRange(limitRange) } +// WarningsOnUpdate returns warnings for the given update. +func (limitrangeStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (limitrangeStrategy) AllowUnconditionalUpdate() bool { return true } diff --git a/pkg/registry/core/namespace/strategy.go b/pkg/registry/core/namespace/strategy.go index 1dc84284f7f..9147ada3637 100644 --- a/pkg/registry/core/namespace/strategy.go +++ b/pkg/registry/core/namespace/strategy.go @@ -100,6 +100,11 @@ func (namespaceStrategy) Validate(ctx context.Context, obj runtime.Object) field return validation.ValidateNamespace(namespace) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (namespaceStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (namespaceStrategy) Canonicalize(obj runtime.Object) { // Ensure the label matches the name for namespaces just created using GenerateName, @@ -139,6 +144,11 @@ func (namespaceStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Ob return append(errorList, validation.ValidateNamespaceUpdate(obj.(*api.Namespace), old.(*api.Namespace))...) } +// WarningsOnUpdate returns warnings for the given update. +func (namespaceStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (namespaceStrategy) AllowUnconditionalUpdate() bool { return true } @@ -169,6 +179,11 @@ func (namespaceStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runt return validation.ValidateNamespaceStatusUpdate(obj.(*api.Namespace), old.(*api.Namespace)) } +// WarningsOnUpdate returns warnings for the given update. +func (namespaceStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + type namespaceFinalizeStrategy struct { namespaceStrategy } @@ -179,6 +194,11 @@ func (namespaceFinalizeStrategy) ValidateUpdate(ctx context.Context, obj, old ru return validation.ValidateNamespaceFinalizeUpdate(obj.(*api.Namespace), old.(*api.Namespace)) } +// WarningsOnUpdate returns warnings for the given update. +func (namespaceFinalizeStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // GetResetFields returns the set of fields that get reset by the strategy // and should not be modified by the user. func (namespaceFinalizeStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { diff --git a/pkg/registry/core/node/strategy.go b/pkg/registry/core/node/strategy.go index 2fe255155bc..e846b74a345 100644 --- a/pkg/registry/core/node/strategy.go +++ b/pkg/registry/core/node/strategy.go @@ -140,6 +140,9 @@ func (nodeStrategy) Validate(ctx context.Context, obj runtime.Object) field.Erro return validation.ValidateNode(node) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (nodeStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + // Canonicalize normalizes the object after validation. func (nodeStrategy) Canonicalize(obj runtime.Object) { } @@ -150,6 +153,11 @@ func (nodeStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) return append(errorList, validation.ValidateNodeUpdate(obj.(*api.Node), old.(*api.Node))...) } +// WarningsOnUpdate returns warnings for the given update. +func (nodeStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (nodeStrategy) AllowUnconditionalUpdate() bool { return true } @@ -197,6 +205,11 @@ func (nodeStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.O return validation.ValidateNodeUpdate(obj.(*api.Node), old.(*api.Node)) } +// WarningsOnUpdate returns warnings for the given update. +func (nodeStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (nodeStatusStrategy) Canonicalize(obj runtime.Object) { } diff --git a/pkg/registry/core/persistentvolume/strategy.go b/pkg/registry/core/persistentvolume/strategy.go index 2cabf394621..6a8f805df4f 100644 --- a/pkg/registry/core/persistentvolume/strategy.go +++ b/pkg/registry/core/persistentvolume/strategy.go @@ -75,6 +75,11 @@ func (persistentvolumeStrategy) Validate(ctx context.Context, obj runtime.Object return append(errorList, volumevalidation.ValidatePersistentVolume(persistentvolume)...) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (persistentvolumeStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (persistentvolumeStrategy) Canonicalize(obj runtime.Object) { } @@ -99,6 +104,11 @@ func (persistentvolumeStrategy) ValidateUpdate(ctx context.Context, obj, old run return append(errorList, validation.ValidatePersistentVolumeUpdate(newPv, old.(*api.PersistentVolume))...) } +// WarningsOnUpdate returns warnings for the given update. +func (persistentvolumeStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (persistentvolumeStrategy) AllowUnconditionalUpdate() bool { return true } @@ -132,6 +142,11 @@ func (persistentvolumeStatusStrategy) ValidateUpdate(ctx context.Context, obj, o return validation.ValidatePersistentVolumeStatusUpdate(obj.(*api.PersistentVolume), old.(*api.PersistentVolume)) } +// WarningsOnUpdate returns warnings for the given update. +func (persistentvolumeStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // GetAttrs returns labels and fields of a given object for filtering purposes. func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { persistentvolumeObj, ok := obj.(*api.PersistentVolume) diff --git a/pkg/registry/core/persistentvolumeclaim/strategy.go b/pkg/registry/core/persistentvolumeclaim/strategy.go index 23b7acb3c9d..688db9cd9f8 100644 --- a/pkg/registry/core/persistentvolumeclaim/strategy.go +++ b/pkg/registry/core/persistentvolumeclaim/strategy.go @@ -75,6 +75,11 @@ func (persistentvolumeclaimStrategy) Validate(ctx context.Context, obj runtime.O return validation.ValidatePersistentVolumeClaim(pvc) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (persistentvolumeclaimStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (persistentvolumeclaimStrategy) Canonicalize(obj runtime.Object) { } @@ -97,6 +102,11 @@ func (persistentvolumeclaimStrategy) ValidateUpdate(ctx context.Context, obj, ol return append(errorList, validation.ValidatePersistentVolumeClaimUpdate(obj.(*api.PersistentVolumeClaim), old.(*api.PersistentVolumeClaim))...) } +// WarningsOnUpdate returns warnings for the given update. +func (persistentvolumeclaimStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (persistentvolumeclaimStrategy) AllowUnconditionalUpdate() bool { return true } @@ -133,6 +143,11 @@ func (persistentvolumeclaimStatusStrategy) ValidateUpdate(ctx context.Context, o return validation.ValidatePersistentVolumeClaimStatusUpdate(obj.(*api.PersistentVolumeClaim), old.(*api.PersistentVolumeClaim)) } +// WarningsOnUpdate returns warnings for the given update. +func (persistentvolumeclaimStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // GetAttrs returns labels and fields of a given object for filtering purposes. func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { persistentvolumeclaimObj, ok := obj.(*api.PersistentVolumeClaim) diff --git a/pkg/registry/core/pod/strategy.go b/pkg/registry/core/pod/strategy.go index 0f411af7128..89f07712394 100644 --- a/pkg/registry/core/pod/strategy.go +++ b/pkg/registry/core/pod/strategy.go @@ -107,6 +107,11 @@ func (podStrategy) Validate(ctx context.Context, obj runtime.Object) field.Error return validation.ValidatePodCreate(pod, opts) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (podStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return podutil.GetWarningsForPod(ctx, obj.(*api.Pod), nil) +} + // Canonicalize normalizes the object after validation. func (podStrategy) Canonicalize(obj runtime.Object) { } @@ -125,6 +130,13 @@ func (podStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) return validation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod), opts) } +// WarningsOnUpdate returns warnings for the given update. +func (podStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + // skip warnings on pod update, since humans don't typically interact directly with pods, + // and we don't want to pay the evaluation cost on what might be a high-frequency update path + return nil +} + // AllowUnconditionalUpdate allows pods to be overwritten func (podStrategy) AllowUnconditionalUpdate() bool { return true @@ -198,6 +210,11 @@ func (podStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Ob return validation.ValidatePodStatusUpdate(obj.(*api.Pod), old.(*api.Pod), opts) } +// WarningsOnUpdate returns warnings for the given update. +func (podStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + type podEphemeralContainersStrategy struct { podStrategy } @@ -231,6 +248,11 @@ func (podEphemeralContainersStrategy) ValidateUpdate(ctx context.Context, obj, o return validation.ValidatePodEphemeralContainersUpdate(newPod, oldPod, opts) } +// WarningsOnUpdate returns warnings for the given update. +func (podEphemeralContainersStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // GetAttrs returns labels and fields of a given object for filtering purposes. func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { pod, ok := obj.(*api.Pod) diff --git a/pkg/registry/core/podtemplate/strategy.go b/pkg/registry/core/podtemplate/strategy.go index 1b26dfab351..24cdb6438a9 100644 --- a/pkg/registry/core/podtemplate/strategy.go +++ b/pkg/registry/core/podtemplate/strategy.go @@ -19,6 +19,7 @@ package podtemplate import ( "context" + apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/storage/names" @@ -46,7 +47,7 @@ func (podTemplateStrategy) NamespaceScoped() bool { // PrepareForCreate clears fields that are not allowed to be set by end users on creation. func (podTemplateStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { template := obj.(*api.PodTemplate) - + template.Generation = 1 pod.DropDisabledTemplateFields(&template.Template, nil) } @@ -57,6 +58,12 @@ func (podTemplateStrategy) Validate(ctx context.Context, obj runtime.Object) fie return corevalidation.ValidatePodTemplate(template, opts) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (podTemplateStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + newPodTemplate := obj.(*api.PodTemplate) + return pod.GetWarningsForPodTemplate(ctx, field.NewPath("template"), &newPodTemplate.Template, nil) +} + // Canonicalize normalizes the object after validation. func (podTemplateStrategy) Canonicalize(obj runtime.Object) { } @@ -72,6 +79,13 @@ func (podTemplateStrategy) PrepareForUpdate(ctx context.Context, obj, old runtim oldTemplate := old.(*api.PodTemplate) pod.DropDisabledTemplateFields(&newTemplate.Template, &oldTemplate.Template) + + // Any changes to the template increment the generation number. + // See metav1.ObjectMeta description for more information on Generation. + if !apiequality.Semantic.DeepEqual(newTemplate.Template, oldTemplate.Template) { + newTemplate.Generation = oldTemplate.Generation + 1 + } + } // ValidateUpdate is the default update validation for an end user. @@ -84,6 +98,17 @@ func (podTemplateStrategy) ValidateUpdate(ctx context.Context, obj, old runtime. return corevalidation.ValidatePodTemplateUpdate(template, oldTemplate, opts) } +// WarningsOnUpdate returns warnings for the given update. +func (podTemplateStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + var warnings []string + newTemplate := obj.(*api.PodTemplate) + oldTemplate := old.(*api.PodTemplate) + if newTemplate.Generation != oldTemplate.Generation { + warnings = pod.GetWarningsForPodTemplate(ctx, field.NewPath("template"), &newTemplate.Template, &oldTemplate.Template) + } + return warnings +} + func (podTemplateStrategy) AllowUnconditionalUpdate() bool { return true } diff --git a/pkg/registry/core/podtemplate/strategy_test.go b/pkg/registry/core/podtemplate/strategy_test.go new file mode 100644 index 00000000000..7ad0f5634d2 --- /dev/null +++ b/pkg/registry/core/podtemplate/strategy_test.go @@ -0,0 +1,90 @@ +/* +Copyright 2021 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 podtemplate + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + api "k8s.io/kubernetes/pkg/apis/core" +) + +func TestStrategy(t *testing.T) { + ctx := genericapirequest.NewDefaultContext() + if !Strategy.NamespaceScoped() { + t.Errorf("must be namespace scoped") + } + if Strategy.AllowCreateOnUpdate() { + t.Errorf("should not allow create on update") + } + + podTemplate := &api.PodTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mytemplate", + Namespace: metav1.NamespaceDefault, + Generation: 999, + }, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyOnFailure, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, + }, + }, + } + + Strategy.PrepareForCreate(ctx, podTemplate) + if podTemplate.Generation != 1 { + t.Errorf("expected Generation=1, got %d", podTemplate.Generation) + } + errs := Strategy.Validate(ctx, podTemplate) + if len(errs) != 0 { + t.Errorf("Unexpected error validating %v", errs) + } + + // ensure we do not change generation for non-spec updates + updatedLabel := podTemplate.DeepCopy() + updatedLabel.Labels = map[string]string{"a": "true"} + Strategy.PrepareForUpdate(ctx, updatedLabel, podTemplate) + if updatedLabel.Generation != 1 { + t.Errorf("expected Generation=1, got %d", updatedLabel.Generation) + } + + updatedTemplate := podTemplate.DeepCopy() + updatedTemplate.ResourceVersion = "10" + updatedTemplate.Generation = 999 + updatedTemplate.Template.Spec.RestartPolicy = api.RestartPolicyNever + + // ensure generation is updated for spec changes + Strategy.PrepareForUpdate(ctx, updatedTemplate, podTemplate) + if updatedTemplate.Generation != 2 { + t.Errorf("expected Generation=2, got %d", updatedTemplate.Generation) + } + errs = Strategy.ValidateUpdate(ctx, updatedTemplate, podTemplate) + if len(errs) != 0 { + t.Errorf("Unexpected error validating %v", errs) + } + + invalidUpdatedTemplate := updatedTemplate.DeepCopy() + invalidUpdatedTemplate.Name = "changed" + Strategy.PrepareForUpdate(ctx, invalidUpdatedTemplate, podTemplate) + errs = Strategy.ValidateUpdate(ctx, invalidUpdatedTemplate, podTemplate) + if len(errs) == 0 { + t.Errorf("expected error validating, got none") + } +} diff --git a/pkg/registry/core/replicationcontroller/strategy.go b/pkg/registry/core/replicationcontroller/strategy.go index 3722365cd43..be056a32cfe 100644 --- a/pkg/registry/core/replicationcontroller/strategy.go +++ b/pkg/registry/core/replicationcontroller/strategy.go @@ -125,6 +125,12 @@ func (rcStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorL return validation.ValidateReplicationController(controller, opts) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (rcStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + newRC := obj.(*api.ReplicationController) + return pod.GetWarningsForPodTemplate(ctx, field.NewPath("template"), newRC.Spec.Template, nil) +} + // Canonicalize normalizes the object after validation. func (rcStrategy) Canonicalize(obj runtime.Object) { } @@ -165,6 +171,17 @@ func (rcStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) f return errs } +// WarningsOnUpdate returns warnings for the given update. +func (rcStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + var warnings []string + oldRc := old.(*api.ReplicationController) + newRc := obj.(*api.ReplicationController) + if oldRc.Generation != newRc.Generation { + warnings = pod.GetWarningsForPodTemplate(ctx, field.NewPath("spec", "template"), oldRc.Spec.Template, newRc.Spec.Template) + } + return warnings +} + func (rcStrategy) AllowUnconditionalUpdate() bool { return true } @@ -225,3 +242,8 @@ func (rcStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.O func (rcStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { return validation.ValidateReplicationControllerStatusUpdate(obj.(*api.ReplicationController), old.(*api.ReplicationController)) } + +// WarningsOnUpdate returns warnings for the given update. +func (rcStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} diff --git a/pkg/registry/core/resourcequota/strategy.go b/pkg/registry/core/resourcequota/strategy.go index 63b3f6089d4..3a384d521e9 100644 --- a/pkg/registry/core/resourcequota/strategy.go +++ b/pkg/registry/core/resourcequota/strategy.go @@ -77,6 +77,11 @@ func (resourcequotaStrategy) Validate(ctx context.Context, obj runtime.Object) f return validation.ValidateResourceQuota(resourcequota, opts) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (resourcequotaStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (resourcequotaStrategy) Canonicalize(obj runtime.Object) { } @@ -93,6 +98,11 @@ func (resourcequotaStrategy) ValidateUpdate(ctx context.Context, obj, old runtim return validation.ValidateResourceQuotaUpdate(newObj, oldObj, opts) } +// WarningsOnUpdate returns warnings for the given update. +func (resourcequotaStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (resourcequotaStrategy) AllowUnconditionalUpdate() bool { return true } @@ -126,6 +136,11 @@ func (resourcequotaStatusStrategy) ValidateUpdate(ctx context.Context, obj, old return validation.ValidateResourceQuotaStatusUpdate(obj.(*api.ResourceQuota), old.(*api.ResourceQuota)) } +// WarningsOnUpdate returns warnings for the given update. +func (resourcequotaStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func getValidationOptionsFromResourceQuota(newObj *api.ResourceQuota, oldObj *api.ResourceQuota) validation.ResourceQuotaValidationOptions { opts := validation.ResourceQuotaValidationOptions{ AllowPodAffinityNamespaceSelector: utilfeature.DefaultFeatureGate.Enabled(features.PodAffinityNamespaceSelector), diff --git a/pkg/registry/core/secret/strategy.go b/pkg/registry/core/secret/strategy.go index 2970c32f3dd..33397044499 100644 --- a/pkg/registry/core/secret/strategy.go +++ b/pkg/registry/core/secret/strategy.go @@ -60,6 +60,9 @@ func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorLis return validation.ValidateSecret(obj.(*api.Secret)) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + func (strategy) Canonicalize(obj runtime.Object) { } @@ -83,6 +86,11 @@ func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) fie return validation.ValidateSecretUpdate(obj.(*api.Secret), old.(*api.Secret)) } +// WarningsOnUpdate returns warnings for the given update. +func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func dropDisabledFields(secret *api.Secret, oldSecret *api.Secret) { } diff --git a/pkg/registry/core/service/strategy.go b/pkg/registry/core/service/strategy.go index db60cc2290f..1a6da3b27bf 100644 --- a/pkg/registry/core/service/strategy.go +++ b/pkg/registry/core/service/strategy.go @@ -133,6 +133,9 @@ func (strategy svcStrategy) Validate(ctx context.Context, obj runtime.Object) fi return allErrs } +// WarningsOnCreate returns warnings for the creation of the given object. +func (svcStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + // Canonicalize normalizes the object after validation. func (svcStrategy) Canonicalize(obj runtime.Object) { } @@ -147,6 +150,11 @@ func (strategy svcStrategy) ValidateUpdate(ctx context.Context, obj, old runtime return allErrs } +// WarningsOnUpdate returns warnings for the given update. +func (svcStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (svcStrategy) AllowUnconditionalUpdate() bool { return true } @@ -302,6 +310,11 @@ func (serviceStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtim return validation.ValidateServiceStatusUpdate(obj.(*api.Service), old.(*api.Service)) } +// WarningsOnUpdate returns warnings for the given update. +func (serviceStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // NormalizeClusterIPs adjust clusterIPs based on ClusterIP. This must not // consider any other fields. func NormalizeClusterIPs(oldSvc, newSvc *api.Service) { diff --git a/pkg/registry/core/serviceaccount/strategy.go b/pkg/registry/core/serviceaccount/strategy.go index a37826dd89b..cad6da26fb1 100644 --- a/pkg/registry/core/serviceaccount/strategy.go +++ b/pkg/registry/core/serviceaccount/strategy.go @@ -49,6 +49,9 @@ func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorLis return validation.ValidateServiceAccount(obj.(*api.ServiceAccount)) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + // Canonicalize normalizes the object after validation. func (strategy) Canonicalize(obj runtime.Object) { } @@ -71,6 +74,11 @@ func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) fie return validation.ValidateServiceAccountUpdate(obj.(*api.ServiceAccount), old.(*api.ServiceAccount)) } +// WarningsOnUpdate returns warnings for the given update. +func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (strategy) AllowUnconditionalUpdate() bool { return true } diff --git a/pkg/registry/discovery/endpointslice/strategy.go b/pkg/registry/discovery/endpointslice/strategy.go index c5e20b64bc2..37ac9dd4663 100644 --- a/pkg/registry/discovery/endpointslice/strategy.go +++ b/pkg/registry/discovery/endpointslice/strategy.go @@ -87,6 +87,11 @@ func (endpointSliceStrategy) Validate(ctx context.Context, obj runtime.Object) f return err } +// WarningsOnCreate returns warnings for the creation of the given object. +func (endpointSliceStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (endpointSliceStrategy) Canonicalize(obj runtime.Object) { } @@ -103,6 +108,11 @@ func (endpointSliceStrategy) ValidateUpdate(ctx context.Context, new, old runtim return validation.ValidateEndpointSliceUpdate(newEPS, oldEPS) } +// WarningsOnUpdate returns warnings for the given update. +func (endpointSliceStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // AllowUnconditionalUpdate is the default update policy for EndpointSlice objects. func (endpointSliceStrategy) AllowUnconditionalUpdate() bool { return true diff --git a/pkg/registry/flowcontrol/flowschema/strategy.go b/pkg/registry/flowcontrol/flowschema/strategy.go index fd18476dc77..a2bb4ee316e 100644 --- a/pkg/registry/flowcontrol/flowschema/strategy.go +++ b/pkg/registry/flowcontrol/flowschema/strategy.go @@ -82,6 +82,11 @@ func (flowSchemaStrategy) Validate(ctx context.Context, obj runtime.Object) fiel return validation.ValidateFlowSchema(obj.(*flowcontrol.FlowSchema)) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (flowSchemaStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (flowSchemaStrategy) Canonicalize(obj runtime.Object) { } @@ -100,6 +105,11 @@ func (flowSchemaStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.O return validation.ValidateFlowSchemaUpdate(old.(*flowcontrol.FlowSchema), obj.(*flowcontrol.FlowSchema)) } +// WarningsOnUpdate returns warnings for the given update. +func (flowSchemaStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + type flowSchemaStatusStrategy struct { flowSchemaStrategy } @@ -139,3 +149,8 @@ func (flowSchemaStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old r func (flowSchemaStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { return validation.ValidateFlowSchemaStatusUpdate(old.(*flowcontrol.FlowSchema), obj.(*flowcontrol.FlowSchema)) } + +// WarningsOnUpdate returns warnings for the given update. +func (flowSchemaStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} diff --git a/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go b/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go index 54208747eaa..eb5a789badf 100644 --- a/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go +++ b/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go @@ -82,6 +82,11 @@ func (priorityLevelConfigurationStrategy) Validate(ctx context.Context, obj runt return validation.ValidatePriorityLevelConfiguration(obj.(*flowcontrol.PriorityLevelConfiguration)) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (priorityLevelConfigurationStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (priorityLevelConfigurationStrategy) Canonicalize(obj runtime.Object) { } @@ -100,6 +105,11 @@ func (priorityLevelConfigurationStrategy) ValidateUpdate(ctx context.Context, ob return validation.ValidatePriorityLevelConfiguration(obj.(*flowcontrol.PriorityLevelConfiguration)) } +// WarningsOnUpdate returns warnings for the given update. +func (priorityLevelConfigurationStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + type priorityLevelConfigurationStatusStrategy struct { priorityLevelConfigurationStrategy } @@ -139,3 +149,8 @@ func (priorityLevelConfigurationStatusStrategy) PrepareForUpdate(ctx context.Con func (priorityLevelConfigurationStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { return validation.ValidatePriorityLevelConfigurationStatusUpdate(old.(*flowcontrol.PriorityLevelConfiguration), obj.(*flowcontrol.PriorityLevelConfiguration)) } + +// WarningsOnUpdate returns warnings for the given update. +func (priorityLevelConfigurationStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} diff --git a/pkg/registry/networking/ingress/strategy.go b/pkg/registry/networking/ingress/strategy.go index 01e0ff33996..8d524188279 100644 --- a/pkg/registry/networking/ingress/strategy.go +++ b/pkg/registry/networking/ingress/strategy.go @@ -98,6 +98,9 @@ func (ingressStrategy) Validate(ctx context.Context, obj runtime.Object) field.E return validation.ValidateIngressCreate(ingress, requestGV) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (ingressStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + // Canonicalize normalizes the object after validation. func (ingressStrategy) Canonicalize(obj runtime.Object) { } @@ -116,6 +119,11 @@ func (ingressStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Obje return validation.ValidateIngressUpdate(obj.(*networking.Ingress), old.(*networking.Ingress), requestGV) } +// WarningsOnUpdate returns warnings for the given update. +func (ingressStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // AllowUnconditionalUpdate is the default update policy for Ingress objects. func (ingressStrategy) AllowUnconditionalUpdate() bool { return true @@ -158,3 +166,8 @@ func (ingressStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runt func (ingressStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { return validation.ValidateIngressStatusUpdate(obj.(*networking.Ingress), old.(*networking.Ingress)) } + +// WarningsOnUpdate returns warnings for the given update. +func (ingressStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} diff --git a/pkg/registry/networking/ingressclass/strategy.go b/pkg/registry/networking/ingressclass/strategy.go index ccd656cbf99..3434a794340 100644 --- a/pkg/registry/networking/ingressclass/strategy.go +++ b/pkg/registry/networking/ingressclass/strategy.go @@ -80,6 +80,11 @@ func (ingressClassStrategy) Validate(ctx context.Context, obj runtime.Object) fi return validation.ValidateIngressClass(ingressClass) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (ingressClassStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (ingressClassStrategy) Canonicalize(obj runtime.Object) { } @@ -98,6 +103,11 @@ func (ingressClassStrategy) ValidateUpdate(ctx context.Context, obj, old runtime return validation.ValidateIngressClassUpdate(newIngressClass, oldIngressClass) } +// WarningsOnUpdate returns warnings for the given update. +func (ingressClassStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // AllowUnconditionalUpdate is the default update policy for IngressClass // objects. func (ingressClassStrategy) AllowUnconditionalUpdate() bool { diff --git a/pkg/registry/networking/networkpolicy/strategy.go b/pkg/registry/networking/networkpolicy/strategy.go index 5ccd9de66a1..1f04bc2d912 100644 --- a/pkg/registry/networking/networkpolicy/strategy.go +++ b/pkg/registry/networking/networkpolicy/strategy.go @@ -77,6 +77,11 @@ func (networkPolicyStrategy) Validate(ctx context.Context, obj runtime.Object) f return validation.ValidateNetworkPolicy(networkPolicy) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (networkPolicyStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (networkPolicyStrategy) Canonicalize(obj runtime.Object) {} @@ -92,6 +97,11 @@ func (networkPolicyStrategy) ValidateUpdate(ctx context.Context, obj, old runtim return append(validationErrorList, updateErrorList...) } +// WarningsOnUpdate returns warnings for the given update. +func (networkPolicyStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // AllowUnconditionalUpdate is the default update policy for NetworkPolicy objects. func (networkPolicyStrategy) AllowUnconditionalUpdate() bool { return true diff --git a/pkg/registry/node/runtimeclass/strategy.go b/pkg/registry/node/runtimeclass/strategy.go index 848b55c3864..b7c2b57de2a 100644 --- a/pkg/registry/node/runtimeclass/strategy.go +++ b/pkg/registry/node/runtimeclass/strategy.go @@ -80,6 +80,9 @@ func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorLis return validation.ValidateRuntimeClass(runtimeClass) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + // Canonicalize normalizes the object after validation. func (strategy) Canonicalize(obj runtime.Object) { _ = obj.(*node.RuntimeClass) @@ -92,6 +95,11 @@ func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) fie return append(errorList, validation.ValidateRuntimeClassUpdate(newObj, old.(*node.RuntimeClass))...) } +// WarningsOnUpdate returns warnings for the given update. +func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // If AllowUnconditionalUpdate() is true and the object specified by // the user does not have a resource version, then generic Update() // populates it with the latest version. Else, it checks that the diff --git a/pkg/registry/policy/poddisruptionbudget/strategy.go b/pkg/registry/policy/poddisruptionbudget/strategy.go index dfb19b5b438..d5fc502c5e8 100644 --- a/pkg/registry/policy/poddisruptionbudget/strategy.go +++ b/pkg/registry/policy/poddisruptionbudget/strategy.go @@ -90,6 +90,11 @@ func (podDisruptionBudgetStrategy) Validate(ctx context.Context, obj runtime.Obj return validation.ValidatePodDisruptionBudget(podDisruptionBudget) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (podDisruptionBudgetStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (podDisruptionBudgetStrategy) Canonicalize(obj runtime.Object) { } @@ -104,6 +109,11 @@ func (podDisruptionBudgetStrategy) ValidateUpdate(ctx context.Context, obj, old return validation.ValidatePodDisruptionBudget(obj.(*policy.PodDisruptionBudget)) } +// WarningsOnUpdate returns warnings for the given update. +func (podDisruptionBudgetStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // AllowUnconditionalUpdate is the default update policy for PodDisruptionBudget objects. Status update should // only be allowed if version match. func (podDisruptionBudgetStrategy) AllowUnconditionalUpdate() bool { @@ -152,3 +162,8 @@ func (podDisruptionBudgetStatusStrategy) ValidateUpdate(ctx context.Context, obj return validation.ValidatePodDisruptionBudgetStatusUpdate(obj.(*policy.PodDisruptionBudget).Status, old.(*policy.PodDisruptionBudget).Status, field.NewPath("status"), apiVersion) } + +// WarningsOnUpdate returns warnings for the given update. +func (podDisruptionBudgetStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} diff --git a/pkg/registry/policy/podsecuritypolicy/strategy.go b/pkg/registry/policy/podsecuritypolicy/strategy.go index 9403eb36ac5..9345a09cb25 100644 --- a/pkg/registry/policy/podsecuritypolicy/strategy.go +++ b/pkg/registry/policy/podsecuritypolicy/strategy.go @@ -81,6 +81,9 @@ func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorLis return validation.ValidatePodSecurityPolicy(obj.(*policy.PodSecurityPolicy), opts) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { opts := validation.PodSecurityPolicyValidationOptions{ // Allowed if the feature is enabled or the old policy already had it. @@ -91,6 +94,11 @@ func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) fie return validation.ValidatePodSecurityPolicyUpdate(old.(*policy.PodSecurityPolicy), obj.(*policy.PodSecurityPolicy), opts) } +// WarningsOnUpdate returns warnings for the given update. +func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func volumeInUse(oldPSP *policy.PodSecurityPolicy, volume policy.FSType) bool { if oldPSP == nil { return false diff --git a/pkg/registry/rbac/clusterrole/strategy.go b/pkg/registry/rbac/clusterrole/strategy.go index 710b0f8d493..ca917c3e73f 100644 --- a/pkg/registry/rbac/clusterrole/strategy.go +++ b/pkg/registry/rbac/clusterrole/strategy.go @@ -74,6 +74,9 @@ func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorLis return validation.ValidateClusterRole(clusterRole) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + // Canonicalize normalizes the object after validation. func (strategy) Canonicalize(obj runtime.Object) { _ = obj.(*rbac.ClusterRole) @@ -86,6 +89,11 @@ func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) fie return append(errorList, validation.ValidateClusterRoleUpdate(newObj, old.(*rbac.ClusterRole))...) } +// WarningsOnUpdate returns warnings for the given update. +func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // If AllowUnconditionalUpdate() is true and the object specified by // the user does not have a resource version, then generic Update() // populates it with the latest version. Else, it checks that the diff --git a/pkg/registry/rbac/clusterrolebinding/strategy.go b/pkg/registry/rbac/clusterrolebinding/strategy.go index 3e3b44c93f9..83f0a54fadc 100644 --- a/pkg/registry/rbac/clusterrolebinding/strategy.go +++ b/pkg/registry/rbac/clusterrolebinding/strategy.go @@ -74,6 +74,9 @@ func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorLis return validation.ValidateClusterRoleBinding(clusterRoleBinding) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + // Canonicalize normalizes the object after validation. func (strategy) Canonicalize(obj runtime.Object) { _ = obj.(*rbac.ClusterRoleBinding) @@ -86,6 +89,11 @@ func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) fie return append(errorList, validation.ValidateClusterRoleBindingUpdate(newObj, old.(*rbac.ClusterRoleBinding))...) } +// WarningsOnUpdate returns warnings for the given update. +func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // If AllowUnconditionalUpdate() is true and the object specified by // the user does not have a resource version, then generic Update() // populates it with the latest version. Else, it checks that the diff --git a/pkg/registry/rbac/role/strategy.go b/pkg/registry/rbac/role/strategy.go index bd578c52aee..a7c0b04284b 100644 --- a/pkg/registry/rbac/role/strategy.go +++ b/pkg/registry/rbac/role/strategy.go @@ -74,6 +74,9 @@ func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorLis return validation.ValidateRole(role) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + // Canonicalize normalizes the object after validation. func (strategy) Canonicalize(obj runtime.Object) { _ = obj.(*rbac.Role) @@ -86,6 +89,11 @@ func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) fie return append(errorList, validation.ValidateRoleUpdate(newObj, old.(*rbac.Role))...) } +// WarningsOnUpdate returns warnings for the given update. +func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // If AllowUnconditionalUpdate() is true and the object specified by // the user does not have a resource version, then generic Update() // populates it with the latest version. Else, it checks that the diff --git a/pkg/registry/rbac/rolebinding/strategy.go b/pkg/registry/rbac/rolebinding/strategy.go index 2715b235324..aa003ae1ca8 100644 --- a/pkg/registry/rbac/rolebinding/strategy.go +++ b/pkg/registry/rbac/rolebinding/strategy.go @@ -74,6 +74,9 @@ func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorLis return validation.ValidateRoleBinding(roleBinding) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + // Canonicalize normalizes the object after validation. func (strategy) Canonicalize(obj runtime.Object) { _ = obj.(*rbac.RoleBinding) @@ -86,6 +89,11 @@ func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) fie return append(errorList, validation.ValidateRoleBindingUpdate(newObj, old.(*rbac.RoleBinding))...) } +// WarningsOnUpdate returns warnings for the given update. +func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // If AllowUnconditionalUpdate() is true and the object specified by // the user does not have a resource version, then generic Update() // populates it with the latest version. Else, it checks that the diff --git a/pkg/registry/scheduling/priorityclass/strategy.go b/pkg/registry/scheduling/priorityclass/strategy.go index 0a3a9cf67d0..36fb1d1a0d5 100644 --- a/pkg/registry/scheduling/priorityclass/strategy.go +++ b/pkg/registry/scheduling/priorityclass/strategy.go @@ -63,6 +63,11 @@ func (priorityClassStrategy) Validate(ctx context.Context, obj runtime.Object) f return validation.ValidatePriorityClass(pc) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (priorityClassStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (priorityClassStrategy) Canonicalize(obj runtime.Object) {} @@ -76,6 +81,11 @@ func (priorityClassStrategy) ValidateUpdate(ctx context.Context, obj, old runtim return validation.ValidatePriorityClassUpdate(obj.(*scheduling.PriorityClass), old.(*scheduling.PriorityClass)) } +// WarningsOnUpdate returns warnings for the given update. +func (priorityClassStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // AllowUnconditionalUpdate is the default update policy for PriorityClass objects. func (priorityClassStrategy) AllowUnconditionalUpdate() bool { return true diff --git a/pkg/registry/storage/csidriver/strategy.go b/pkg/registry/storage/csidriver/strategy.go index 46ae526493f..ecf7a5a6225 100644 --- a/pkg/registry/storage/csidriver/strategy.go +++ b/pkg/registry/storage/csidriver/strategy.go @@ -68,6 +68,11 @@ func (csiDriverStrategy) Validate(ctx context.Context, obj runtime.Object) field return validation.ValidateCSIDriver(csiDriver) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (csiDriverStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (csiDriverStrategy) Canonicalize(obj runtime.Object) { } @@ -116,6 +121,11 @@ func (csiDriverStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Ob return validation.ValidateCSIDriverUpdate(newCSIDriverObj, oldCSIDriverObj) } +// WarningsOnUpdate returns warnings for the given update. +func (csiDriverStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (csiDriverStrategy) AllowUnconditionalUpdate() bool { return false } diff --git a/pkg/registry/storage/csinode/strategy.go b/pkg/registry/storage/csinode/strategy.go index 6581b1ded07..835623d0da3 100644 --- a/pkg/registry/storage/csinode/strategy.go +++ b/pkg/registry/storage/csinode/strategy.go @@ -59,6 +59,9 @@ func (csiNodeStrategy) Validate(ctx context.Context, obj runtime.Object) field.E return errs } +// WarningsOnCreate returns warnings for the creation of the given object. +func (csiNodeStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + // Canonicalize normalizes the object after validation. func (csiNodeStrategy) Canonicalize(obj runtime.Object) { } @@ -90,6 +93,11 @@ func (csiNodeStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Obje return append(errorList, validation.ValidateCSINodeUpdate(newCSINodeObj, oldCSINodeObj, validateOptions)...) } +// WarningsOnUpdate returns warnings for the given update. +func (csiNodeStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (csiNodeStrategy) AllowUnconditionalUpdate() bool { return false } diff --git a/pkg/registry/storage/csistoragecapacity/strategy.go b/pkg/registry/storage/csistoragecapacity/strategy.go index 35e1df57be7..5e294fbe254 100644 --- a/pkg/registry/storage/csistoragecapacity/strategy.go +++ b/pkg/registry/storage/csistoragecapacity/strategy.go @@ -54,6 +54,11 @@ func (csiStorageCapacityStrategy) Validate(ctx context.Context, obj runtime.Obje return errs } +// WarningsOnCreate returns warnings for the creation of the given object. +func (csiStorageCapacityStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (csiStorageCapacityStrategy) Canonicalize(obj runtime.Object) { } @@ -73,6 +78,11 @@ func (csiStorageCapacityStrategy) ValidateUpdate(ctx context.Context, obj, old r return append(errorList, validation.ValidateCSIStorageCapacityUpdate(newCSIStorageCapacityObj, oldCSIStorageCapacityObj)...) } +// WarningsOnUpdate returns warnings for the given update. +func (csiStorageCapacityStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (csiStorageCapacityStrategy) AllowUnconditionalUpdate() bool { return false } diff --git a/pkg/registry/storage/storageclass/strategy.go b/pkg/registry/storage/storageclass/strategy.go index 58744efeb43..7572cec4fa7 100644 --- a/pkg/registry/storage/storageclass/strategy.go +++ b/pkg/registry/storage/storageclass/strategy.go @@ -54,6 +54,11 @@ func (storageClassStrategy) Validate(ctx context.Context, obj runtime.Object) fi return validation.ValidateStorageClass(storageClass) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (storageClassStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (storageClassStrategy) Canonicalize(obj runtime.Object) { } @@ -75,6 +80,11 @@ func (storageClassStrategy) ValidateUpdate(ctx context.Context, obj, old runtime return append(errorList, validation.ValidateStorageClassUpdate(obj.(*storage.StorageClass), old.(*storage.StorageClass))...) } +// WarningsOnUpdate returns warnings for the given update. +func (storageClassStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (storageClassStrategy) AllowUnconditionalUpdate() bool { return true } diff --git a/pkg/registry/storage/volumeattachment/strategy.go b/pkg/registry/storage/volumeattachment/strategy.go index e1d03c2682b..2e4182f2294 100644 --- a/pkg/registry/storage/volumeattachment/strategy.go +++ b/pkg/registry/storage/volumeattachment/strategy.go @@ -104,6 +104,11 @@ func (volumeAttachmentStrategy) Validate(ctx context.Context, obj runtime.Object return errs } +// WarningsOnCreate returns warnings for the creation of the given object. +func (volumeAttachmentStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (volumeAttachmentStrategy) Canonicalize(obj runtime.Object) { } @@ -143,6 +148,11 @@ func (volumeAttachmentStrategy) ValidateUpdate(ctx context.Context, obj, old run return append(errorList, validation.ValidateVolumeAttachmentUpdate(newVolumeAttachmentObj, oldVolumeAttachmentObj)...) } +// WarningsOnUpdate returns warnings for the given update. +func (volumeAttachmentStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (volumeAttachmentStrategy) AllowUnconditionalUpdate() bool { return false } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go index e87efe35527..0f219b9fc8e 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go @@ -83,3 +83,8 @@ func (a statusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.O func (a statusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { return a.customResourceStrategy.validator.ValidateStatusUpdate(ctx, obj, old, a.scale) } + +// WarningsOnUpdate returns warnings for the given update. +func (statusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go index 3025a605f09..b4d3f3ff26f 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go @@ -161,6 +161,11 @@ func (a customResourceStrategy) Validate(ctx context.Context, obj runtime.Object return errs } +// WarningsOnCreate returns warnings for the creation of the given object. +func (customResourceStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + // Canonicalize normalizes the object after validation. func (customResourceStrategy) Canonicalize(obj runtime.Object) { } @@ -202,6 +207,11 @@ func (a customResourceStrategy) ValidateUpdate(ctx context.Context, obj, old run return errs } +// WarningsOnUpdate returns warnings for the given update. +func (customResourceStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // GetAttrs returns labels and fields of a given object for filtering purposes. func (a customResourceStrategy) GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { accessor, err := meta.Accessor(obj) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go index c3e31021d9d..ae339013afe 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go @@ -93,6 +93,11 @@ func (a customResourceValidator) ValidateUpdate(ctx context.Context, obj, old ru return allErrs } +// WarningsOnUpdate returns warnings for the given update. +func (customResourceValidator) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + func (a customResourceValidator) ValidateStatusUpdate(ctx context.Context, obj, old runtime.Object, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList { u, ok := obj.(*unstructured.Unstructured) if !ok { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go index e1ab194fb06..389c87b9e3d 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go @@ -119,6 +119,9 @@ func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorLis return validation.ValidateCustomResourceDefinition(obj.(*apiextensions.CustomResourceDefinition), groupVersion) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + // AllowCreateOnUpdate is false for CustomResourceDefinition; this means a POST is // needed to create one. func (strategy) AllowCreateOnUpdate() bool { @@ -144,6 +147,11 @@ func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) fie return validation.ValidateCustomResourceDefinitionUpdate(obj.(*apiextensions.CustomResourceDefinition), old.(*apiextensions.CustomResourceDefinition), groupVersion) } +// WarningsOnUpdate returns warnings for the given update. +func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + type statusStrategy struct { runtime.ObjectTyper names.NameGenerator @@ -198,6 +206,11 @@ func (statusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Objec return validation.ValidateUpdateCustomResourceDefinitionStatus(obj.(*apiextensions.CustomResourceDefinition), old.(*apiextensions.CustomResourceDefinition)) } +// WarningsOnUpdate returns warnings for the given update. +func (statusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // GetAttrs returns labels and fields of a given object for filtering purposes. func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { apiserver, ok := obj.(*apiextensions.CustomResourceDefinition) diff --git a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go index 308c338e77a..8466a30fb20 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go @@ -115,9 +115,15 @@ func (t *testRESTStrategy) PrepareForUpdate(ctx context.Context, obj, old runtim func (t *testRESTStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { return nil } +func (t *testRESTStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} func (t *testRESTStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { return nil } +func (t *testRESTStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} func (t *testRESTStrategy) Canonicalize(obj runtime.Object) {} func NewTestGenericStoreRegistry(t *testing.T) (factory.DestroyFunc, *Store) { diff --git a/staging/src/k8s.io/apiserver/pkg/registry/rest/create.go b/staging/src/k8s.io/apiserver/pkg/registry/rest/create.go index dd70a4eb780..950260e457c 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/rest/create.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/rest/create.go @@ -31,6 +31,7 @@ import ( "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/storage/names" utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/apiserver/pkg/warning" ) // RESTCreateStrategy defines the minimum validation, accepted input, and @@ -59,6 +60,26 @@ type RESTCreateStrategy interface { // before the object is persisted. This method should not mutate the // object. Validate(ctx context.Context, obj runtime.Object) field.ErrorList + // WarningsOnCreate returns warnings to the client performing a create. + // WarningsOnCreate is invoked after default fields in the object have been filled in + // and after Validate has passed, before Canonicalize is called, and the object is persisted. + // This method must not mutate the object. + // + // Be brief; limit warnings to 120 characters if possible. + // Don't include a "Warning:" prefix in the message (that is added by clients on output). + // Warnings returned about a specific field should be formatted as "path.to.field: message". + // For example: `spec.imagePullSecrets[0].name: invalid empty name ""` + // + // Use warning messages to describe problems the client making the API request should correct or be aware of. + // For example: + // - use of deprecated fields/labels/annotations that will stop working in a future release + // - use of obsolete fields/labels/annotations that are non-functional + // - malformed or invalid specifications that prevent successful handling of the submitted object, + // but are not rejected by validation for compatibility reasons + // + // Warnings should not be returned for fields which cannot be resolved by the caller. + // For example, do not warn about spec fields in a subresource creation request. + WarningsOnCreate(ctx context.Context, obj runtime.Object) []string // Canonicalize allows an object to be mutated into a canonical form. This // ensures that code that operates on these objects can rely on the common // form for things like comparison. Canonicalize is invoked after @@ -113,6 +134,10 @@ func BeforeCreate(strategy RESTCreateStrategy, ctx context.Context, obj runtime. return errors.NewInvalid(kind.GroupKind(), objectMeta.GetName(), errs) } + for _, w := range strategy.WarningsOnCreate(ctx, obj) { + warning.AddWarning(ctx, "", w) + } + strategy.Canonicalize(obj) return nil diff --git a/staging/src/k8s.io/apiserver/pkg/registry/rest/create_update.go b/staging/src/k8s.io/apiserver/pkg/registry/rest/create_update.go index 37d6c8f8ada..acef76fa63e 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/rest/create_update.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/rest/create_update.go @@ -39,6 +39,26 @@ type RESTCreateUpdateStrategy interface { // filled in before the object is persisted. This method should not mutate // the object. ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList + // WarningsOnUpdate returns warnings to the client performing the update. + // WarningsOnUpdate is invoked after default fields in the object have been filled in + // and after ValidateUpdate has passed, before Canonicalize is called, and before the object is persisted. + // This method must not mutate either object. + // + // Be brief; limit warnings to 120 characters if possible. + // Don't include a "Warning:" prefix in the message (that is added by clients on output). + // Warnings returned about a specific field should be formatted as "path.to.field: message". + // For example: `spec.imagePullSecrets[0].name: invalid empty name ""` + // + // Use warning messages to describe problems the client making the API request should correct or be aware of. + // For example: + // - use of deprecated fields/labels/annotations that will stop working in a future release + // - use of obsolete fields/labels/annotations that are non-functional + // - malformed or invalid specifications that prevent successful handling of the submitted object, + // but are not rejected by validation for compatibility reasons + // + // Warnings should not be returned for fields which cannot be resolved by the caller. + // For example, do not warn about spec fields in a status update. + WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string // AllowUnconditionalUpdate returns true if the object can be updated // unconditionally (irrespective of the latest resource version), when // there is no resource version specified in the object. diff --git a/staging/src/k8s.io/apiserver/pkg/registry/rest/update.go b/staging/src/k8s.io/apiserver/pkg/registry/rest/update.go index 0741b84ec29..ffcca33c05e 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/rest/update.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/rest/update.go @@ -30,6 +30,7 @@ import ( "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/features" utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/apiserver/pkg/warning" ) // RESTUpdateStrategy defines the minimum validation, accepted input, and @@ -51,6 +52,26 @@ type RESTUpdateStrategy interface { // filled in before the object is persisted. This method should not mutate // the object. ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList + // WarningsOnUpdate returns warnings to the client performing the update. + // WarningsOnUpdate is invoked after default fields in the object have been filled in + // and after ValidateUpdate has passed, before Canonicalize is called, and before the object is persisted. + // This method must not mutate either object. + // + // Be brief; limit warnings to 120 characters if possible. + // Don't include a "Warning:" prefix in the message (that is added by clients on output). + // Warnings returned about a specific field should be formatted as "path.to.field: message". + // For example: `spec.imagePullSecrets[0].name: invalid empty name ""` + // + // Use warning messages to describe problems the client making the API request should correct or be aware of. + // For example: + // - use of deprecated fields/labels/annotations that will stop working in a future release + // - use of obsolete fields/labels/annotations that are non-functional + // - malformed or invalid specifications that prevent successful handling of the submitted object, + // but are not rejected by validation for compatibility reasons + // + // Warnings should not be returned for fields which cannot be resolved by the caller. + // For example, do not warn about spec fields in a status update. + WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string // Canonicalize allows an object to be mutated into a canonical form. This // ensures that code that operates on these objects can rely on the common // form for things like comparison. Canonicalize is invoked after @@ -144,6 +165,10 @@ func BeforeUpdate(strategy RESTUpdateStrategy, ctx context.Context, obj, old run return errors.NewInvalid(kind.GroupKind(), objectMeta.GetName(), errs) } + for _, w := range strategy.WarningsOnUpdate(ctx, obj, old) { + warning.AddWarning(ctx, "", w) + } + strategy.Canonicalize(obj) return nil diff --git a/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/strategy.go b/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/strategy.go index 1818d503c91..5e368c82b2a 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/strategy.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/strategy.go @@ -85,6 +85,11 @@ func (apiServerStrategy) Validate(ctx context.Context, obj runtime.Object) field return validation.ValidateAPIService(obj.(*apiregistration.APIService)) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (apiServerStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + func (apiServerStrategy) AllowCreateOnUpdate() bool { return false } @@ -100,6 +105,11 @@ func (apiServerStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Ob return validation.ValidateAPIServiceUpdate(obj.(*apiregistration.APIService), old.(*apiregistration.APIService)) } +// WarningsOnUpdate returns warnings for the given update. +func (apiServerStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + type apiServerStatusStrategy struct { runtime.ObjectTyper names.NameGenerator @@ -156,6 +166,11 @@ func (apiServerStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runt return validation.ValidateAPIServiceStatusUpdate(obj.(*apiregistration.APIService), old.(*apiregistration.APIService)) } +// WarningsOnUpdate returns warnings for the given update. +func (apiServerStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + // GetAttrs returns the labels and fields of an API server for filtering purposes. func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { apiserver, ok := obj.(*apiregistration.APIService) diff --git a/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/fischer/strategy.go b/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/fischer/strategy.go index e87f72179a8..0e9f2a919c3 100644 --- a/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/fischer/strategy.go +++ b/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/fischer/strategy.go @@ -79,6 +79,9 @@ func (fischerStrategy) Validate(ctx context.Context, obj runtime.Object) field.E return field.ErrorList{} } +// WarningsOnCreate returns warnings for the creation of the given object. +func (fischerStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + func (fischerStrategy) AllowCreateOnUpdate() bool { return false } @@ -93,3 +96,8 @@ func (fischerStrategy) Canonicalize(obj runtime.Object) { func (fischerStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { return field.ErrorList{} } + +// WarningsOnUpdate returns warnings for the given update. +func (fischerStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} diff --git a/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/flunder/strategy.go b/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/flunder/strategy.go index 046047d083a..0c17fdc44f6 100644 --- a/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/flunder/strategy.go +++ b/staging/src/k8s.io/sample-apiserver/pkg/registry/wardle/flunder/strategy.go @@ -81,6 +81,9 @@ func (flunderStrategy) Validate(ctx context.Context, obj runtime.Object) field.E return validation.ValidateFlunder(flunder) } +// WarningsOnCreate returns warnings for the creation of the given object. +func (flunderStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } + func (flunderStrategy) AllowCreateOnUpdate() bool { return false } @@ -95,3 +98,8 @@ func (flunderStrategy) Canonicalize(obj runtime.Object) { func (flunderStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { return field.ErrorList{} } + +// WarningsOnUpdate returns warnings for the given update. +func (flunderStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +}