k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/api/pod/warnings_test.go (about)

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package pod
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	"k8s.io/apimachinery/pkg/api/resource"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/util/sets"
    26  	"k8s.io/apimachinery/pkg/util/validation/field"
    27  	api "k8s.io/kubernetes/pkg/apis/core"
    28  	utilpointer "k8s.io/utils/pointer"
    29  )
    30  
    31  func BenchmarkNoWarnings(b *testing.B) {
    32  	ctx := context.TODO()
    33  	resources := api.ResourceList{
    34  		api.ResourceCPU:              resource.MustParse("100m"),
    35  		api.ResourceMemory:           resource.MustParse("4M"),
    36  		api.ResourceEphemeralStorage: resource.MustParse("4G"),
    37  	}
    38  	env := []api.EnvVar{
    39  		{Name: "a"},
    40  		{Name: "b"},
    41  	}
    42  	pod := &api.Pod{
    43  		ObjectMeta: metav1.ObjectMeta{
    44  			Annotations: map[string]string{`foo`: `bar`},
    45  		},
    46  		Spec: api.PodSpec{
    47  			NodeSelector: map[string]string{"foo": "bar", "baz": "quux"},
    48  			Affinity: &api.Affinity{
    49  				NodeAffinity: &api.NodeAffinity{
    50  					RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
    51  						NodeSelectorTerms: []api.NodeSelectorTerm{
    52  							{MatchExpressions: []api.NodeSelectorRequirement{{Key: `foo`}}},
    53  						},
    54  					},
    55  					PreferredDuringSchedulingIgnoredDuringExecution: []api.PreferredSchedulingTerm{
    56  						{Preference: api.NodeSelectorTerm{MatchExpressions: []api.NodeSelectorRequirement{{Key: `foo`}}}},
    57  					},
    58  				},
    59  			},
    60  			TopologySpreadConstraints: []api.TopologySpreadConstraint{
    61  				{TopologyKey: `foo`},
    62  			},
    63  			HostAliases: []api.HostAlias{
    64  				{IP: "1.1.1.1"},
    65  				{IP: "2.2.2.2"},
    66  			},
    67  			ImagePullSecrets: []api.LocalObjectReference{
    68  				{Name: "secret1"},
    69  				{Name: "secret2"},
    70  			},
    71  			InitContainers: []api.Container{
    72  				{Name: "init1", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}},
    73  				{Name: "init2", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}},
    74  			},
    75  			Containers: []api.Container{
    76  				{Name: "container1", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}},
    77  				{Name: "container2", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}},
    78  			},
    79  			Overhead: resources,
    80  			Volumes: []api.Volume{
    81  				{Name: "a"},
    82  				{Name: "b"},
    83  			},
    84  		},
    85  	}
    86  	oldPod := &api.Pod{}
    87  	b.ResetTimer()
    88  	for i := 0; i < b.N; i++ {
    89  		w := GetWarningsForPod(ctx, pod, oldPod)
    90  		if len(w) > 0 {
    91  			b.Fatalf("expected 0 warnings, got %q", w)
    92  		}
    93  	}
    94  }
    95  
    96  func BenchmarkWarnings(b *testing.B) {
    97  	ctx := context.TODO()
    98  	resources := api.ResourceList{
    99  		api.ResourceCPU:              resource.MustParse("100m"),
   100  		api.ResourceMemory:           resource.MustParse("4m"),
   101  		api.ResourceEphemeralStorage: resource.MustParse("4m"),
   102  	}
   103  	env := []api.EnvVar{
   104  		{Name: "a"},
   105  		{Name: "a"},
   106  	}
   107  	pod := &api.Pod{
   108  		Spec: api.PodSpec{
   109  			HostAliases: []api.HostAlias{
   110  				{IP: "1.1.1.1"},
   111  				{IP: "1.1.1.1"},
   112  			},
   113  			ImagePullSecrets: []api.LocalObjectReference{
   114  				{Name: "secret1"},
   115  				{Name: "secret1"},
   116  				{Name: ""},
   117  			},
   118  			InitContainers: []api.Container{
   119  				{Name: "init1", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}},
   120  				{Name: "init2", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}},
   121  			},
   122  			Containers: []api.Container{
   123  				{Name: "container1", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}},
   124  				{Name: "container2", Env: env, Resources: api.ResourceRequirements{Requests: resources, Limits: resources}},
   125  			},
   126  			Overhead: resources,
   127  			Volumes: []api.Volume{
   128  				{Name: "a"},
   129  				{Name: "a"},
   130  			},
   131  		},
   132  	}
   133  	oldPod := &api.Pod{}
   134  	b.ResetTimer()
   135  	for i := 0; i < b.N; i++ {
   136  		GetWarningsForPod(ctx, pod, oldPod)
   137  	}
   138  }
   139  
   140  func TestWarnings(t *testing.T) {
   141  	resources := api.ResourceList{
   142  		api.ResourceCPU:              resource.MustParse("100m"),
   143  		api.ResourceMemory:           resource.MustParse("4m"),
   144  		api.ResourceEphemeralStorage: resource.MustParse("4m"),
   145  	}
   146  	testcases := []struct {
   147  		name        string
   148  		template    *api.PodTemplateSpec
   149  		oldTemplate *api.PodTemplateSpec
   150  		expected    []string
   151  	}{
   152  		{
   153  			name:     "null",
   154  			template: nil,
   155  			expected: nil,
   156  		},
   157  		{
   158  			name: "photon",
   159  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   160  				Volumes: []api.Volume{
   161  					{Name: "p", VolumeSource: api.VolumeSource{PhotonPersistentDisk: &api.PhotonPersistentDiskVolumeSource{}}},
   162  				}},
   163  			},
   164  			expected: []string{`spec.volumes[0].photonPersistentDisk: deprecated in v1.11, non-functional in v1.16+`},
   165  		},
   166  		{
   167  			name: "gitRepo",
   168  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   169  				Volumes: []api.Volume{
   170  					{Name: "s", VolumeSource: api.VolumeSource{GitRepo: &api.GitRepoVolumeSource{}}},
   171  				}},
   172  			},
   173  			expected: []string{`spec.volumes[0].gitRepo: deprecated in v1.11`},
   174  		},
   175  		{
   176  			name: "scaleIO",
   177  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   178  				Volumes: []api.Volume{
   179  					{Name: "s", VolumeSource: api.VolumeSource{ScaleIO: &api.ScaleIOVolumeSource{}}},
   180  				}},
   181  			},
   182  			expected: []string{`spec.volumes[0].scaleIO: deprecated in v1.16, non-functional in v1.22+`},
   183  		},
   184  		{
   185  			name: "flocker",
   186  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   187  				Volumes: []api.Volume{
   188  					{Name: "s", VolumeSource: api.VolumeSource{Flocker: &api.FlockerVolumeSource{}}},
   189  				}},
   190  			},
   191  			expected: []string{`spec.volumes[0].flocker: deprecated in v1.22, non-functional in v1.25+`},
   192  		},
   193  		{
   194  			name: "storageOS",
   195  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   196  				Volumes: []api.Volume{
   197  					{Name: "s", VolumeSource: api.VolumeSource{StorageOS: &api.StorageOSVolumeSource{}}},
   198  				}},
   199  			},
   200  			expected: []string{`spec.volumes[0].storageOS: deprecated in v1.22, non-functional in v1.25+`},
   201  		},
   202  		{
   203  			name: "quobyte",
   204  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   205  				Volumes: []api.Volume{
   206  					{Name: "s", VolumeSource: api.VolumeSource{Quobyte: &api.QuobyteVolumeSource{}}},
   207  				}},
   208  			},
   209  			expected: []string{`spec.volumes[0].quobyte: deprecated in v1.22, non-functional in v1.25+`},
   210  		},
   211  		{
   212  			name: "glusterfs",
   213  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   214  				Volumes: []api.Volume{
   215  					{Name: "s", VolumeSource: api.VolumeSource{Glusterfs: &api.GlusterfsVolumeSource{}}},
   216  				}},
   217  			},
   218  			expected: []string{`spec.volumes[0].glusterfs: deprecated in v1.25, non-functional in v1.26+`},
   219  		}, {
   220  			name: "CephFS",
   221  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   222  				Volumes: []api.Volume{
   223  					{Name: "s", VolumeSource: api.VolumeSource{CephFS: &api.CephFSVolumeSource{}}},
   224  				}},
   225  			},
   226  			expected: []string{`spec.volumes[0].cephfs: deprecated in v1.28, non-functional in v1.31+`},
   227  		},
   228  
   229  		{
   230  			name: "rbd",
   231  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   232  				Volumes: []api.Volume{
   233  					{Name: "s", VolumeSource: api.VolumeSource{RBD: &api.RBDVolumeSource{}}},
   234  				}},
   235  			},
   236  			expected: []string{`spec.volumes[0].rbd: deprecated in v1.28, non-functional in v1.31+`},
   237  		},
   238  		{
   239  			name: "duplicate hostAlias",
   240  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   241  				HostAliases: []api.HostAlias{
   242  					{IP: "1.1.1.1"},
   243  					{IP: "1.1.1.1"},
   244  					{IP: "1.1.1.1"},
   245  				}},
   246  			},
   247  			expected: []string{
   248  				`spec.hostAliases[1].ip: duplicate ip "1.1.1.1"`,
   249  				`spec.hostAliases[2].ip: duplicate ip "1.1.1.1"`,
   250  			},
   251  		},
   252  		{
   253  			name: "duplicate imagePullSecret",
   254  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   255  				ImagePullSecrets: []api.LocalObjectReference{
   256  					{Name: "a"},
   257  					{Name: "a"},
   258  					{Name: "a"},
   259  				}},
   260  			},
   261  			expected: []string{
   262  				`spec.imagePullSecrets[1].name: duplicate name "a"`,
   263  				`spec.imagePullSecrets[2].name: duplicate name "a"`,
   264  			},
   265  		},
   266  		{
   267  			name: "empty imagePullSecret",
   268  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   269  				ImagePullSecrets: []api.LocalObjectReference{
   270  					{Name: ""},
   271  				}},
   272  			},
   273  			expected: []string{
   274  				`spec.imagePullSecrets[0].name: invalid empty name ""`,
   275  			},
   276  		},
   277  		{
   278  			name: "duplicate env",
   279  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   280  				InitContainers: []api.Container{{Env: []api.EnvVar{
   281  					{Name: "a", Value: "a"},
   282  					{Name: "a", Value: "a"},
   283  					{Name: "a", Value: "other"},
   284  					{Name: "a", Value: ""},
   285  					{Name: "a", Value: "$(a)"},
   286  					{Name: "a", ValueFrom: &api.EnvVarSource{}},
   287  					{Name: "a", Value: "$(a) $(a)"}, // no warning
   288  				}}},
   289  				Containers: []api.Container{{Env: []api.EnvVar{
   290  					{Name: "b", Value: "b"},
   291  					{Name: "b", Value: "b"},
   292  					{Name: "b", Value: "other"},
   293  					{Name: "b", Value: ""},
   294  					{Name: "b", Value: "$(b)"},
   295  					{Name: "b", ValueFrom: &api.EnvVarSource{}},
   296  					{Name: "b", Value: "$(b) $(b)"}, // no warning
   297  				}}},
   298  			}},
   299  			expected: []string{
   300  				`spec.initContainers[0].env[1]: hides previous definition of "a", which may be dropped when using apply`,
   301  				`spec.initContainers[0].env[2]: hides previous definition of "a", which may be dropped when using apply`,
   302  				`spec.initContainers[0].env[3]: hides previous definition of "a", which may be dropped when using apply`,
   303  				`spec.initContainers[0].env[4]: hides previous definition of "a", which may be dropped when using apply`,
   304  				`spec.initContainers[0].env[5]: hides previous definition of "a", which may be dropped when using apply`,
   305  				`spec.containers[0].env[1]: hides previous definition of "b", which may be dropped when using apply`,
   306  				`spec.containers[0].env[2]: hides previous definition of "b", which may be dropped when using apply`,
   307  				`spec.containers[0].env[3]: hides previous definition of "b", which may be dropped when using apply`,
   308  				`spec.containers[0].env[4]: hides previous definition of "b", which may be dropped when using apply`,
   309  				`spec.containers[0].env[5]: hides previous definition of "b", which may be dropped when using apply`,
   310  			},
   311  		},
   312  		{
   313  			name: "fractional resources",
   314  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   315  				InitContainers: []api.Container{{
   316  					Resources: api.ResourceRequirements{Requests: resources, Limits: resources},
   317  				}},
   318  				Containers: []api.Container{{
   319  					Resources: api.ResourceRequirements{Requests: resources, Limits: resources},
   320  				}},
   321  				Overhead: resources,
   322  			}},
   323  			expected: []string{
   324  				`spec.initContainers[0].resources.requests[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`,
   325  				`spec.initContainers[0].resources.requests[memory]: fractional byte value "4m" is invalid, must be an integer`,
   326  				`spec.initContainers[0].resources.limits[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`,
   327  				`spec.initContainers[0].resources.limits[memory]: fractional byte value "4m" is invalid, must be an integer`,
   328  				`spec.containers[0].resources.requests[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`,
   329  				`spec.containers[0].resources.requests[memory]: fractional byte value "4m" is invalid, must be an integer`,
   330  				`spec.containers[0].resources.limits[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`,
   331  				`spec.containers[0].resources.limits[memory]: fractional byte value "4m" is invalid, must be an integer`,
   332  				`spec.overhead[ephemeral-storage]: fractional byte value "4m" is invalid, must be an integer`,
   333  				`spec.overhead[memory]: fractional byte value "4m" is invalid, must be an integer`,
   334  			},
   335  		},
   336  		{
   337  			name: "node labels in nodeSelector",
   338  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   339  				NodeSelector: map[string]string{
   340  					`beta.kubernetes.io/arch`:                  `true`,
   341  					`beta.kubernetes.io/os`:                    `true`,
   342  					`failure-domain.beta.kubernetes.io/region`: `true`,
   343  					`failure-domain.beta.kubernetes.io/zone`:   `true`,
   344  					`beta.kubernetes.io/instance-type`:         `true`,
   345  				},
   346  			}},
   347  			expected: []string{
   348  				`spec.nodeSelector[beta.kubernetes.io/arch]: deprecated since v1.14; use "kubernetes.io/arch" instead`,
   349  				`spec.nodeSelector[beta.kubernetes.io/instance-type]: deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`,
   350  				`spec.nodeSelector[beta.kubernetes.io/os]: deprecated since v1.14; use "kubernetes.io/os" instead`,
   351  				`spec.nodeSelector[failure-domain.beta.kubernetes.io/region]: deprecated since v1.17; use "topology.kubernetes.io/region" instead`,
   352  				`spec.nodeSelector[failure-domain.beta.kubernetes.io/zone]: deprecated since v1.17; use "topology.kubernetes.io/zone" instead`,
   353  			},
   354  		},
   355  		{
   356  			name: "node labels in affinity requiredDuringSchedulingIgnoredDuringExecution",
   357  			template: &api.PodTemplateSpec{
   358  				Spec: api.PodSpec{
   359  					Affinity: &api.Affinity{
   360  						NodeAffinity: &api.NodeAffinity{
   361  							RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
   362  								NodeSelectorTerms: []api.NodeSelectorTerm{
   363  									{
   364  										MatchExpressions: []api.NodeSelectorRequirement{
   365  											{Key: `foo`},
   366  											{Key: `beta.kubernetes.io/arch`},
   367  											{Key: `beta.kubernetes.io/os`},
   368  											{Key: `failure-domain.beta.kubernetes.io/region`},
   369  											{Key: `failure-domain.beta.kubernetes.io/zone`},
   370  											{Key: `beta.kubernetes.io/instance-type`},
   371  										},
   372  									},
   373  								},
   374  							},
   375  						},
   376  					},
   377  				},
   378  			},
   379  			expected: []string{
   380  				`spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[1].key: beta.kubernetes.io/arch is deprecated since v1.14; use "kubernetes.io/arch" instead`,
   381  				`spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[2].key: beta.kubernetes.io/os is deprecated since v1.14; use "kubernetes.io/os" instead`,
   382  				`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`,
   383  				`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`,
   384  				`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`,
   385  			},
   386  		},
   387  		{
   388  			name: "node labels in affinity preferredDuringSchedulingIgnoredDuringExecution",
   389  			template: &api.PodTemplateSpec{
   390  				Spec: api.PodSpec{
   391  					Affinity: &api.Affinity{
   392  						NodeAffinity: &api.NodeAffinity{
   393  							PreferredDuringSchedulingIgnoredDuringExecution: []api.PreferredSchedulingTerm{
   394  								{
   395  									Preference: api.NodeSelectorTerm{
   396  										MatchExpressions: []api.NodeSelectorRequirement{
   397  											{Key: `foo`},
   398  											{Key: `beta.kubernetes.io/arch`},
   399  											{Key: `beta.kubernetes.io/os`},
   400  											{Key: `failure-domain.beta.kubernetes.io/region`},
   401  											{Key: `failure-domain.beta.kubernetes.io/zone`},
   402  											{Key: `beta.kubernetes.io/instance-type`},
   403  										},
   404  									},
   405  								},
   406  							},
   407  						},
   408  					},
   409  				},
   410  			},
   411  			expected: []string{
   412  				`spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[1].key: beta.kubernetes.io/arch is deprecated since v1.14; use "kubernetes.io/arch" instead`,
   413  				`spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[2].key: beta.kubernetes.io/os is deprecated since v1.14; use "kubernetes.io/os" instead`,
   414  				`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`,
   415  				`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`,
   416  				`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`,
   417  			},
   418  		},
   419  		{
   420  			name: "node labels in topologySpreadConstraints",
   421  			template: &api.PodTemplateSpec{
   422  				Spec: api.PodSpec{
   423  					TopologySpreadConstraints: []api.TopologySpreadConstraint{
   424  						{
   425  							TopologyKey:   `foo`,
   426  							LabelSelector: &metav1.LabelSelector{},
   427  						},
   428  						{
   429  							TopologyKey:   `beta.kubernetes.io/arch`,
   430  							LabelSelector: &metav1.LabelSelector{},
   431  						},
   432  						{
   433  							TopologyKey:   `beta.kubernetes.io/os`,
   434  							LabelSelector: &metav1.LabelSelector{},
   435  						},
   436  						{
   437  							TopologyKey:   `failure-domain.beta.kubernetes.io/region`,
   438  							LabelSelector: &metav1.LabelSelector{},
   439  						},
   440  						{
   441  							TopologyKey:   `failure-domain.beta.kubernetes.io/zone`,
   442  							LabelSelector: &metav1.LabelSelector{},
   443  						},
   444  						{
   445  							TopologyKey:   `beta.kubernetes.io/instance-type`,
   446  							LabelSelector: &metav1.LabelSelector{},
   447  						},
   448  					},
   449  				},
   450  			},
   451  			expected: []string{
   452  				`spec.topologySpreadConstraints[1].topologyKey: beta.kubernetes.io/arch is deprecated since v1.14; use "kubernetes.io/arch" instead`,
   453  				`spec.topologySpreadConstraints[2].topologyKey: beta.kubernetes.io/os is deprecated since v1.14; use "kubernetes.io/os" instead`,
   454  				`spec.topologySpreadConstraints[3].topologyKey: failure-domain.beta.kubernetes.io/region is deprecated since v1.17; use "topology.kubernetes.io/region" instead`,
   455  				`spec.topologySpreadConstraints[4].topologyKey: failure-domain.beta.kubernetes.io/zone is deprecated since v1.17; use "topology.kubernetes.io/zone" instead`,
   456  				`spec.topologySpreadConstraints[5].topologyKey: beta.kubernetes.io/instance-type is deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`,
   457  			},
   458  		},
   459  		{
   460  			name: "annotations",
   461  			template: &api.PodTemplateSpec{
   462  				ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
   463  					`foo`: `bar`,
   464  					`scheduler.alpha.kubernetes.io/critical-pod`:         `true`,
   465  					`seccomp.security.alpha.kubernetes.io/pod`:           `default`,
   466  					`container.seccomp.security.alpha.kubernetes.io/foo`: `default`,
   467  					`security.alpha.kubernetes.io/sysctls`:               `a,b,c`,
   468  					`security.alpha.kubernetes.io/unsafe-sysctls`:        `d,e,f`,
   469  				}},
   470  				Spec: api.PodSpec{Containers: []api.Container{{Name: "foo"}}},
   471  			},
   472  			expected: []string{
   473  				`metadata.annotations[scheduler.alpha.kubernetes.io/critical-pod]: non-functional in v1.16+; use the "priorityClassName" field instead`,
   474  				`metadata.annotations[seccomp.security.alpha.kubernetes.io/pod]: non-functional in v1.27+; use the "seccompProfile" field instead`,
   475  				`metadata.annotations[container.seccomp.security.alpha.kubernetes.io/foo]: non-functional in v1.27+; use the "seccompProfile" field instead`,
   476  				`metadata.annotations[security.alpha.kubernetes.io/sysctls]: non-functional in v1.11+; use the "sysctls" field instead`,
   477  				`metadata.annotations[security.alpha.kubernetes.io/unsafe-sysctls]: non-functional in v1.11+; use the "sysctls" field instead`,
   478  			},
   479  		},
   480  		{
   481  			name: "seccomp fields",
   482  			template: &api.PodTemplateSpec{
   483  				ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
   484  					`seccomp.security.alpha.kubernetes.io/pod`:           `default`,
   485  					`container.seccomp.security.alpha.kubernetes.io/foo`: `default`,
   486  				}},
   487  				Spec: api.PodSpec{
   488  					SecurityContext: &api.PodSecurityContext{
   489  						SeccompProfile: &api.SeccompProfile{Type: api.SeccompProfileTypeRuntimeDefault},
   490  					},
   491  					Containers: []api.Container{{
   492  						Name: "foo",
   493  						SecurityContext: &api.SecurityContext{
   494  							SeccompProfile: &api.SeccompProfile{Type: api.SeccompProfileTypeRuntimeDefault},
   495  						},
   496  					}},
   497  				},
   498  			},
   499  			expected: []string{},
   500  		},
   501  		{
   502  			name: "pod with ephemeral volume source 200Mi",
   503  			template: &api.PodTemplateSpec{
   504  				ObjectMeta: metav1.ObjectMeta{},
   505  				Spec: api.PodSpec{Volumes: []api.Volume{
   506  					{Name: "ephemeral-volume", VolumeSource: api.VolumeSource{Ephemeral: &api.EphemeralVolumeSource{
   507  						VolumeClaimTemplate: &api.PersistentVolumeClaimTemplate{
   508  							Spec: api.PersistentVolumeClaimSpec{Resources: api.VolumeResourceRequirements{
   509  								Requests: api.ResourceList{api.ResourceStorage: resource.MustParse("200Mi")}}},
   510  						},
   511  					}}}}},
   512  			},
   513  			expected: []string{},
   514  		},
   515  		{
   516  			name: "pod with ephemeral volume source 200m",
   517  			template: &api.PodTemplateSpec{
   518  				ObjectMeta: metav1.ObjectMeta{},
   519  				Spec: api.PodSpec{Volumes: []api.Volume{
   520  					{Name: "ephemeral-volume", VolumeSource: api.VolumeSource{Ephemeral: &api.EphemeralVolumeSource{
   521  						VolumeClaimTemplate: &api.PersistentVolumeClaimTemplate{
   522  							Spec: api.PersistentVolumeClaimSpec{Resources: api.VolumeResourceRequirements{
   523  								Requests: api.ResourceList{api.ResourceStorage: resource.MustParse("200m")}}},
   524  						},
   525  					}}}}},
   526  			},
   527  			expected: []string{
   528  				`spec.volumes[0].ephemeral.volumeClaimTemplate.spec.resources.requests[storage]: fractional byte value "200m" is invalid, must be an integer`,
   529  			},
   530  		},
   531  		{
   532  			name: "terminationGracePeriodSeconds is negative",
   533  			template: &api.PodTemplateSpec{
   534  				ObjectMeta: metav1.ObjectMeta{},
   535  				Spec: api.PodSpec{
   536  					TerminationGracePeriodSeconds: utilpointer.Int64Ptr(-1),
   537  				},
   538  			},
   539  			expected: []string{
   540  				`spec.terminationGracePeriodSeconds: must be >= 0; negative values are invalid and will be treated as 1`,
   541  			},
   542  		},
   543  		{
   544  			name: "null LabelSelector in topologySpreadConstraints",
   545  			template: &api.PodTemplateSpec{
   546  				ObjectMeta: metav1.ObjectMeta{},
   547  				Spec: api.PodSpec{
   548  					TopologySpreadConstraints: []api.TopologySpreadConstraint{
   549  						{
   550  							LabelSelector: &metav1.LabelSelector{},
   551  						},
   552  						{
   553  							LabelSelector: nil,
   554  						},
   555  					},
   556  				},
   557  			},
   558  			expected: []string{
   559  				`spec.topologySpreadConstraints[1].labelSelector: a null labelSelector results in matching no pod`,
   560  			},
   561  		},
   562  		{
   563  			name: "null LabelSelector in PodAffinity",
   564  			template: &api.PodTemplateSpec{
   565  				ObjectMeta: metav1.ObjectMeta{},
   566  				Spec: api.PodSpec{
   567  					Affinity: &api.Affinity{
   568  						PodAffinity: &api.PodAffinity{
   569  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
   570  								{
   571  									LabelSelector: &metav1.LabelSelector{},
   572  								},
   573  								{
   574  									LabelSelector: nil,
   575  								},
   576  							},
   577  							PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
   578  								{
   579  									PodAffinityTerm: api.PodAffinityTerm{
   580  										LabelSelector: &metav1.LabelSelector{},
   581  									},
   582  								},
   583  								{
   584  									PodAffinityTerm: api.PodAffinityTerm{
   585  										LabelSelector: nil,
   586  									},
   587  								},
   588  							},
   589  						},
   590  						PodAntiAffinity: &api.PodAntiAffinity{
   591  							RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
   592  								{
   593  									LabelSelector: &metav1.LabelSelector{},
   594  								},
   595  								{
   596  									LabelSelector: nil,
   597  								},
   598  							},
   599  							PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
   600  								{
   601  									PodAffinityTerm: api.PodAffinityTerm{
   602  										LabelSelector: &metav1.LabelSelector{},
   603  									},
   604  								},
   605  								{
   606  									PodAffinityTerm: api.PodAffinityTerm{
   607  										LabelSelector: nil,
   608  									},
   609  								},
   610  							},
   611  						},
   612  					},
   613  				},
   614  			},
   615  			expected: []string{
   616  				`spec.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution[1].labelSelector: a null labelSelector results in matching no pod`,
   617  				`spec.affinity.podAffinity.preferredDuringSchedulingIgnoredDuringExecution[1].podAffinityTerm.labelSelector: a null labelSelector results in matching no pod`,
   618  				`spec.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[1].labelSelector: a null labelSelector results in matching no pod`,
   619  				`spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution[1].podAffinityTerm.labelSelector: a null labelSelector results in matching no pod`,
   620  			},
   621  		},
   622  		{
   623  			name: "container no ports",
   624  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   625  				Containers: []api.Container{{
   626  					Name:  "foo",
   627  					Ports: []api.ContainerPort{},
   628  				}},
   629  			}},
   630  			expected: []string{},
   631  		},
   632  		{
   633  			name: "one container, one port",
   634  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   635  				Containers: []api.Container{{
   636  					Name: "foo",
   637  					Ports: []api.ContainerPort{
   638  						{ContainerPort: 80, HostPort: 80},
   639  					},
   640  				}},
   641  			}},
   642  			expected: []string{},
   643  		},
   644  		{
   645  			name: "one container, two ports, same protocol, different ports",
   646  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   647  				Containers: []api.Container{{
   648  					Name: "foo",
   649  					Ports: []api.ContainerPort{
   650  						{ContainerPort: 80, Protocol: api.ProtocolUDP},
   651  						{ContainerPort: 81, Protocol: api.ProtocolUDP},
   652  					},
   653  				}},
   654  			}},
   655  			expected: []string{},
   656  		},
   657  		{
   658  			name: "one container, two ports, different protocols, same port",
   659  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   660  				Containers: []api.Container{{
   661  					Name: "foo",
   662  					Ports: []api.ContainerPort{
   663  						{ContainerPort: 80, Protocol: api.ProtocolUDP},
   664  						{ContainerPort: 80, Protocol: api.ProtocolTCP},
   665  					},
   666  				}},
   667  			}},
   668  			expected: []string{},
   669  		},
   670  		{
   671  			name: "one container, two ports, same protocol, same port, different hostport",
   672  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   673  				Containers: []api.Container{{
   674  					Name: "foo",
   675  					Ports: []api.ContainerPort{
   676  						{ContainerPort: 80, Protocol: api.ProtocolTCP, HostPort: 80},
   677  						{ContainerPort: 80, Protocol: api.ProtocolTCP, HostPort: 81},
   678  					},
   679  				}},
   680  			}},
   681  			expected: []string{},
   682  		},
   683  		{
   684  			name: "one container, two ports, same protocol, port and hostPort, different hostIP",
   685  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   686  				Containers: []api.Container{{
   687  					Name: "foo",
   688  					Ports: []api.ContainerPort{
   689  						{ContainerPort: 80, Protocol: api.ProtocolTCP, HostPort: 80, HostIP: "10.0.0.1"},
   690  						{ContainerPort: 80, Protocol: api.ProtocolTCP, HostPort: 80, HostIP: "10.0.0.2"},
   691  					},
   692  				}},
   693  			}},
   694  			expected: []string{},
   695  		},
   696  		{
   697  			name: "two containers, one port each, same protocol, different ports",
   698  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   699  				Containers: []api.Container{{
   700  					Name: "foo",
   701  					Ports: []api.ContainerPort{
   702  						{ContainerPort: 80, Protocol: api.ProtocolUDP},
   703  					},
   704  				}, {
   705  					Name: "bar",
   706  					Ports: []api.ContainerPort{
   707  						{ContainerPort: 81, Protocol: api.ProtocolUDP},
   708  					},
   709  				}},
   710  			}},
   711  			expected: []string{},
   712  		},
   713  		{
   714  			name: "two containers, one port each, different protocols, same port",
   715  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   716  				Containers: []api.Container{{
   717  					Name: "foo",
   718  					Ports: []api.ContainerPort{
   719  						{ContainerPort: 80, Protocol: api.ProtocolUDP},
   720  					},
   721  				}, {
   722  					Name: "bar",
   723  					Ports: []api.ContainerPort{
   724  						{ContainerPort: 80, Protocol: api.ProtocolTCP},
   725  					},
   726  				}},
   727  			}},
   728  			expected: []string{},
   729  		},
   730  		{
   731  			name: "two containers, one port each, same protocol, same port, different hostport",
   732  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   733  				Containers: []api.Container{{
   734  					Name: "foo",
   735  					Ports: []api.ContainerPort{
   736  						{ContainerPort: 80, Protocol: api.ProtocolTCP, HostPort: 80},
   737  					},
   738  				}, {
   739  					Name: "bar",
   740  					Ports: []api.ContainerPort{
   741  						{ContainerPort: 80, Protocol: api.ProtocolTCP, HostPort: 81},
   742  					},
   743  				}},
   744  			}},
   745  			expected: []string{},
   746  		},
   747  		{
   748  			name: "two containers, one port each, same protocol, port and hostPort, different hostIP",
   749  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   750  				Containers: []api.Container{{
   751  					Name: "foo",
   752  					Ports: []api.ContainerPort{
   753  						{ContainerPort: 80, Protocol: api.ProtocolTCP, HostPort: 80, HostIP: "10.0.0.1"},
   754  					},
   755  				}, {
   756  					Name: "bar",
   757  					Ports: []api.ContainerPort{
   758  						{ContainerPort: 80, Protocol: api.ProtocolTCP, HostPort: 80, HostIP: "10.0.0.2"},
   759  					},
   760  				}},
   761  			}},
   762  			expected: []string{},
   763  		},
   764  		{
   765  			name: "duplicate container ports with same port and protocol",
   766  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   767  				Containers: []api.Container{{
   768  					Name: "foo",
   769  					Ports: []api.ContainerPort{
   770  						{ContainerPort: 80, Protocol: api.ProtocolUDP},
   771  						{ContainerPort: 80, Protocol: api.ProtocolUDP},
   772  					},
   773  				}},
   774  			}},
   775  			expected: []string{
   776  				`spec.containers[0].ports[1]: duplicate port definition with spec.containers[0].ports[0]`,
   777  			},
   778  		},
   779  		{
   780  			name: "duplicate container ports with same port, hostPort and protocol",
   781  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   782  				Containers: []api.Container{{
   783  					Name: "foo",
   784  					Ports: []api.ContainerPort{
   785  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP},
   786  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP},
   787  					},
   788  				}},
   789  			}},
   790  			expected: []string{
   791  				`spec.containers[0].ports[1]: duplicate port definition with spec.containers[0].ports[0]`,
   792  			},
   793  		},
   794  		{
   795  			name: "duplicate container ports with same port, host port, host IP and protocol",
   796  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   797  				Containers: []api.Container{{
   798  					Name: "foo",
   799  					Ports: []api.ContainerPort{
   800  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"},
   801  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"},
   802  					},
   803  				}},
   804  			}},
   805  			expected: []string{
   806  				`spec.containers[0].ports[1]: duplicate port definition with spec.containers[0].ports[0]`,
   807  			},
   808  		},
   809  		{
   810  			name: "one container port hostIP set without host port set",
   811  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   812  				Containers: []api.Container{{
   813  					Name: "foo",
   814  					Ports: []api.ContainerPort{
   815  						{ContainerPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"},
   816  					},
   817  				}},
   818  			}},
   819  			expected: []string{
   820  				`spec.containers[0].ports[0]: hostIP set without hostPort: {Name: HostPort:0 ContainerPort:80 Protocol:UDP HostIP:10.0.0.1}`,
   821  			},
   822  		},
   823  		{
   824  			name: "duplicate container ports with one host port set and one without",
   825  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   826  				Containers: []api.Container{{
   827  					Name: "foo",
   828  					Ports: []api.ContainerPort{
   829  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"},
   830  						{ContainerPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"},
   831  					},
   832  				}},
   833  			}},
   834  			expected: []string{
   835  				`spec.containers[0].ports[1]: overlapping port definition with spec.containers[0].ports[0]`,
   836  				`spec.containers[0].ports[1]: hostIP set without hostPort: {Name: HostPort:0 ContainerPort:80 Protocol:UDP HostIP:10.0.0.1}`,
   837  			},
   838  		},
   839  		{
   840  			name: "duplicate container ports without one host IP set and two with",
   841  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   842  				Containers: []api.Container{{
   843  					Name: "foo",
   844  					Ports: []api.ContainerPort{
   845  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"},
   846  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP},
   847  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.2"},
   848  					},
   849  				}},
   850  			}},
   851  			expected: []string{
   852  				`spec.containers[0].ports[1]: dangerously ambiguous port definition with spec.containers[0].ports[0]`,
   853  				`spec.containers[0].ports[2]: dangerously ambiguous port definition with spec.containers[0].ports[1]`,
   854  			},
   855  		},
   856  		{
   857  			name: "duplicate container ports with one host IP set and one without",
   858  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   859  				Containers: []api.Container{{
   860  					Name: "foo",
   861  					Ports: []api.ContainerPort{
   862  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"},
   863  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP},
   864  					},
   865  				}},
   866  			}},
   867  			expected: []string{
   868  				`spec.containers[0].ports[1]: dangerously ambiguous port definition with spec.containers[0].ports[0]`,
   869  			},
   870  		},
   871  		{
   872  			name: "duplicate containers with same port and protocol",
   873  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   874  				Containers: []api.Container{{
   875  					Name: "foo",
   876  					Ports: []api.ContainerPort{
   877  						{ContainerPort: 80, Protocol: api.ProtocolUDP},
   878  					},
   879  				}, {
   880  					Name: "bar",
   881  					Ports: []api.ContainerPort{
   882  						{ContainerPort: 80, Protocol: api.ProtocolUDP},
   883  					},
   884  				}},
   885  			}},
   886  			expected: []string{
   887  				`spec.containers[1].ports[0]: duplicate port definition with spec.containers[0].ports[0]`,
   888  			},
   889  		},
   890  		{
   891  			name: "duplicate containers with same port, hostPort and protocol",
   892  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   893  				Containers: []api.Container{{
   894  					Name: "foo",
   895  					Ports: []api.ContainerPort{
   896  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP},
   897  					},
   898  				}, {
   899  					Name: "bar",
   900  					Ports: []api.ContainerPort{
   901  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP},
   902  					},
   903  				}},
   904  			}},
   905  			expected: []string{
   906  				`spec.containers[1].ports[0]: duplicate port definition with spec.containers[0].ports[0]`,
   907  			},
   908  		},
   909  		{
   910  			name: "duplicate containers with same port, host port, host IP and protocol",
   911  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   912  				Containers: []api.Container{{
   913  					Name: "foo",
   914  					Ports: []api.ContainerPort{
   915  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"},
   916  					},
   917  				}, {
   918  					Name: "bar",
   919  					Ports: []api.ContainerPort{
   920  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"},
   921  					},
   922  				}},
   923  			}},
   924  			expected: []string{
   925  				`spec.containers[1].ports[0]: duplicate port definition with spec.containers[0].ports[0]`,
   926  			},
   927  		},
   928  		{
   929  			name: "duplicate containers with one host port set and one without",
   930  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   931  				Containers: []api.Container{{
   932  					Name: "foo",
   933  					Ports: []api.ContainerPort{
   934  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"},
   935  					},
   936  				}, {
   937  					Name: "bar",
   938  					Ports: []api.ContainerPort{
   939  						{ContainerPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"},
   940  					},
   941  				}},
   942  			}},
   943  			expected: []string{
   944  				`spec.containers[1].ports[0]: overlapping port definition with spec.containers[0].ports[0]`,
   945  				`spec.containers[1].ports[0]: hostIP set without hostPort: {Name: HostPort:0 ContainerPort:80 Protocol:UDP HostIP:10.0.0.1}`,
   946  			},
   947  		},
   948  		{
   949  			name: "duplicate container ports without one host IP set and one with",
   950  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   951  				Containers: []api.Container{{
   952  					Name: "foo",
   953  					Ports: []api.ContainerPort{
   954  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP},
   955  					},
   956  				}, {
   957  					Name: "bar",
   958  					Ports: []api.ContainerPort{
   959  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"},
   960  					},
   961  				}},
   962  			}},
   963  			expected: []string{
   964  				`spec.containers[1].ports[0]: dangerously ambiguous port definition with spec.containers[0].ports[0]`,
   965  			},
   966  		},
   967  		{
   968  			name: "duplicate container ports with one host IP set and one without",
   969  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   970  				Containers: []api.Container{{
   971  					Name: "foo",
   972  					Ports: []api.ContainerPort{
   973  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP, HostIP: "10.0.0.1"},
   974  					},
   975  				}, {
   976  					Name: "bar",
   977  					Ports: []api.ContainerPort{
   978  						{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP},
   979  					},
   980  				}},
   981  			}},
   982  			expected: []string{
   983  				`spec.containers[1].ports[0]: dangerously ambiguous port definition with spec.containers[0].ports[0]`,
   984  			},
   985  		},
   986  		{
   987  			name: "create duplicate container ports in two containers",
   988  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
   989  				Containers: []api.Container{
   990  					{
   991  						Name: "foo1",
   992  						Ports: []api.ContainerPort{
   993  							{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP},
   994  							{ContainerPort: 180, HostPort: 80, Protocol: api.ProtocolUDP},
   995  							{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP},
   996  						},
   997  					},
   998  					{
   999  						Name: "foo",
  1000  						Ports: []api.ContainerPort{
  1001  							{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP},
  1002  							{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolUDP},
  1003  						},
  1004  					}},
  1005  			}},
  1006  			expected: []string{
  1007  				`spec.containers[0].ports[2]: duplicate port definition with spec.containers[0].ports[0]`,
  1008  				`spec.containers[1].ports[0]: duplicate port definition with spec.containers[0].ports[0]`,
  1009  				`spec.containers[1].ports[0]: duplicate port definition with spec.containers[0].ports[2]`,
  1010  				`spec.containers[1].ports[1]: duplicate port definition with spec.containers[0].ports[0]`,
  1011  				`spec.containers[1].ports[1]: duplicate port definition with spec.containers[0].ports[2]`,
  1012  				`spec.containers[1].ports[1]: duplicate port definition with spec.containers[1].ports[0]`,
  1013  			},
  1014  		},
  1015  		{
  1016  			name: "update duplicate container ports in two containers",
  1017  			template: &api.PodTemplateSpec{Spec: api.PodSpec{
  1018  				Containers: []api.Container{
  1019  					{
  1020  						Name: "foo1",
  1021  						Ports: []api.ContainerPort{
  1022  							{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolTCP},
  1023  							{ContainerPort: 180, HostPort: 80, Protocol: api.ProtocolTCP},
  1024  							{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolTCP},
  1025  						},
  1026  					},
  1027  					{
  1028  						Name: "foo",
  1029  						Ports: []api.ContainerPort{
  1030  							{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolTCP},
  1031  							{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolTCP},
  1032  						},
  1033  					}},
  1034  			}},
  1035  			oldTemplate: &api.PodTemplateSpec{Spec: api.PodSpec{
  1036  				Containers: []api.Container{
  1037  					{
  1038  						Name: "foo1",
  1039  						Ports: []api.ContainerPort{
  1040  							{ContainerPort: 80, HostPort: 180, Protocol: api.ProtocolTCP},
  1041  							{ContainerPort: 180, HostPort: 80, Protocol: api.ProtocolTCP},
  1042  							{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolTCP},
  1043  						},
  1044  					},
  1045  					{
  1046  						Name: "foo",
  1047  						Ports: []api.ContainerPort{
  1048  							{ContainerPort: 80, HostPort: 180, Protocol: api.ProtocolTCP},
  1049  							{ContainerPort: 80, HostPort: 80, Protocol: api.ProtocolTCP},
  1050  						},
  1051  					}},
  1052  			}},
  1053  			expected: []string{
  1054  				`spec.containers[0].ports[2]: duplicate port definition with spec.containers[0].ports[0]`,
  1055  				`spec.containers[1].ports[0]: duplicate port definition with spec.containers[0].ports[0]`,
  1056  				`spec.containers[1].ports[0]: duplicate port definition with spec.containers[0].ports[2]`,
  1057  				`spec.containers[1].ports[1]: duplicate port definition with spec.containers[0].ports[0]`,
  1058  				`spec.containers[1].ports[1]: duplicate port definition with spec.containers[0].ports[2]`,
  1059  				`spec.containers[1].ports[1]: duplicate port definition with spec.containers[1].ports[0]`,
  1060  			},
  1061  		},
  1062  	}
  1063  
  1064  	for _, tc := range testcases {
  1065  		t.Run("podspec_"+tc.name, func(t *testing.T) {
  1066  			var oldTemplate *api.PodTemplateSpec
  1067  			if tc.oldTemplate != nil {
  1068  				oldTemplate = tc.oldTemplate
  1069  			}
  1070  			actual := sets.New[string](GetWarningsForPodTemplate(context.TODO(), nil, tc.template, oldTemplate)...)
  1071  			expected := sets.New[string](tc.expected...)
  1072  			for _, missing := range sets.List[string](expected.Difference(actual)) {
  1073  				t.Errorf("missing: %s", missing)
  1074  			}
  1075  			for _, extra := range sets.List[string](actual.Difference(expected)) {
  1076  				t.Errorf("extra: %s", extra)
  1077  			}
  1078  		})
  1079  
  1080  		t.Run("pod_"+tc.name, func(t *testing.T) {
  1081  			var pod *api.Pod
  1082  			if tc.template != nil {
  1083  				pod = &api.Pod{
  1084  					ObjectMeta: tc.template.ObjectMeta,
  1085  					Spec:       tc.template.Spec,
  1086  				}
  1087  			}
  1088  			actual := sets.New[string](GetWarningsForPod(context.TODO(), pod, &api.Pod{})...)
  1089  			expected := sets.New[string](tc.expected...)
  1090  			for _, missing := range sets.List[string](expected.Difference(actual)) {
  1091  				t.Errorf("missing: %s", missing)
  1092  			}
  1093  			for _, extra := range sets.List[string](actual.Difference(expected)) {
  1094  				t.Errorf("extra: %s", extra)
  1095  			}
  1096  		})
  1097  	}
  1098  }
  1099  
  1100  func TestTemplateOnlyWarnings(t *testing.T) {
  1101  	testcases := []struct {
  1102  		name        string
  1103  		template    *api.PodTemplateSpec
  1104  		oldTemplate *api.PodTemplateSpec
  1105  		expected    []string
  1106  	}{
  1107  		{
  1108  			name: "annotations",
  1109  			template: &api.PodTemplateSpec{
  1110  				ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
  1111  					`container.apparmor.security.beta.kubernetes.io/foo`: `unconfined`,
  1112  				}},
  1113  				Spec: api.PodSpec{Containers: []api.Container{{Name: "foo"}}},
  1114  			},
  1115  			expected: []string{
  1116  				`template.metadata.annotations[container.apparmor.security.beta.kubernetes.io/foo]: deprecated since v1.30; use the "appArmorProfile" field instead`,
  1117  			},
  1118  		},
  1119  		{
  1120  			name: "AppArmor pod field",
  1121  			template: &api.PodTemplateSpec{
  1122  				ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
  1123  					`container.apparmor.security.beta.kubernetes.io/foo`: `unconfined`,
  1124  				}},
  1125  				Spec: api.PodSpec{
  1126  					SecurityContext: &api.PodSecurityContext{
  1127  						AppArmorProfile: &api.AppArmorProfile{Type: api.AppArmorProfileTypeUnconfined},
  1128  					},
  1129  					Containers: []api.Container{{
  1130  						Name: "foo",
  1131  					}},
  1132  				},
  1133  			},
  1134  			expected: []string{},
  1135  		},
  1136  		{
  1137  			name: "AppArmor container field",
  1138  			template: &api.PodTemplateSpec{
  1139  				ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
  1140  					`container.apparmor.security.beta.kubernetes.io/foo`: `unconfined`,
  1141  				}},
  1142  				Spec: api.PodSpec{
  1143  					Containers: []api.Container{{
  1144  						Name: "foo",
  1145  						SecurityContext: &api.SecurityContext{
  1146  							AppArmorProfile: &api.AppArmorProfile{Type: api.AppArmorProfileTypeUnconfined},
  1147  						},
  1148  					}},
  1149  				},
  1150  			},
  1151  			expected: []string{},
  1152  		},
  1153  	}
  1154  
  1155  	for _, tc := range testcases {
  1156  		t.Run("podspec_"+tc.name, func(t *testing.T) {
  1157  			var oldTemplate *api.PodTemplateSpec
  1158  			if tc.oldTemplate != nil {
  1159  				oldTemplate = tc.oldTemplate
  1160  			}
  1161  			actual := sets.New[string](GetWarningsForPodTemplate(context.TODO(), field.NewPath("template"), tc.template, oldTemplate)...)
  1162  			expected := sets.New[string](tc.expected...)
  1163  			for _, missing := range sets.List[string](expected.Difference(actual)) {
  1164  				t.Errorf("missing: %s", missing)
  1165  			}
  1166  			for _, extra := range sets.List[string](actual.Difference(expected)) {
  1167  				t.Errorf("extra: %s", extra)
  1168  			}
  1169  		})
  1170  
  1171  		t.Run("pod_"+tc.name, func(t *testing.T) {
  1172  			var pod *api.Pod
  1173  			if tc.template != nil {
  1174  				pod = &api.Pod{
  1175  					ObjectMeta: tc.template.ObjectMeta,
  1176  					Spec:       tc.template.Spec,
  1177  				}
  1178  			}
  1179  			actual := GetWarningsForPod(context.TODO(), pod, &api.Pod{})
  1180  			if len(actual) > 0 {
  1181  				t.Errorf("unexpected template-only warnings on pod: %v", actual)
  1182  			}
  1183  		})
  1184  	}
  1185  }