Skip to content

Commit 3ecaa0e

Browse files
feat(push): add media types mapping
1 parent 5b025a1 commit 3ecaa0e

File tree

2 files changed

+242
-0
lines changed

2 files changed

+242
-0
lines changed

api/internal/oci/pusher.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package oci
55

66
import (
77
"log"
8+
"net/url"
9+
"path/filepath"
810

911
"github.com/distribution/reference"
1012
"sigs.k8s.io/kustomize/api/types"
@@ -20,6 +22,77 @@ type PushOptions struct {
2022
annotations map[string]string
2123
}
2224

25+
func validatePath(path string, elementType string) error {
26+
if path == "" {
27+
return nil
28+
}
29+
30+
u, err := url.Parse(path)
31+
if err != nil {
32+
return err
33+
}
34+
if u.Scheme == "file" && u.Host == "" {
35+
return errors.Errorf("Path %s in element %s is a file url relative to localhost", path, elementType)
36+
} else if u.IsAbs() || u.Host != "" {
37+
// Other schemes or host-rooted URLs are assumed to be valid....
38+
return nil
39+
}
40+
41+
path = u.Path
42+
43+
if !filepath.IsLocal(path) {
44+
return errors.Errorf("Path '%s' in element %s is not local", path, elementType)
45+
}
46+
47+
return nil
48+
}
49+
50+
func iteratePathElementsSimple(elements []string, elementType string, errors []error) []error {
51+
return iteratePathElements(elements, func(x string) string { return x }, elementType, errors)
52+
}
53+
54+
func iteratePathElements[T any](elements []T, fn func(T) string, elementType string, errors []error) []error {
55+
for _, element := range elements {
56+
path := fn(element)
57+
58+
if err := validatePath(path, elementType); err != nil {
59+
errors = append(errors, err)
60+
}
61+
}
62+
63+
return errors
64+
}
65+
66+
func validateFilePaths(k *types.Kustomization) *[]error {
67+
errors := []error{}
68+
69+
errors = iteratePathElementsSimple(k.Components, "Components", errors)
70+
errors = iteratePathElements(k.Patches, func(x types.Patch) string { return x.Path }, "Patches", errors)
71+
errors = iteratePathElements(k.Replacements, func(x types.ReplacementField) string { return x.Path }, "Replacements", errors)
72+
errors = iteratePathElementsSimple(k.Resources, "Resources", errors)
73+
errors = iteratePathElementsSimple(k.Crds, "Crds", errors)
74+
75+
for _, generator := range k.ConfigMapGenerator {
76+
errors = iteratePathElementsSimple(generator.EnvSources, "ConfigMapGenerator", errors)
77+
errors = iteratePathElementsSimple(generator.FileSources, "ConfigMapGenerator", errors)
78+
}
79+
for _, generator := range k.SecretGenerator {
80+
errors = iteratePathElementsSimple(generator.EnvSources, "SecretGenerator", errors)
81+
errors = iteratePathElementsSimple(generator.FileSources, "SecretGenerator", errors)
82+
}
83+
84+
for _, charts := range k.HelmCharts {
85+
errors = iteratePathElementsSimple(append(charts.AdditionalValuesFiles, charts.ValuesFile), "HelmCharts", errors)
86+
}
87+
88+
errors = iteratePathElementsSimple(k.Configurations, "Configurations", errors)
89+
errors = iteratePathElementsSimple(k.Generators, "Generators", errors)
90+
errors = iteratePathElementsSimple(k.Transformers, "Transformers", errors)
91+
errors = iteratePathElementsSimple(k.Validators, "Validators", errors)
92+
93+
return &errors
94+
}
95+
2396
func PushToOciRegistries(options *PushOptions) error {
2497
// ctx := context.Background()
2598

@@ -55,6 +128,16 @@ func PushToOciRegistries(options *PushOptions) error {
55128
return errors.Errorf("kustFileName %s was a directory", options.kustFileName)
56129
}
57130

131+
// We attempt to perform validation that the paths are either remote URLs or in the root of the kustomization file.
132+
// There are limitations:
133+
// - This doesn't prevent someone manually creating an invalid artifact, so the reader still has to perform its own validations
134+
// - We can only examine the root kustomization. If there are nested kustomization definitions, we (currently) have to skip those.
135+
// - We can only examine the kustomization elements. If there are resource definitions that reference an invalid file path, we will never see it.
136+
// - The paths discovered here are only for validation - the actual list of files added to the artifact will be gathered by walking the directory
137+
if pathErrors := validateFilePaths(options.kustomization); pathErrors != nil && len(*pathErrors) > 0 {
138+
return errors.Errorf("kustomization includes non-local file paths: %v", pathErrors)
139+
}
140+
58141
// var dir string = filepath.Dir(mf.GetPath())
59142

60143
// fs, err := file.New("")

api/internal/oci/pusher_test.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,165 @@ func TestKustomizationFileMustNotBeDirectory(t *testing.T) {
295295
require.EqualError(t, err, fmt.Sprintf("kustFileName %s was a directory", absPath))
296296
}
297297

298+
func TestKustomizationFilePathsMustBeLocalToDirectory(t *testing.T) {
299+
fields := map[string]struct {
300+
fieldName string
301+
factory func(string) types.Kustomization
302+
}{
303+
"components": {
304+
"Components",
305+
func(p string) types.Kustomization {
306+
return types.Kustomization{
307+
Components: []string{p},
308+
}
309+
},
310+
},
311+
"resources": {
312+
"Resources",
313+
func(p string) types.Kustomization {
314+
return types.Kustomization{
315+
Resources: []string{p},
316+
}
317+
},
318+
},
319+
"crds": {
320+
"Crds",
321+
func(p string) types.Kustomization {
322+
return types.Kustomization{
323+
Crds: []string{p},
324+
}
325+
},
326+
},
327+
"configurations": {
328+
"Configurations",
329+
func(p string) types.Kustomization {
330+
return types.Kustomization{
331+
Configurations: []string{p},
332+
}
333+
},
334+
},
335+
"generators": {
336+
"Generators",
337+
func(p string) types.Kustomization {
338+
return types.Kustomization{
339+
Generators: []string{p},
340+
}
341+
},
342+
},
343+
"transformers": {
344+
"Transformers",
345+
func(p string) types.Kustomization {
346+
return types.Kustomization{
347+
Transformers: []string{p},
348+
}
349+
},
350+
},
351+
"validators": {
352+
"Validators",
353+
func(p string) types.Kustomization {
354+
return types.Kustomization{
355+
Validators: []string{p},
356+
}
357+
},
358+
},
359+
"patches": {
360+
"Patches",
361+
func(p string) types.Kustomization {
362+
return types.Kustomization{
363+
Patches: []types.Patch{{Path: p}},
364+
}
365+
},
366+
},
367+
"replacements": {
368+
"Replacements",
369+
func(p string) types.Kustomization {
370+
return types.Kustomization{
371+
Replacements: []types.ReplacementField{{Path: p}},
372+
}
373+
},
374+
},
375+
"configMapGenerator files": {
376+
"ConfigMapGenerator",
377+
func(p string) types.Kustomization {
378+
return types.Kustomization{
379+
ConfigMapGenerator: []types.ConfigMapArgs{{GeneratorArgs: types.GeneratorArgs{KvPairSources: types.KvPairSources{FileSources: []string{p}}}}},
380+
}
381+
},
382+
},
383+
"configMapGenerator envs": {
384+
"ConfigMapGenerator",
385+
func(p string) types.Kustomization {
386+
return types.Kustomization{
387+
ConfigMapGenerator: []types.ConfigMapArgs{{GeneratorArgs: types.GeneratorArgs{KvPairSources: types.KvPairSources{EnvSources: []string{p}}}}},
388+
}
389+
},
390+
},
391+
"secretGenerator files": {
392+
"SecretGenerator",
393+
func(p string) types.Kustomization {
394+
return types.Kustomization{
395+
SecretGenerator: []types.SecretArgs{{GeneratorArgs: types.GeneratorArgs{KvPairSources: types.KvPairSources{FileSources: []string{p}}}}},
396+
}
397+
},
398+
},
399+
"SecretGenerator envs": {
400+
"SecretGenerator",
401+
func(p string) types.Kustomization {
402+
return types.Kustomization{
403+
SecretGenerator: []types.SecretArgs{{GeneratorArgs: types.GeneratorArgs{KvPairSources: types.KvPairSources{EnvSources: []string{p}}}}},
404+
}
405+
},
406+
},
407+
"helmCharts valuesFile": {
408+
"HelmCharts",
409+
func(p string) types.Kustomization {
410+
return types.Kustomization{
411+
HelmCharts: []types.HelmChart{{ValuesFile: p}},
412+
}
413+
},
414+
},
415+
"helmCharts additionalValuesFile": {
416+
"HelmCharts",
417+
func(p string) types.Kustomization {
418+
return types.Kustomization{
419+
HelmCharts: []types.HelmChart{{AdditionalValuesFiles: []string{p}}},
420+
}
421+
},
422+
},
423+
}
424+
paths := map[string]string{
425+
// "invalid fileurl": "file://asdfsd/something.txt",
426+
"parent directory": "..",
427+
}
428+
429+
for fieldName, generator := range fields {
430+
431+
for pathName, path := range paths {
432+
t.Run(fieldName+"|"+pathName, func(t *testing.T) {
433+
kustname := "kustomization.yaml"
434+
files := map[string]string{
435+
filepath.Join("src", kustname): `namePrefix: test-
436+
`,
437+
}
438+
439+
dummy, _, dir := loctest.PrepareFs(t, []string{"src"}, files)
440+
kustomization := generator.factory(path)
441+
442+
pushOptions := PushOptions{
443+
fSys: dummy,
444+
kustFileName: filepath.Join(dir.String(), "src", kustname),
445+
kustomization: &kustomization,
446+
targets: []reference.NamedTagged{AsNamedTagged("registry.domain/something", "sometag")},
447+
}
448+
449+
err := PushToOciRegistries(&pushOptions)
450+
require.ErrorContains(t, err, "kustomization includes non-local file paths")
451+
require.ErrorContains(t, err, fmt.Sprintf("Path '%s' in element %s is not local", path, generator.fieldName))
452+
})
453+
}
454+
}
455+
}
456+
298457
// func TestFnContainerTransformerWithConfig(t *testing.T) {
299458

300459
// kustomization := map[string]string{

0 commit comments

Comments
 (0)