Merge pull request #101688 from liggitt/field-warnings

Add field-level warning plumbing and add pod spec warnings
This commit is contained in:
Kubernetes Prow Robot 2021-05-19 17:23:04 -07:00 committed by GitHub
commit c115435adc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 1573 additions and 3 deletions

257
pkg/api/pod/warnings.go Normal file
View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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
}

View File

@ -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 }

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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")

View File

@ -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)

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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) {
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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) {
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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),

View File

@ -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) {
}

View File

@ -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) {

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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 {

View File

@ -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)

View File

@ -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) {

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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
}