Skip to content

Commit 7201e2c

Browse files
authored
Merge pull request #1017 from fluxcd/kustomize-ignore-components
kustomize: Add `ignoreMissingComponents` option
2 parents a5e02ce + 929f4b4 commit 7201e2c

2 files changed

Lines changed: 129 additions & 11 deletions

File tree

kustomize/kustomize_generator.go

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,18 @@ import (
4545
)
4646

4747
const (
48-
specField = "spec"
49-
targetNSField = "targetNamespace"
50-
resourcesField = "resources"
51-
patchesField = "patches"
52-
componentsField = "components"
53-
crdsField = "crds"
54-
patchesSMField = "patchesStrategicMerge"
55-
patchesJson6902Field = "patchesJson6902"
56-
imagesField = "images"
57-
namePrefixField = "namePrefix"
58-
nameSuffixField = "nameSuffix"
48+
specField = "spec"
49+
targetNSField = "targetNamespace"
50+
resourcesField = "resources"
51+
patchesField = "patches"
52+
componentsField = "components"
53+
ignoreComponentsField = "ignoreMissingComponents"
54+
crdsField = "crds"
55+
patchesSMField = "patchesStrategicMerge"
56+
patchesJson6902Field = "patchesJson6902"
57+
imagesField = "images"
58+
namePrefixField = "namePrefix"
59+
nameSuffixField = "nameSuffix"
5960
)
6061

6162
// Action is the action that was taken on the kustomization file
@@ -237,10 +238,14 @@ func (g *Generator) WriteFile(dirPath string, opts ...SavingOptions) (Action, er
237238
return action, fmt.Errorf("unable to get components: %w", fmt.Errorf("%v %v", err, errf))
238239
}
239240

241+
ignoreMissing := g.getNestedBool(specField, ignoreComponentsField)
240242
for _, component := range components {
241243
if !IsLocalRelativePath(component) {
242244
return "", fmt.Errorf("component path '%s' must be local and relative", component)
243245
}
246+
if _, err := os.Stat(filepath.Join(dirPath, component)); errors.Is(err, os.ErrNotExist) && ignoreMissing {
247+
continue
248+
}
244249
kus.Components = append(kus.Components, component)
245250
}
246251

@@ -458,6 +463,11 @@ func (g *Generator) getNestedStringSlice(fields ...string) ([]string, bool, erro
458463
return val, ok, nil
459464
}
460465

466+
func (g *Generator) getNestedBool(fields ...string) bool {
467+
val, _, _ := unstructured.NestedBool(g.kustomization.Object, fields...)
468+
return val
469+
}
470+
461471
func (g *Generator) getNestedSlice(fields ...string) ([]interface{}, bool, error) {
462472
val, ok, err := unstructured.NestedSlice(g.kustomization.Object, fields...)
463473
if err != nil {

kustomize/kustomize_generator_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,114 @@ func Test_IsLocalRelativePath(t *testing.T) {
294294
}
295295
}
296296

297+
func Test_IgnoreMissingComponents(t *testing.T) {
298+
tests := []struct {
299+
name string
300+
components []string
301+
ignoreMissingComponents bool
302+
expectError bool
303+
expectedComponents []string
304+
}{
305+
{
306+
name: "missing components with ignore enabled",
307+
components: []string{"existing-component", "missing-component"},
308+
ignoreMissingComponents: true,
309+
expectError: false,
310+
expectedComponents: []string{"existing-component"},
311+
},
312+
{
313+
name: "all components missing with ignore enabled",
314+
components: []string{"missing-component-1", "missing-component-2"},
315+
ignoreMissingComponents: true,
316+
expectError: false,
317+
expectedComponents: nil,
318+
},
319+
{
320+
name: "all components exist",
321+
components: []string{"existing-component"},
322+
ignoreMissingComponents: true,
323+
expectError: false,
324+
expectedComponents: []string{"existing-component"},
325+
},
326+
{
327+
name: "missing components with ignore disabled - should fail during build",
328+
components: []string{"existing-component", "missing-component"},
329+
ignoreMissingComponents: false,
330+
expectError: true,
331+
expectedComponents: []string{"existing-component", "missing-component"},
332+
},
333+
}
334+
335+
for _, tt := range tests {
336+
t.Run(tt.name, func(t *testing.T) {
337+
g := NewWithT(t)
338+
baseDir, err := testTempDir(t)
339+
g.Expect(err).ToNot(HaveOccurred())
340+
341+
// Create an existing component directory in the base directory
342+
existingComponentDir := filepath.Join(baseDir, "existing-component")
343+
g.Expect(os.MkdirAll(existingComponentDir, 0755)).To(Succeed())
344+
componentYaml := `apiVersion: kustomize.config.k8s.io/v1alpha1
345+
kind: Component
346+
resources: []
347+
`
348+
g.Expect(os.WriteFile(filepath.Join(existingComponentDir, "kustomization.yaml"), []byte(componentYaml), 0644)).To(Succeed())
349+
350+
// Create overlay directory where kustomization.yaml will be generated
351+
overlayDir := filepath.Join(baseDir, "overlay")
352+
g.Expect(os.MkdirAll(overlayDir, 0755)).To(Succeed())
353+
354+
// Update component paths to be relative from overlay directory
355+
var overlayComponents []string
356+
for _, comp := range tt.components {
357+
overlayComponents = append(overlayComponents, "../"+comp)
358+
}
359+
360+
// Update expected components to match the relative paths
361+
var expectedOverlayComponents []any
362+
if tt.expectedComponents != nil {
363+
for _, comp := range tt.expectedComponents {
364+
expectedOverlayComponents = append(expectedOverlayComponents, "../"+comp)
365+
}
366+
}
367+
368+
// Create Flux Kustomization object with relative paths
369+
ks := unstructured.Unstructured{Object: map[string]any{}}
370+
err = unstructured.SetNestedStringSlice(ks.Object, overlayComponents, "spec", "components")
371+
g.Expect(err).ToNot(HaveOccurred())
372+
err = unstructured.SetNestedField(ks.Object, tt.ignoreMissingComponents, "spec", "ignoreMissingComponents")
373+
g.Expect(err).ToNot(HaveOccurred())
374+
375+
// Generate kustomization in the overlay directory
376+
_, err = kustomize.NewGenerator(overlayDir, ks).WriteFile(overlayDir)
377+
g.Expect(err).ToNot(HaveOccurred())
378+
379+
// Read generated kustomization.yaml
380+
kfileYAML, err := os.ReadFile(filepath.Join(overlayDir, "kustomization.yaml"))
381+
g.Expect(err).ToNot(HaveOccurred())
382+
var k any
383+
g.Expect(yaml.Unmarshal(kfileYAML, &k)).To(Succeed())
384+
385+
// Check components field against the expected overlay components
386+
components := k.(map[string]any)["components"]
387+
if expectedOverlayComponents == nil {
388+
g.Expect(components).To(BeNil())
389+
} else {
390+
g.Expect(components).To(Equal(expectedOverlayComponents))
391+
}
392+
393+
// Build the kustomization
394+
_, buildErr := kustomize.SecureBuild(baseDir, overlayDir, false)
395+
if tt.expectError {
396+
g.Expect(buildErr).To(HaveOccurred())
397+
g.Expect(buildErr.Error()).To(ContainSubstring("missing-component"))
398+
} else {
399+
g.Expect(buildErr).ToNot(HaveOccurred())
400+
}
401+
})
402+
}
403+
}
404+
297405
func testTempDir(t *testing.T) (string, error) {
298406
tmpDir := t.TempDir()
299407

0 commit comments

Comments
 (0)