optimize checks for overlapping in projected

This commit is contained in:
Nikola 2024-07-22 20:50:05 +03:00
parent 1853742da6
commit e1178c4cf8
2 changed files with 418 additions and 445 deletions

View File

@ -20,7 +20,6 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"sort"
"strings" "strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -380,119 +379,149 @@ func warningsForWeightedPodAffinityTerms(terms []api.WeightedPodAffinityTerm, fi
func warningsForOverlappingVirtualPaths(volumes []api.Volume) []string { func warningsForOverlappingVirtualPaths(volumes []api.Volume) []string {
var warnings []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 { for _, v := range volumes {
if v.ConfigMap != nil && v.ConfigMap.Items != nil { if v.ConfigMap != nil && v.ConfigMap.Items != nil {
warnings = append(warnings, checkVolumeMappingForOverlap(extractPaths(v.ConfigMap.Items), fmt.Sprintf("volume %q (ConfigMap %q): overlapping paths", v.Name, v.ConfigMap.Name))...) 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 { if v.Secret != nil && v.Secret.Items != nil {
warnings = append(warnings, checkVolumeMappingForOverlap(extractPaths(v.Secret.Items), fmt.Sprintf("volume %q (Secret %q): overlapping paths", v.Name, v.Secret.SecretName))...) 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 { if v.DownwardAPI != nil && v.DownwardAPI.Items != nil {
warnings = append(warnings, checkVolumeMappingForOverlap(extractPathsDownwardAPI(v.DownwardAPI.Items), fmt.Sprintf("volume %q (DownwardAPI): overlapping paths", v.Name))...) overlaps := checkVolumeMappingForOverlap(extractPathsDownwardAPI(v.DownwardAPI.Items, ""))
for _, ol := range overlaps {
warnings = append(warnings, mkWarn(v.Name, "DownwardAPI", ol))
}
} }
if v.Projected != nil { if v.Projected != nil {
var sourcePaths []string var sourcePaths []pathAndSource
var errorMessage string var allPaths []pathAndSource
allPaths := sets.New[string]()
for _, source := range v.Projected.Sources { for _, source := range v.Projected.Sources {
if source == (api.VolumeProjection{}) { if source == (api.VolumeProjection{}) {
warnings = append(warnings, fmt.Sprintf("volume %q (Projected) has no sources provided", v.Name)) warnings = append(warnings, fmt.Sprintf("volume %q (Projected) has no sources provided", v.Name))
continue continue
} }
switch { switch {
case source.ConfigMap != nil && source.ConfigMap.Items != nil: case source.ConfigMap != nil && source.ConfigMap.Items != nil:
sourcePaths = extractPaths(source.ConfigMap.Items) sourcePaths = extractPaths(source.ConfigMap.Items, fmt.Sprintf("ConfigMap %q", source.ConfigMap.Name))
errorMessage = fmt.Sprintf("volume %q (Projected ConfigMap %q): overlapping paths", v.Name, source.ConfigMap.Name)
case source.Secret != nil && source.Secret.Items != nil: case source.Secret != nil && source.Secret.Items != nil:
sourcePaths = extractPaths(source.Secret.Items) sourcePaths = extractPaths(source.Secret.Items, fmt.Sprintf("Secret %q", source.Secret.Name))
errorMessage = fmt.Sprintf("volume %q (Projected Secret %q): overlapping paths", v.Name, source.Secret.Name)
case source.DownwardAPI != nil && source.DownwardAPI.Items != nil: case source.DownwardAPI != nil && source.DownwardAPI.Items != nil:
sourcePaths = extractPathsDownwardAPI(source.DownwardAPI.Items) sourcePaths = extractPathsDownwardAPI(source.DownwardAPI.Items, "DownwardAPI")
errorMessage = fmt.Sprintf("volume %q (Projected DownwardAPI): overlapping paths", v.Name)
case source.ServiceAccountToken != nil: case source.ServiceAccountToken != nil:
sourcePaths = []string{source.ServiceAccountToken.Path} sourcePaths = []pathAndSource{{source.ServiceAccountToken.Path, "ServiceAccountToken"}}
errorMessage = fmt.Sprintf("volume %q (Projected ServiceAccountToken): overlapping paths", v.Name)
case source.ClusterTrustBundle != nil: case source.ClusterTrustBundle != nil:
sourcePaths = []string{source.ClusterTrustBundle.Path} name := ""
if source.ClusterTrustBundle.Name != nil { if source.ClusterTrustBundle.Name != nil {
errorMessage = fmt.Sprintf("volume %q (Projected ClusterTrustBundle %q): overlapping paths", v.Name, *source.ClusterTrustBundle.Name) name = *source.ClusterTrustBundle.Name
} else { } else {
errorMessage = fmt.Sprintf("volume %q (Projected ClusterTrustBundle %q): overlapping paths", v.Name, *source.ClusterTrustBundle.SignerName) name = *source.ClusterTrustBundle.SignerName
} }
sourcePaths = []pathAndSource{{source.ClusterTrustBundle.Path, fmt.Sprintf("ClusterTrustBundle %q", name)}}
} }
if len(sourcePaths) == 0 { if len(sourcePaths) == 0 {
continue continue
} }
warnings = append(warnings, checkVolumeMappingForOverlap(sourcePaths, errorMessage)...) for _, ps := range sourcePaths {
ps.path = strings.TrimRight(ps.path, string(os.PathSeparator))
// remove duplicates path and sort the new array, so we can get predetermined result if collisions := checkForOverlap(allPaths, ps); len(collisions) > 0 {
uniqueSourcePaths := sets.New[string](sourcePaths...).UnsortedList() for _, c := range collisions {
orderedSourcePaths := append(uniqueSourcePaths, allPaths.UnsortedList()...) warnings = append(warnings, mkWarn(v.Name, "Projected", fmt.Sprintf("%s with %s", ps.String(), c.String())))
sort.Strings(orderedSourcePaths) }
warnings = append(warnings, checkVolumeMappingForOverlap(orderedSourcePaths, errorMessage)...) }
allPaths.Insert(uniqueSourcePaths...) allPaths = append(allPaths, ps)
}
} }
} }
} }
return warnings return warnings
} }
func extractPaths(mapping []api.KeyToPath) []string { // this lets us track a path and where it came from, for better errors
result := make([]string, 0, len(mapping)) 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 { for _, v := range mapping {
result = append(result, v.Path) result = append(result, pathAndSource{v.Path, source})
} }
return result return result
} }
func extractPathsDownwardAPI(mapping []api.DownwardAPIVolumeFile) []string { func extractPathsDownwardAPI(mapping []api.DownwardAPIVolumeFile, source string) []pathAndSource {
result := make([]string, 0, len(mapping)) result := make([]pathAndSource, 0, len(mapping))
for _, v := range mapping { for _, v := range mapping {
result = append(result, v.Path) result = append(result, pathAndSource{v.Path, source})
} }
return result return result
} }
func checkVolumeMappingForOverlap(paths []string, warningMessage string) []string { func checkVolumeMappingForOverlap(paths []pathAndSource) []string {
pathSeparator := string(os.PathSeparator)
var warnings []string var warnings []string
pathSeparator := string(os.PathSeparator) var allPaths []pathAndSource
uniquePaths := sets.New[string]()
for _, path := range paths { for _, ps := range paths {
normalizedPath := strings.TrimRight(path, pathSeparator) ps.path = strings.TrimRight(ps.path, pathSeparator)
if collision := checkForOverlap(uniquePaths, normalizedPath); collision != "" { if collisions := checkForOverlap(allPaths, ps); len(collisions) > 0 {
warnings = append(warnings, fmt.Sprintf("%s: %q with %q", warningMessage, normalizedPath, collision)) for _, c := range collisions {
warnings = append(warnings, fmt.Sprintf("%s with %s", ps.String(), c.String()))
} }
uniquePaths.Insert(normalizedPath) }
allPaths = append(allPaths, ps)
} }
return warnings return warnings
} }
func checkForOverlap(paths sets.Set[string], path string) string { func checkForOverlap(haystack []pathAndSource, needle pathAndSource) []pathAndSource {
pathSeparator := string(os.PathSeparator) pathSeparator := string(os.PathSeparator)
for item := range paths { if needle.path == "" {
return nil
}
var result []pathAndSource
for _, item := range haystack {
switch { switch {
case item == "" || path == "": case item.path == "":
return "" continue
case item == path: case item == needle:
return item result = append(result, item)
case strings.HasPrefix(item+pathSeparator, path): case strings.HasPrefix(item.path+pathSeparator, needle.path+pathSeparator):
return item result = append(result, item)
case strings.HasPrefix(path+pathSeparator, item): case strings.HasPrefix(needle.path+pathSeparator, item.path+pathSeparator):
return item result = append(result, item)
} }
} }
return "" return result
} }

View File

@ -18,6 +18,7 @@ package pod
import ( import (
"context" "context"
"reflect"
"strings" "strings"
"testing" "testing"
@ -240,8 +241,7 @@ func TestWarnings(t *testing.T) {
{ {
name: "overlapping paths in a configmap volume", name: "overlapping paths in a configmap volume",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "Test", Name: "Test",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
ConfigMap: &api.ConfigMapVolumeSource{ ConfigMap: &api.ConfigMapVolumeSource{
@ -252,8 +252,7 @@ func TestWarnings(t *testing.T) {
}, },
}, },
}, },
}, }},
},
}}, }},
expected: []string{ expected: []string{
`volume "Test" (ConfigMap "foo"): overlapping paths: "test" with "test"`, `volume "Test" (ConfigMap "foo"): overlapping paths: "test" with "test"`,
@ -262,8 +261,7 @@ func TestWarnings(t *testing.T) {
{ {
name: "overlapping paths in a configmap volume - try to mount dir path into a file", name: "overlapping paths in a configmap volume - try to mount dir path into a file",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "Test", Name: "Test",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
ConfigMap: &api.ConfigMapVolumeSource{ ConfigMap: &api.ConfigMapVolumeSource{
@ -274,8 +272,7 @@ func TestWarnings(t *testing.T) {
}, },
}, },
}, },
}, }},
},
}}, }},
expected: []string{ expected: []string{
`volume "Test" (ConfigMap "foo"): overlapping paths: "test/app" with "test"`, `volume "Test" (ConfigMap "foo"): overlapping paths: "test/app" with "test"`,
@ -284,8 +281,7 @@ func TestWarnings(t *testing.T) {
{ {
name: "overlapping paths in a configmap volume - try to mount file into a dir path", name: "overlapping paths in a configmap volume - try to mount file into a dir path",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "Test", Name: "Test",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
ConfigMap: &api.ConfigMapVolumeSource{ ConfigMap: &api.ConfigMapVolumeSource{
@ -296,8 +292,7 @@ func TestWarnings(t *testing.T) {
}, },
}, },
}, },
}, }},
},
}}, }},
expected: []string{ expected: []string{
`volume "Test" (ConfigMap "foo"): overlapping paths: "test" with "test/app"`, `volume "Test" (ConfigMap "foo"): overlapping paths: "test" with "test/app"`,
@ -306,8 +301,7 @@ func TestWarnings(t *testing.T) {
{ {
name: "overlapping paths in a secret volume", name: "overlapping paths in a secret volume",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "Test", Name: "Test",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
Secret: &api.SecretVolumeSource{ Secret: &api.SecretVolumeSource{
@ -318,8 +312,7 @@ func TestWarnings(t *testing.T) {
}, },
}, },
}, },
}, }},
},
}}, }},
expected: []string{ expected: []string{
`volume "Test" (Secret "foo"): overlapping paths: "test" with "test"`, `volume "Test" (Secret "foo"): overlapping paths: "test" with "test"`,
@ -328,8 +321,7 @@ func TestWarnings(t *testing.T) {
{ {
name: "overlapping paths in a downward api volume", name: "overlapping paths in a downward api volume",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "Test", Name: "Test",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
DownwardAPI: &api.DownwardAPIVolumeSource{ DownwardAPI: &api.DownwardAPIVolumeSource{
@ -339,8 +331,7 @@ func TestWarnings(t *testing.T) {
}, },
}, },
}, },
}, }},
},
}}, }},
expected: []string{ expected: []string{
`volume "Test" (DownwardAPI): overlapping paths: "test" with "test"`, `volume "Test" (DownwardAPI): overlapping paths: "test" with "test"`,
@ -349,426 +340,358 @@ func TestWarnings(t *testing.T) {
{ {
name: "overlapping paths in projected volume - service account and config", name: "overlapping paths in projected volume - service account and config",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "foo", Name: "foo",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
Projected: &api.ProjectedVolumeSource{ Projected: &api.ProjectedVolumeSource{
Sources: []api.VolumeProjection{ Sources: []api.VolumeProjection{{
{
ConfigMap: &api.ConfigMapProjection{ ConfigMap: &api.ConfigMapProjection{
LocalObjectReference: api.LocalObjectReference{Name: "Test"}, LocalObjectReference: api.LocalObjectReference{Name: "Test"},
Items: []api.KeyToPath{ Items: []api.KeyToPath{
{Key: "foo", Path: "test"}, {Key: "foo", Path: "test"},
}, },
}, },
}, }, {
{
ServiceAccountToken: &api.ServiceAccountTokenProjection{ ServiceAccountToken: &api.ServiceAccountTokenProjection{
Path: "test", Path: "test",
}, },
}, }},
},
},
},
}, },
}, },
}}, }},
}},
expected: []string{ expected: []string{
`volume "foo" (Projected ServiceAccountToken): overlapping paths: "test" with "test"`, `volume "foo" (Projected): overlapping paths: "test" (ServiceAccountToken) with "test" (ConfigMap "Test")`,
}, },
}, },
{ {
name: "overlapping paths in projected volume volume: service account dir and config file", name: "overlapping paths in projected volume volume: service account dir and config file",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "foo", Name: "foo",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
Projected: &api.ProjectedVolumeSource{ Projected: &api.ProjectedVolumeSource{
Sources: []api.VolumeProjection{ Sources: []api.VolumeProjection{{
{
ConfigMap: &api.ConfigMapProjection{ ConfigMap: &api.ConfigMapProjection{
LocalObjectReference: api.LocalObjectReference{Name: "Test"}, LocalObjectReference: api.LocalObjectReference{Name: "Test"},
Items: []api.KeyToPath{ Items: []api.KeyToPath{
{Key: "foo", Path: "test"}, {Key: "foo", Path: "test"},
}, },
}, },
}, }, {
{
ServiceAccountToken: &api.ServiceAccountTokenProjection{ ServiceAccountToken: &api.ServiceAccountTokenProjection{
Path: "test/file", Path: "test/file",
}, },
}, }},
},
},
},
}, },
}, },
}}, }},
}},
expected: []string{ expected: []string{
`volume "foo" (Projected ServiceAccountToken): overlapping paths: "test/file" with "test"`, `volume "foo" (Projected): overlapping paths: "test/file" (ServiceAccountToken) with "test" (ConfigMap "Test")`,
}, },
}, },
{ {
name: "overlapping paths in projected volume - service account file and config dir", name: "overlapping paths in projected volume - service account file and config dir",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "foo", Name: "foo",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
Projected: &api.ProjectedVolumeSource{ Projected: &api.ProjectedVolumeSource{
Sources: []api.VolumeProjection{ Sources: []api.VolumeProjection{{
{
ConfigMap: &api.ConfigMapProjection{ ConfigMap: &api.ConfigMapProjection{
LocalObjectReference: api.LocalObjectReference{Name: "Test"}, LocalObjectReference: api.LocalObjectReference{Name: "Test"},
Items: []api.KeyToPath{ Items: []api.KeyToPath{
{Key: "foo", Path: "test/file"}, {Key: "foo", Path: "test/file"},
}, },
}, },
}, }, {
{
ServiceAccountToken: &api.ServiceAccountTokenProjection{ ServiceAccountToken: &api.ServiceAccountTokenProjection{
Path: "test", Path: "test",
}, },
}, }},
},
},
},
}, },
}, },
}}, }},
}},
expected: []string{ expected: []string{
`volume "foo" (Projected ServiceAccountToken): overlapping paths: "test/file" with "test"`, `volume "foo" (Projected): overlapping paths: "test" (ServiceAccountToken) with "test/file" (ConfigMap "Test")`,
}, },
}, },
{ {
name: "overlapping paths in projected volume - service account and secret", name: "overlapping paths in projected volume - service account and secret",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "foo", Name: "foo",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
Projected: &api.ProjectedVolumeSource{ Projected: &api.ProjectedVolumeSource{
Sources: []api.VolumeProjection{ Sources: []api.VolumeProjection{{
{
Secret: &api.SecretProjection{ Secret: &api.SecretProjection{
LocalObjectReference: api.LocalObjectReference{Name: "Test"}, LocalObjectReference: api.LocalObjectReference{Name: "Test"},
Items: []api.KeyToPath{ Items: []api.KeyToPath{
{Key: "foo", Path: "test"}, {Key: "foo", Path: "test"},
}, },
}, },
}, }, {
{
ServiceAccountToken: &api.ServiceAccountTokenProjection{ ServiceAccountToken: &api.ServiceAccountTokenProjection{
Path: "test", Path: "test",
}, },
}, }},
},
},
},
}, },
}, },
}}, }},
}},
expected: []string{ expected: []string{
`volume "foo" (Projected ServiceAccountToken): overlapping paths: "test" with "test"`, `volume "foo" (Projected): overlapping paths: "test" (ServiceAccountToken) with "test" (Secret "Test")`,
}, },
}, },
{ {
name: "overlapping paths in projected volume - service account and downward api", name: "overlapping paths in projected volume - service account and downward api",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "foo", Name: "foo",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
Projected: &api.ProjectedVolumeSource{ Projected: &api.ProjectedVolumeSource{
Sources: []api.VolumeProjection{ Sources: []api.VolumeProjection{{
{
DownwardAPI: &api.DownwardAPIProjection{ DownwardAPI: &api.DownwardAPIProjection{
Items: []api.DownwardAPIVolumeFile{ Items: []api.DownwardAPIVolumeFile{{
{FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}, Path: "test"}, FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"},
Path: "test",
}},
}, },
}, }, {
},
{
ServiceAccountToken: &api.ServiceAccountTokenProjection{ ServiceAccountToken: &api.ServiceAccountTokenProjection{
Path: "test", Path: "test",
}, },
}, }},
},
},
},
}, },
}, },
}}, }},
}},
expected: []string{ expected: []string{
`volume "foo" (Projected ServiceAccountToken): overlapping paths: "test" with "test"`, `volume "foo" (Projected): overlapping paths: "test" (ServiceAccountToken) with "test" (DownwardAPI)`,
}, },
}, },
{ {
name: "overlapping paths in projected volume - service account and cluster trust bundle", name: "overlapping paths in projected volume - service account and cluster trust bundle",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "foo", Name: "foo",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
Projected: &api.ProjectedVolumeSource{ Projected: &api.ProjectedVolumeSource{
Sources: []api.VolumeProjection{ Sources: []api.VolumeProjection{{
{
ClusterTrustBundle: &api.ClusterTrustBundleProjection{ ClusterTrustBundle: &api.ClusterTrustBundleProjection{
Name: &testName, Path: "test", Name: &testName, Path: "test",
}, },
}, }, {
{
ServiceAccountToken: &api.ServiceAccountTokenProjection{ ServiceAccountToken: &api.ServiceAccountTokenProjection{
Path: "test", Path: "test",
}, },
}, }},
},
},
},
}, },
}, },
}}, }},
}},
expected: []string{ expected: []string{
`volume "foo" (Projected ServiceAccountToken): overlapping paths: "test" with "test"`, `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", name: "overlapping paths in projected volume - service account and cluster trust bundle with signer name",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "foo", Name: "foo",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
Projected: &api.ProjectedVolumeSource{ Projected: &api.ProjectedVolumeSource{
Sources: []api.VolumeProjection{ Sources: []api.VolumeProjection{{
{
ClusterTrustBundle: &api.ClusterTrustBundleProjection{ ClusterTrustBundle: &api.ClusterTrustBundleProjection{
SignerName: &testName, Path: "test", SignerName: &testName, Path: "test",
}, },
}, }, {
{
ServiceAccountToken: &api.ServiceAccountTokenProjection{ ServiceAccountToken: &api.ServiceAccountTokenProjection{
Path: "test", Path: "test",
}, },
}, }},
},
},
},
}, },
}, },
}}, }},
}},
expected: []string{ expected: []string{
`volume "foo" (Projected ServiceAccountToken): overlapping paths: "test" with "test"`, `volume "foo" (Projected): overlapping paths: "test" (ServiceAccountToken) with "test" (ClusterTrustBundle "Test")`,
}, },
}, },
{ {
name: "overlapping paths in projected volume - secret and config map", name: "overlapping paths in projected volume - secret and config map",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "foo", Name: "foo",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
Projected: &api.ProjectedVolumeSource{ Projected: &api.ProjectedVolumeSource{
Sources: []api.VolumeProjection{ Sources: []api.VolumeProjection{{
{
Secret: &api.SecretProjection{ Secret: &api.SecretProjection{
LocalObjectReference: api.LocalObjectReference{Name: "TestSecret"}, LocalObjectReference: api.LocalObjectReference{Name: "TestSecret"},
Items: []api.KeyToPath{ Items: []api.KeyToPath{
{ {Key: "mykey", Path: "test"},
Key: "mykey",
Path: "test",
}, },
}, },
}, }, {
},
{
ConfigMap: &api.ConfigMapProjection{ ConfigMap: &api.ConfigMapProjection{
LocalObjectReference: api.LocalObjectReference{Name: "TestConfigMap"}, LocalObjectReference: api.LocalObjectReference{Name: "TestConfigMap"},
Items: []api.KeyToPath{ Items: []api.KeyToPath{
{ {Key: "mykey", Path: "test/test1"},
Key: "mykey",
Path: "test/test1",
},
},
},
},
},
},
},
}, },
}, },
}}, }},
},
},
}},
}},
expected: []string{ expected: []string{
`volume "foo" (Projected ConfigMap "TestConfigMap"): overlapping paths: "test/test1" with "test"`, `volume "foo" (Projected): overlapping paths: "test/test1" (ConfigMap "TestConfigMap") with "test" (Secret "TestSecret")`,
}, },
}, },
{ {
name: "overlapping paths in projected volume - config map and downward api", name: "overlapping paths in projected volume - config map and downward api",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "foo", Name: "foo",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
Projected: &api.ProjectedVolumeSource{ Projected: &api.ProjectedVolumeSource{
Sources: []api.VolumeProjection{ Sources: []api.VolumeProjection{{
{
Secret: &api.SecretProjection{ Secret: &api.SecretProjection{
LocalObjectReference: api.LocalObjectReference{Name: "TestSecret"}, LocalObjectReference: api.LocalObjectReference{Name: "TestSecret"},
Items: []api.KeyToPath{ Items: []api.KeyToPath{
{ {Key: "mykey", Path: "test"},
Key: "mykey",
Path: "test",
}, },
}, },
}, }, {
},
{
DownwardAPI: &api.DownwardAPIProjection{ DownwardAPI: &api.DownwardAPIProjection{
Items: []api.DownwardAPIVolumeFile{ Items: []api.DownwardAPIVolumeFile{{
{FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}, Path: "test/test2"}, FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"},
}, Path: "test/test2",
}, }},
},
},
},
}, },
}},
}, },
}, },
}}, }},
}},
expected: []string{ expected: []string{
`volume "foo" (Projected DownwardAPI): overlapping paths: "test/test2" with "test"`, `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", name: "overlapping paths in projected volume - downward api and cluster thrust bundle api",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "foo", Name: "foo",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
Projected: &api.ProjectedVolumeSource{ Projected: &api.ProjectedVolumeSource{
Sources: []api.VolumeProjection{ Sources: []api.VolumeProjection{{
{
DownwardAPI: &api.DownwardAPIProjection{ DownwardAPI: &api.DownwardAPIProjection{
Items: []api.DownwardAPIVolumeFile{ Items: []api.DownwardAPIVolumeFile{{
{FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}, Path: "test/test2"}, FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"},
Path: "test/test2",
}},
}, },
}, }, {
},
{
ClusterTrustBundle: &api.ClusterTrustBundleProjection{ ClusterTrustBundle: &api.ClusterTrustBundleProjection{
Name: &testName, Path: "test", Name: &testName, Path: "test",
}, },
}, }},
},
},
},
}, },
}, },
}}, }},
}},
expected: []string{ expected: []string{
`volume "foo" (Projected ClusterTrustBundle "Test"): overlapping paths: "test/test2" with "test"`, `volume "foo" (Projected): overlapping paths: "test" (ClusterTrustBundle "Test") with "test/test2" (DownwardAPI)`,
}, },
}, },
{ {
name: "overlapping paths in projected volume - multiple sources", name: "overlapping paths in projected volume - multiple sources",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "foo", Name: "foo",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
Projected: &api.ProjectedVolumeSource{ Projected: &api.ProjectedVolumeSource{
Sources: []api.VolumeProjection{ Sources: []api.VolumeProjection{{
{
ClusterTrustBundle: &api.ClusterTrustBundleProjection{ ClusterTrustBundle: &api.ClusterTrustBundleProjection{
SignerName: &testName, Path: "test/test", SignerName: &testName, Path: "test/test"},
}, }, {
},
{
DownwardAPI: &api.DownwardAPIProjection{ DownwardAPI: &api.DownwardAPIProjection{
Items: []api.DownwardAPIVolumeFile{ Items: []api.DownwardAPIVolumeFile{{
{FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}, Path: "test"}, FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"},
Path: "test",
}},
}, },
}, }, {
},
{
Secret: &api.SecretProjection{ Secret: &api.SecretProjection{
LocalObjectReference: api.LocalObjectReference{Name: "Test"}, LocalObjectReference: api.LocalObjectReference{Name: "Test"},
Items: []api.KeyToPath{ Items: []api.KeyToPath{
{Key: "foo", Path: "test"}, {Key: "foo", Path: "test"},
}, },
}, },
}, }, {
{
ServiceAccountToken: &api.ServiceAccountTokenProjection{ ServiceAccountToken: &api.ServiceAccountTokenProjection{
Path: "test", Path: "test",
}, },
}, }},
},
},
},
}, },
}, },
}}, }},
}},
expected: []string{ expected: []string{
`volume "foo" (Projected DownwardAPI): overlapping paths: "test/test" with "test"`, `volume "foo" (Projected): overlapping paths: "test" (DownwardAPI) with "test/test" (ClusterTrustBundle "Test")`,
`volume "foo" (Projected Secret "Test"): overlapping paths: "test" with "test"`, `volume "foo" (Projected): overlapping paths: "test" (Secret "Test") with "test/test" (ClusterTrustBundle "Test")`,
`volume "foo" (Projected Secret "Test"): overlapping paths: "test/test" with "test"`, `volume "foo" (Projected): overlapping paths: "test" (Secret "Test") with "test" (DownwardAPI)`,
`volume "foo" (Projected ServiceAccountToken): overlapping paths: "test/test" with "test"`, `volume "foo" (Projected): overlapping paths: "test" (ServiceAccountToken) with "test/test" (ClusterTrustBundle "Test")`,
`volume "foo" (Projected ServiceAccountToken): overlapping paths: "test" with "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 - multiple sources", name: "overlapping paths in projected volume - ServiceAccount vs. DownwardAPI",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "foo", Name: "foo",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
Projected: &api.ProjectedVolumeSource{ Projected: &api.ProjectedVolumeSource{
Sources: []api.VolumeProjection{ Sources: []api.VolumeProjection{{
{
ServiceAccountToken: &api.ServiceAccountTokenProjection{ ServiceAccountToken: &api.ServiceAccountTokenProjection{
Path: "test/test2", Path: "test/test2",
}, },
}, }, {
{
DownwardAPI: &api.DownwardAPIProjection{ DownwardAPI: &api.DownwardAPIProjection{
Items: []api.DownwardAPIVolumeFile{ Items: []api.DownwardAPIVolumeFile{
{FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}, Path: "test"}, {FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}, Path: "test"},
{FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}, Path: "test"}, {FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.name"}, Path: "test"},
}, },
}, },
}, }},
},
},
},
}, },
}, },
}}, }},
}},
expected: []string{ expected: []string{
`volume "foo" (Projected DownwardAPI): overlapping paths: "test" with "test"`, `volume "foo" (Projected): overlapping paths: "test" (DownwardAPI) with "test/test2" (ServiceAccountToken)`,
`volume "foo" (Projected DownwardAPI): overlapping paths: "test/test2" with "test"`, `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", name: "empty sources in projected volume",
template: &api.PodTemplateSpec{Spec: api.PodSpec{ template: &api.PodTemplateSpec{Spec: api.PodSpec{
Volumes: []api.Volume{ Volumes: []api.Volume{{
{
Name: "foo", Name: "foo",
VolumeSource: api.VolumeSource{ VolumeSource: api.VolumeSource{
Projected: &api.ProjectedVolumeSource{ Projected: &api.ProjectedVolumeSource{
Sources: []api.VolumeProjection{ Sources: []api.VolumeProjection{
{}, {}, // one item, no fields set
},
},
}, },
}, },
}, },
}}, }},
}},
expected: []string{ expected: []string{
`volume "foo" (Projected) has no sources provided`, `volume "foo" (Projected) has no sources provided`,
}, },
@ -1732,86 +1655,107 @@ func TestTemplateOnlyWarnings(t *testing.T) {
func TestCheckForOverLap(t *testing.T) { func TestCheckForOverLap(t *testing.T) {
testCase := map[string]struct { testCase := map[string]struct {
checkPaths []string checkPaths []pathAndSource
path string path pathAndSource
expected string found bool
expected []pathAndSource
}{ }{
"exact match": { "exact match": {
checkPaths: []string{"path/path1"}, checkPaths: []pathAndSource{{"path/path1", "src1"}},
path: "path/path1", path: pathAndSource{"path/path1", "src2"},
expected: "path/path1", found: true,
expected: []pathAndSource{{"path/path1", "src1"}},
}, },
"no match": { "no match": {
checkPaths: []string{"path/path1"}, checkPaths: []pathAndSource{{"path/path1", "src1"}},
path: "path2/path1", path: pathAndSource{"path2/path1", "src2"},
expected: "", found: false,
}, },
"empty checkPaths": { "empty checkPaths": {
checkPaths: []string{}, checkPaths: []pathAndSource{},
path: "path2/path1", path: pathAndSource{"path2/path1", "src2"},
expected: "", found: false,
}, },
"empty string in checkPaths": { "empty string in checkPaths": {
checkPaths: []string{""}, checkPaths: []pathAndSource{{"", "src1"}},
path: "path2/path1", path: pathAndSource{"path2/path1", "src2"},
expected: "", found: false,
}, },
"empty path": { "empty path": {
checkPaths: []string{"test"}, checkPaths: []pathAndSource{{"test", "src1"}},
path: "", path: pathAndSource{"", ""},
expected: "", found: false,
}, },
"empty strings in checkPaths and path": { "empty strings in checkPaths and path": {
checkPaths: []string{""}, checkPaths: []pathAndSource{{"", "src1"}},
path: "", path: pathAndSource{"", ""},
expected: "", expected: []pathAndSource{{"", ""}},
found: false,
}, },
"between file and dir": { "between file and dir": {
checkPaths: []string{"path/path1"}, checkPaths: []pathAndSource{{"path/path1", "src1"}},
path: "path", path: pathAndSource{"path", "src2"},
expected: "path/path1", found: true,
expected: []pathAndSource{{"path/path1", "src1"}},
}, },
"between dir and file": { "between dir and file": {
checkPaths: []string{"path"}, checkPaths: []pathAndSource{{"path", "src1"}},
path: "path/path1", path: pathAndSource{"path/path1", "src2"},
expected: "path", found: true,
expected: []pathAndSource{{"path", "src1"}},
}, },
"multiple paths without overlap": { "multiple paths without overlap": {
checkPaths: []string{"path1/path", "path2/path", "path3/path"}, checkPaths: []pathAndSource{{"path1/path", "src1"}, {"path2/path", "src2"}, {"path3/path", "src3"}},
path: "path4/path", path: pathAndSource{"path4/path", "src4"},
expected: "", found: false,
}, },
"multiple paths with overlap": { "multiple paths with 1 overlap": {
checkPaths: []string{"path1/path", "path2/path", "path3/path"}, checkPaths: []pathAndSource{{"path1/path", "src1"}, {"path2/path", "src2"}, {"path3/path", "src3"}},
path: "path3/path3", path: pathAndSource{"path3/path", "src4"},
expected: "path3/path", 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": { "partial overlap": {
checkPaths: []string{"path1/path", "path2/path", "path3/path"}, checkPaths: []pathAndSource{{"path1/path", "src1"}, {"path2/path", "src2"}, {"path3/path", "src3"}},
path: "path101/path3", path: pathAndSource{"path101/path3", "src4"},
expected: "", found: false,
}, },
"partial overlap in path": { "partial overlap in path": {
checkPaths: []string{"path101/path", "path2/path", "path3/path"}, checkPaths: []pathAndSource{{"dir/path1", "src1"}, {"dir/path2", "src2"}, {"dir/path3", "src3"}},
path: "path1/path3", path: pathAndSource{"dir/path345", "src4"},
expected: "", found: false,
}, },
"trailing slash in path": { "trailing slash in path": {
checkPaths: []string{"path1/path3"}, checkPaths: []pathAndSource{{"path1/path3", "src1"}},
path: "path1/path3/", path: pathAndSource{"path1/path3/", "src2"},
expected: "path1/path3", found: true,
expected: []pathAndSource{{"path1/path3", "src1"}},
}, },
"trailing slash in checkPaths": { "trailing slash in checkPaths": {
checkPaths: []string{"path1/path3/"}, checkPaths: []pathAndSource{{"path1/path3/", "src1"}},
path: "path1/path3", path: pathAndSource{"path1/path3", "src2"},
expected: "path1/path3/", found: true,
expected: []pathAndSource{{"path1/path3/", "src1"}},
}, },
} }
for name, tc := range testCase { for name, tc := range testCase {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
result := checkForOverlap(sets.New(tc.checkPaths...), tc.path) result := checkForOverlap(tc.checkPaths, tc.path)
if result != tc.expected { 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) t.Errorf("expected %q, got %q", tc.expected, result)
} }
}) })