mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 17:30:00 +00:00
Merge pull request #121968 from Peac36/fix/121414
add warnings for cases one of projected volume types get overwritten by service account token
This commit is contained in:
commit
a2a32fc31a
@ -19,6 +19,7 @@ package pod
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@ -168,6 +169,10 @@ func warningsForPodSpecAndMeta(fieldPath *field.Path, podSpec *api.PodSpec, meta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if overlaps := warningsForOverlappingVirtualPaths(podSpec.Volumes); len(overlaps) > 0 {
|
||||||
|
warnings = append(warnings, overlaps...)
|
||||||
|
}
|
||||||
|
|
||||||
// duplicate hostAliases (#91670, #58477)
|
// duplicate hostAliases (#91670, #58477)
|
||||||
if len(podSpec.HostAliases) > 1 {
|
if len(podSpec.HostAliases) > 1 {
|
||||||
items := sets.New[string]()
|
items := sets.New[string]()
|
||||||
@ -354,3 +359,166 @@ func warningsForWeightedPodAffinityTerms(terms []api.WeightedPodAffinityTerm, fi
|
|||||||
}
|
}
|
||||||
return warnings
|
return warnings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// warningsForOverlappingVirtualPaths validates that there are no overlapping paths in single ConfigMapVolume, SecretVolume, DownwardAPIVolume and ProjectedVolume.
|
||||||
|
// A volume can try to load different keys to the same path which will result in overwriting of the value from the latest registered key
|
||||||
|
// Another possible scenario is when one of the path contains the other key path. Example:
|
||||||
|
// configMap:
|
||||||
|
//
|
||||||
|
// name: myconfig
|
||||||
|
// items:
|
||||||
|
// - key: key1
|
||||||
|
// path: path
|
||||||
|
// - key: key2
|
||||||
|
// path: path/path2
|
||||||
|
//
|
||||||
|
// In such cases we either get `is directory` or 'file exists' error message.
|
||||||
|
func warningsForOverlappingVirtualPaths(volumes []api.Volume) []string {
|
||||||
|
var warnings []string
|
||||||
|
|
||||||
|
mkWarn := func(volName, volDesc, body string) string {
|
||||||
|
return fmt.Sprintf("volume %q (%s): overlapping paths: %s", volName, volDesc, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range volumes {
|
||||||
|
if v.ConfigMap != nil && v.ConfigMap.Items != nil {
|
||||||
|
overlaps := checkVolumeMappingForOverlap(extractPaths(v.ConfigMap.Items, ""))
|
||||||
|
for _, ol := range overlaps {
|
||||||
|
warnings = append(warnings, mkWarn(v.Name, fmt.Sprintf("ConfigMap %q", v.ConfigMap.Name), ol))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Secret != nil && v.Secret.Items != nil {
|
||||||
|
overlaps := checkVolumeMappingForOverlap(extractPaths(v.Secret.Items, ""))
|
||||||
|
for _, ol := range overlaps {
|
||||||
|
warnings = append(warnings, mkWarn(v.Name, fmt.Sprintf("Secret %q", v.Secret.SecretName), ol))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.DownwardAPI != nil && v.DownwardAPI.Items != nil {
|
||||||
|
overlaps := checkVolumeMappingForOverlap(extractPathsDownwardAPI(v.DownwardAPI.Items, ""))
|
||||||
|
for _, ol := range overlaps {
|
||||||
|
warnings = append(warnings, mkWarn(v.Name, "DownwardAPI", ol))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Projected != nil {
|
||||||
|
var sourcePaths []pathAndSource
|
||||||
|
var allPaths []pathAndSource
|
||||||
|
|
||||||
|
for _, source := range v.Projected.Sources {
|
||||||
|
if source == (api.VolumeProjection{}) {
|
||||||
|
warnings = append(warnings, fmt.Sprintf("volume %q (Projected) has no sources provided", v.Name))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case source.ConfigMap != nil && source.ConfigMap.Items != nil:
|
||||||
|
sourcePaths = extractPaths(source.ConfigMap.Items, fmt.Sprintf("ConfigMap %q", source.ConfigMap.Name))
|
||||||
|
case source.Secret != nil && source.Secret.Items != nil:
|
||||||
|
sourcePaths = extractPaths(source.Secret.Items, fmt.Sprintf("Secret %q", source.Secret.Name))
|
||||||
|
case source.DownwardAPI != nil && source.DownwardAPI.Items != nil:
|
||||||
|
sourcePaths = extractPathsDownwardAPI(source.DownwardAPI.Items, "DownwardAPI")
|
||||||
|
case source.ServiceAccountToken != nil:
|
||||||
|
sourcePaths = []pathAndSource{{source.ServiceAccountToken.Path, "ServiceAccountToken"}}
|
||||||
|
case source.ClusterTrustBundle != nil:
|
||||||
|
name := ""
|
||||||
|
if source.ClusterTrustBundle.Name != nil {
|
||||||
|
name = *source.ClusterTrustBundle.Name
|
||||||
|
} else {
|
||||||
|
name = *source.ClusterTrustBundle.SignerName
|
||||||
|
}
|
||||||
|
sourcePaths = []pathAndSource{{source.ClusterTrustBundle.Path, fmt.Sprintf("ClusterTrustBundle %q", name)}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sourcePaths) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ps := range sourcePaths {
|
||||||
|
ps.path = strings.TrimRight(ps.path, string(os.PathSeparator))
|
||||||
|
if collisions := checkForOverlap(allPaths, ps); len(collisions) > 0 {
|
||||||
|
for _, c := range collisions {
|
||||||
|
warnings = append(warnings, mkWarn(v.Name, "Projected", fmt.Sprintf("%s with %s", ps.String(), c.String())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allPaths = append(allPaths, ps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return warnings
|
||||||
|
}
|
||||||
|
|
||||||
|
// this lets us track a path and where it came from, for better errors
|
||||||
|
type pathAndSource struct {
|
||||||
|
path string
|
||||||
|
source string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps pathAndSource) String() string {
|
||||||
|
if ps.source != "" {
|
||||||
|
return fmt.Sprintf("%q (%s)", ps.path, ps.source)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%q", ps.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractPaths(mapping []api.KeyToPath, source string) []pathAndSource {
|
||||||
|
result := make([]pathAndSource, 0, len(mapping))
|
||||||
|
|
||||||
|
for _, v := range mapping {
|
||||||
|
result = append(result, pathAndSource{v.Path, source})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractPathsDownwardAPI(mapping []api.DownwardAPIVolumeFile, source string) []pathAndSource {
|
||||||
|
result := make([]pathAndSource, 0, len(mapping))
|
||||||
|
|
||||||
|
for _, v := range mapping {
|
||||||
|
result = append(result, pathAndSource{v.Path, source})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkVolumeMappingForOverlap(paths []pathAndSource) []string {
|
||||||
|
pathSeparator := string(os.PathSeparator)
|
||||||
|
var warnings []string
|
||||||
|
var allPaths []pathAndSource
|
||||||
|
|
||||||
|
for _, ps := range paths {
|
||||||
|
ps.path = strings.TrimRight(ps.path, pathSeparator)
|
||||||
|
if collisions := checkForOverlap(allPaths, ps); len(collisions) > 0 {
|
||||||
|
for _, c := range collisions {
|
||||||
|
warnings = append(warnings, fmt.Sprintf("%s with %s", ps.String(), c.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allPaths = append(allPaths, ps)
|
||||||
|
}
|
||||||
|
|
||||||
|
return warnings
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkForOverlap(haystack []pathAndSource, needle pathAndSource) []pathAndSource {
|
||||||
|
pathSeparator := string(os.PathSeparator)
|
||||||
|
|
||||||
|
if needle.path == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []pathAndSource
|
||||||
|
for _, item := range haystack {
|
||||||
|
switch {
|
||||||
|
case item.path == "":
|
||||||
|
continue
|
||||||
|
case item == needle:
|
||||||
|
result = append(result, item)
|
||||||
|
case strings.HasPrefix(item.path+pathSeparator, needle.path+pathSeparator):
|
||||||
|
result = append(result, item)
|
||||||
|
case strings.HasPrefix(needle.path+pathSeparator, item.path+pathSeparator):
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
@ -18,6 +18,8 @@ package pod
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
@ -143,6 +145,7 @@ func TestWarnings(t *testing.T) {
|
|||||||
api.ResourceMemory: resource.MustParse("4m"),
|
api.ResourceMemory: resource.MustParse("4m"),
|
||||||
api.ResourceEphemeralStorage: resource.MustParse("4m"),
|
api.ResourceEphemeralStorage: resource.MustParse("4m"),
|
||||||
}
|
}
|
||||||
|
testName := "Test"
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
name string
|
name string
|
||||||
template *api.PodTemplateSpec
|
template *api.PodTemplateSpec
|
||||||
@ -235,6 +238,464 @@ func TestWarnings(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expected: []string{`spec.volumes[0].rbd: deprecated in v1.28, non-functional in v1.31+`},
|
expected: []string{`spec.volumes[0].rbd: deprecated in v1.28, non-functional in v1.31+`},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in a configmap volume",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "Test",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
ConfigMap: &api.ConfigMapVolumeSource{
|
||||||
|
LocalObjectReference: api.LocalObjectReference{Name: "foo"},
|
||||||
|
Items: []api.KeyToPath{
|
||||||
|
{Key: "foo", Path: "test"},
|
||||||
|
{Key: "bar", Path: "test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "Test" (ConfigMap "foo"): overlapping paths: "test" with "test"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in a configmap volume - try to mount dir path into a file",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "Test",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
ConfigMap: &api.ConfigMapVolumeSource{
|
||||||
|
LocalObjectReference: api.LocalObjectReference{Name: "foo"},
|
||||||
|
Items: []api.KeyToPath{
|
||||||
|
{Key: "foo", Path: "test"},
|
||||||
|
{Key: "bar", Path: "test/app"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "Test" (ConfigMap "foo"): overlapping paths: "test/app" with "test"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in a configmap volume - try to mount file into a dir path",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "Test",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
ConfigMap: &api.ConfigMapVolumeSource{
|
||||||
|
LocalObjectReference: api.LocalObjectReference{Name: "foo"},
|
||||||
|
Items: []api.KeyToPath{
|
||||||
|
{Key: "bar", Path: "test/app"},
|
||||||
|
{Key: "foo", Path: "test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "Test" (ConfigMap "foo"): overlapping paths: "test" with "test/app"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in a secret volume",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "Test",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
Secret: &api.SecretVolumeSource{
|
||||||
|
SecretName: "foo",
|
||||||
|
Items: []api.KeyToPath{
|
||||||
|
{Key: "foo", Path: "test"},
|
||||||
|
{Key: "bar", Path: "test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "Test" (Secret "foo"): overlapping paths: "test" with "test"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in a downward api volume",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "Test",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
DownwardAPI: &api.DownwardAPIVolumeSource{
|
||||||
|
Items: []api.DownwardAPIVolumeFile{
|
||||||
|
{FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}, Path: "test"},
|
||||||
|
{FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.labels"}, Path: "test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "Test" (DownwardAPI): overlapping paths: "test" with "test"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in projected volume - service account and config",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "foo",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
Projected: &api.ProjectedVolumeSource{
|
||||||
|
Sources: []api.VolumeProjection{{
|
||||||
|
ConfigMap: &api.ConfigMapProjection{
|
||||||
|
LocalObjectReference: api.LocalObjectReference{Name: "Test"},
|
||||||
|
Items: []api.KeyToPath{
|
||||||
|
{Key: "foo", Path: "test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
ServiceAccountToken: &api.ServiceAccountTokenProjection{
|
||||||
|
Path: "test",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test" (ServiceAccountToken) with "test" (ConfigMap "Test")`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in projected volume volume: service account dir and config file",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "foo",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
Projected: &api.ProjectedVolumeSource{
|
||||||
|
Sources: []api.VolumeProjection{{
|
||||||
|
ConfigMap: &api.ConfigMapProjection{
|
||||||
|
LocalObjectReference: api.LocalObjectReference{Name: "Test"},
|
||||||
|
Items: []api.KeyToPath{
|
||||||
|
{Key: "foo", Path: "test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
ServiceAccountToken: &api.ServiceAccountTokenProjection{
|
||||||
|
Path: "test/file",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test/file" (ServiceAccountToken) with "test" (ConfigMap "Test")`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in projected volume - service account file and config dir",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "foo",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
Projected: &api.ProjectedVolumeSource{
|
||||||
|
Sources: []api.VolumeProjection{{
|
||||||
|
ConfigMap: &api.ConfigMapProjection{
|
||||||
|
LocalObjectReference: api.LocalObjectReference{Name: "Test"},
|
||||||
|
Items: []api.KeyToPath{
|
||||||
|
{Key: "foo", Path: "test/file"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
ServiceAccountToken: &api.ServiceAccountTokenProjection{
|
||||||
|
Path: "test",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test" (ServiceAccountToken) with "test/file" (ConfigMap "Test")`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in projected volume - service account and secret",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "foo",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
Projected: &api.ProjectedVolumeSource{
|
||||||
|
Sources: []api.VolumeProjection{{
|
||||||
|
Secret: &api.SecretProjection{
|
||||||
|
LocalObjectReference: api.LocalObjectReference{Name: "Test"},
|
||||||
|
Items: []api.KeyToPath{
|
||||||
|
{Key: "foo", Path: "test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
ServiceAccountToken: &api.ServiceAccountTokenProjection{
|
||||||
|
Path: "test",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test" (ServiceAccountToken) with "test" (Secret "Test")`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in projected volume - service account and downward api",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "foo",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
Projected: &api.ProjectedVolumeSource{
|
||||||
|
Sources: []api.VolumeProjection{{
|
||||||
|
DownwardAPI: &api.DownwardAPIProjection{
|
||||||
|
Items: []api.DownwardAPIVolumeFile{{
|
||||||
|
FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"},
|
||||||
|
Path: "test",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
ServiceAccountToken: &api.ServiceAccountTokenProjection{
|
||||||
|
Path: "test",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test" (ServiceAccountToken) with "test" (DownwardAPI)`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in projected volume - service account and cluster trust bundle",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "foo",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
Projected: &api.ProjectedVolumeSource{
|
||||||
|
Sources: []api.VolumeProjection{{
|
||||||
|
ClusterTrustBundle: &api.ClusterTrustBundleProjection{
|
||||||
|
Name: &testName, Path: "test",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
ServiceAccountToken: &api.ServiceAccountTokenProjection{
|
||||||
|
Path: "test",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test" (ServiceAccountToken) with "test" (ClusterTrustBundle "Test")`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in projected volume - service account and cluster trust bundle with signer name",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "foo",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
Projected: &api.ProjectedVolumeSource{
|
||||||
|
Sources: []api.VolumeProjection{{
|
||||||
|
ClusterTrustBundle: &api.ClusterTrustBundleProjection{
|
||||||
|
SignerName: &testName, Path: "test",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
ServiceAccountToken: &api.ServiceAccountTokenProjection{
|
||||||
|
Path: "test",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test" (ServiceAccountToken) with "test" (ClusterTrustBundle "Test")`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in projected volume - secret and config map",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "foo",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
Projected: &api.ProjectedVolumeSource{
|
||||||
|
Sources: []api.VolumeProjection{{
|
||||||
|
Secret: &api.SecretProjection{
|
||||||
|
LocalObjectReference: api.LocalObjectReference{Name: "TestSecret"},
|
||||||
|
Items: []api.KeyToPath{
|
||||||
|
{Key: "mykey", Path: "test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
ConfigMap: &api.ConfigMapProjection{
|
||||||
|
LocalObjectReference: api.LocalObjectReference{Name: "TestConfigMap"},
|
||||||
|
Items: []api.KeyToPath{
|
||||||
|
{Key: "mykey", Path: "test/test1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test/test1" (ConfigMap "TestConfigMap") with "test" (Secret "TestSecret")`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in projected volume - config map and downward api",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "foo",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
Projected: &api.ProjectedVolumeSource{
|
||||||
|
Sources: []api.VolumeProjection{{
|
||||||
|
Secret: &api.SecretProjection{
|
||||||
|
LocalObjectReference: api.LocalObjectReference{Name: "TestSecret"},
|
||||||
|
Items: []api.KeyToPath{
|
||||||
|
{Key: "mykey", Path: "test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
DownwardAPI: &api.DownwardAPIProjection{
|
||||||
|
Items: []api.DownwardAPIVolumeFile{{
|
||||||
|
FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"},
|
||||||
|
Path: "test/test2",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test/test2" (DownwardAPI) with "test" (Secret "TestSecret")`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in projected volume - downward api and cluster thrust bundle api",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "foo",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
Projected: &api.ProjectedVolumeSource{
|
||||||
|
Sources: []api.VolumeProjection{{
|
||||||
|
DownwardAPI: &api.DownwardAPIProjection{
|
||||||
|
Items: []api.DownwardAPIVolumeFile{{
|
||||||
|
FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"},
|
||||||
|
Path: "test/test2",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
ClusterTrustBundle: &api.ClusterTrustBundleProjection{
|
||||||
|
Name: &testName, Path: "test",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test" (ClusterTrustBundle "Test") with "test/test2" (DownwardAPI)`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in projected volume - multiple sources",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "foo",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
Projected: &api.ProjectedVolumeSource{
|
||||||
|
Sources: []api.VolumeProjection{{
|
||||||
|
ClusterTrustBundle: &api.ClusterTrustBundleProjection{
|
||||||
|
SignerName: &testName, Path: "test/test"},
|
||||||
|
}, {
|
||||||
|
DownwardAPI: &api.DownwardAPIProjection{
|
||||||
|
Items: []api.DownwardAPIVolumeFile{{
|
||||||
|
FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"},
|
||||||
|
Path: "test",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Secret: &api.SecretProjection{
|
||||||
|
LocalObjectReference: api.LocalObjectReference{Name: "Test"},
|
||||||
|
Items: []api.KeyToPath{
|
||||||
|
{Key: "foo", Path: "test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
ServiceAccountToken: &api.ServiceAccountTokenProjection{
|
||||||
|
Path: "test",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test" (DownwardAPI) with "test/test" (ClusterTrustBundle "Test")`,
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test" (Secret "Test") with "test/test" (ClusterTrustBundle "Test")`,
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test" (Secret "Test") with "test" (DownwardAPI)`,
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test" (ServiceAccountToken) with "test/test" (ClusterTrustBundle "Test")`,
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test" (ServiceAccountToken) with "test" (DownwardAPI)`,
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test" (ServiceAccountToken) with "test" (Secret "Test")`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlapping paths in projected volume - ServiceAccount vs. DownwardAPI",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "foo",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
Projected: &api.ProjectedVolumeSource{
|
||||||
|
Sources: []api.VolumeProjection{{
|
||||||
|
ServiceAccountToken: &api.ServiceAccountTokenProjection{
|
||||||
|
Path: "test/test2",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
DownwardAPI: &api.DownwardAPIProjection{
|
||||||
|
Items: []api.DownwardAPIVolumeFile{
|
||||||
|
{FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}, Path: "test"},
|
||||||
|
{FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}, Path: "test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test" (DownwardAPI) with "test/test2" (ServiceAccountToken)`,
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test" (DownwardAPI) with "test/test2" (ServiceAccountToken)`,
|
||||||
|
`volume "foo" (Projected): overlapping paths: "test" (DownwardAPI) with "test" (DownwardAPI)`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty sources in projected volume",
|
||||||
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{{
|
||||||
|
Name: "foo",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
Projected: &api.ProjectedVolumeSource{
|
||||||
|
Sources: []api.VolumeProjection{
|
||||||
|
{}, // one item, no fields set
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
expected: []string{
|
||||||
|
`volume "foo" (Projected) has no sources provided`,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "duplicate hostAlias",
|
name: "duplicate hostAlias",
|
||||||
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
template: &api.PodTemplateSpec{Spec: api.PodSpec{
|
||||||
@ -1067,13 +1528,17 @@ func TestWarnings(t *testing.T) {
|
|||||||
if tc.oldTemplate != nil {
|
if tc.oldTemplate != nil {
|
||||||
oldTemplate = tc.oldTemplate
|
oldTemplate = tc.oldTemplate
|
||||||
}
|
}
|
||||||
actual := sets.New[string](GetWarningsForPodTemplate(context.TODO(), nil, tc.template, oldTemplate)...)
|
actual := GetWarningsForPodTemplate(context.TODO(), nil, tc.template, oldTemplate)
|
||||||
expected := sets.New[string](tc.expected...)
|
if len(actual) != len(tc.expected) {
|
||||||
for _, missing := range sets.List[string](expected.Difference(actual)) {
|
t.Errorf("expected %d errors, got %d:\n%v", len(tc.expected), len(actual), strings.Join(actual, "\n"))
|
||||||
|
}
|
||||||
|
actualSet := sets.New(actual...)
|
||||||
|
expectedSet := sets.New(tc.expected...)
|
||||||
|
for _, missing := range sets.List(expectedSet.Difference(actualSet)) {
|
||||||
t.Errorf("missing: %s", missing)
|
t.Errorf("missing: %s", missing)
|
||||||
}
|
}
|
||||||
for _, extra := range sets.List[string](actual.Difference(expected)) {
|
for _, extra := range sets.List(actualSet.Difference(expectedSet)) {
|
||||||
t.Errorf("extra: %s", extra)
|
t.Errorf("extra: %s", extra)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1085,13 +1550,17 @@ func TestWarnings(t *testing.T) {
|
|||||||
Spec: tc.template.Spec,
|
Spec: tc.template.Spec,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actual := sets.New[string](GetWarningsForPod(context.TODO(), pod, &api.Pod{})...)
|
actual := GetWarningsForPod(context.TODO(), pod, &api.Pod{})
|
||||||
expected := sets.New[string](tc.expected...)
|
if len(actual) != len(tc.expected) {
|
||||||
for _, missing := range sets.List[string](expected.Difference(actual)) {
|
t.Errorf("expected %d errors, got %d:\n%v", len(tc.expected), len(actual), strings.Join(actual, "\n"))
|
||||||
|
}
|
||||||
|
actualSet := sets.New(actual...)
|
||||||
|
expectedSet := sets.New(tc.expected...)
|
||||||
|
for _, missing := range sets.List(expectedSet.Difference(actualSet)) {
|
||||||
t.Errorf("missing: %s", missing)
|
t.Errorf("missing: %s", missing)
|
||||||
}
|
}
|
||||||
for _, extra := range sets.List[string](actual.Difference(expected)) {
|
for _, extra := range sets.List(actualSet.Difference(expectedSet)) {
|
||||||
t.Errorf("extra: %s", extra)
|
t.Errorf("extra: %s", extra)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1183,3 +1652,112 @@ func TestTemplateOnlyWarnings(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCheckForOverLap(t *testing.T) {
|
||||||
|
testCase := map[string]struct {
|
||||||
|
checkPaths []pathAndSource
|
||||||
|
path pathAndSource
|
||||||
|
found bool
|
||||||
|
expected []pathAndSource
|
||||||
|
}{
|
||||||
|
"exact match": {
|
||||||
|
checkPaths: []pathAndSource{{"path/path1", "src1"}},
|
||||||
|
path: pathAndSource{"path/path1", "src2"},
|
||||||
|
found: true,
|
||||||
|
expected: []pathAndSource{{"path/path1", "src1"}},
|
||||||
|
},
|
||||||
|
"no match": {
|
||||||
|
checkPaths: []pathAndSource{{"path/path1", "src1"}},
|
||||||
|
path: pathAndSource{"path2/path1", "src2"},
|
||||||
|
found: false,
|
||||||
|
},
|
||||||
|
"empty checkPaths": {
|
||||||
|
checkPaths: []pathAndSource{},
|
||||||
|
path: pathAndSource{"path2/path1", "src2"},
|
||||||
|
found: false,
|
||||||
|
},
|
||||||
|
"empty string in checkPaths": {
|
||||||
|
checkPaths: []pathAndSource{{"", "src1"}},
|
||||||
|
path: pathAndSource{"path2/path1", "src2"},
|
||||||
|
found: false,
|
||||||
|
},
|
||||||
|
"empty path": {
|
||||||
|
checkPaths: []pathAndSource{{"test", "src1"}},
|
||||||
|
path: pathAndSource{"", ""},
|
||||||
|
found: false,
|
||||||
|
},
|
||||||
|
"empty strings in checkPaths and path": {
|
||||||
|
checkPaths: []pathAndSource{{"", "src1"}},
|
||||||
|
path: pathAndSource{"", ""},
|
||||||
|
expected: []pathAndSource{{"", ""}},
|
||||||
|
found: false,
|
||||||
|
},
|
||||||
|
"between file and dir": {
|
||||||
|
checkPaths: []pathAndSource{{"path/path1", "src1"}},
|
||||||
|
path: pathAndSource{"path", "src2"},
|
||||||
|
found: true,
|
||||||
|
expected: []pathAndSource{{"path/path1", "src1"}},
|
||||||
|
},
|
||||||
|
"between dir and file": {
|
||||||
|
checkPaths: []pathAndSource{{"path", "src1"}},
|
||||||
|
path: pathAndSource{"path/path1", "src2"},
|
||||||
|
found: true,
|
||||||
|
expected: []pathAndSource{{"path", "src1"}},
|
||||||
|
},
|
||||||
|
"multiple paths without overlap": {
|
||||||
|
checkPaths: []pathAndSource{{"path1/path", "src1"}, {"path2/path", "src2"}, {"path3/path", "src3"}},
|
||||||
|
path: pathAndSource{"path4/path", "src4"},
|
||||||
|
found: false,
|
||||||
|
},
|
||||||
|
"multiple paths with 1 overlap": {
|
||||||
|
checkPaths: []pathAndSource{{"path1/path", "src1"}, {"path2/path", "src2"}, {"path3/path", "src3"}},
|
||||||
|
path: pathAndSource{"path3/path", "src4"},
|
||||||
|
found: true,
|
||||||
|
expected: []pathAndSource{{"path3/path", "src3"}},
|
||||||
|
},
|
||||||
|
"multiple paths with multiple overlap": {
|
||||||
|
checkPaths: []pathAndSource{{"path/path1", "src1"}, {"path/path2", "src2"}, {"path/path3", "src3"}},
|
||||||
|
path: pathAndSource{"path", "src4"},
|
||||||
|
found: true,
|
||||||
|
expected: []pathAndSource{{"path/path1", "src1"}, {"path/path2", "src2"}, {"path/path3", "src3"}},
|
||||||
|
},
|
||||||
|
"partial overlap": {
|
||||||
|
checkPaths: []pathAndSource{{"path1/path", "src1"}, {"path2/path", "src2"}, {"path3/path", "src3"}},
|
||||||
|
path: pathAndSource{"path101/path3", "src4"},
|
||||||
|
found: false,
|
||||||
|
},
|
||||||
|
"partial overlap in path": {
|
||||||
|
checkPaths: []pathAndSource{{"dir/path1", "src1"}, {"dir/path2", "src2"}, {"dir/path3", "src3"}},
|
||||||
|
path: pathAndSource{"dir/path345", "src4"},
|
||||||
|
found: false,
|
||||||
|
},
|
||||||
|
"trailing slash in path": {
|
||||||
|
checkPaths: []pathAndSource{{"path1/path3", "src1"}},
|
||||||
|
path: pathAndSource{"path1/path3/", "src2"},
|
||||||
|
found: true,
|
||||||
|
expected: []pathAndSource{{"path1/path3", "src1"}},
|
||||||
|
},
|
||||||
|
"trailing slash in checkPaths": {
|
||||||
|
checkPaths: []pathAndSource{{"path1/path3/", "src1"}},
|
||||||
|
path: pathAndSource{"path1/path3", "src2"},
|
||||||
|
found: true,
|
||||||
|
expected: []pathAndSource{{"path1/path3/", "src1"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCase {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
result := checkForOverlap(tc.checkPaths, tc.path)
|
||||||
|
found := len(result) > 0
|
||||||
|
if found && !tc.found {
|
||||||
|
t.Errorf("unexpected match for %q: %q", tc.path, result)
|
||||||
|
}
|
||||||
|
if !found && tc.found {
|
||||||
|
t.Errorf("expected match for %q: %q", tc.path, tc.expected)
|
||||||
|
}
|
||||||
|
if tc.found && !reflect.DeepEqual(result, tc.expected) {
|
||||||
|
t.Errorf("expected %q, got %q", tc.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user