diff --git a/docs/api-reference/landscapekit-v1alpha1.md b/docs/api-reference/landscapekit-v1alpha1.md
index 0e58c231..9b23792e 100644
--- a/docs/api-reference/landscapekit-v1alpha1.md
+++ b/docs/api-reference/landscapekit-v1alpha1.md
@@ -81,6 +81,23 @@ _Appears in:_
+#### MergeMode
+
+_Underlying type:_ _string_
+
+MergeMode controls how operator overwrites are handled during three-way merge.
+
+
+
+_Appears in:_
+- [LandscapeKitConfiguration](#landscapekitconfiguration)
+
+| Field | Description |
+| --- | --- |
+| `Hint` | MergeModeHint annotates operator-overwritten values with a comment showing the current GLK default.
|
+| `Silent` | MergeModeSilent retains operator overwrites without annotation.
|
+
+
#### OCMComponent
diff --git a/example/20-componentconfig-glk.yaml b/example/20-componentconfig-glk.yaml
index 2eab32f8..77b010d7 100644
--- a/example/20-componentconfig-glk.yaml
+++ b/example/20-componentconfig-glk.yaml
@@ -19,3 +19,4 @@ kind: LandscapeKitConfiguration
# - component-name
# versionConfig:
# defaultVersionsUpdateStrategy: ReleaseBranch
+# mergeMode: Hint
diff --git a/hack/tools/prettify/main.go b/hack/tools/prettify/main.go
index dbd33c7a..1b278375 100644
--- a/hack/tools/prettify/main.go
+++ b/hack/tools/prettify/main.go
@@ -11,6 +11,7 @@ import (
flag "github.com/spf13/pflag"
+ configv1alpha1 "github.com/gardener/gardener-landscape-kit/pkg/apis/config/v1alpha1"
"github.com/gardener/gardener-landscape-kit/pkg/utils/meta"
)
@@ -31,7 +32,7 @@ func main() {
if err != nil {
log.Fatalf("Error reading file: %s", err)
}
- prettified, err := meta.ThreeWayMergeManifest(nil, content, nil)
+ prettified, err := meta.ThreeWayMergeManifest(nil, content, nil, configv1alpha1.MergeModeSilent)
if err != nil {
log.Fatalf("Marshalling failed: %s", err)
}
diff --git a/pkg/apis/config/v1alpha1/defaults.go b/pkg/apis/config/v1alpha1/defaults.go
index 9c3a743a..0e151fef 100644
--- a/pkg/apis/config/v1alpha1/defaults.go
+++ b/pkg/apis/config/v1alpha1/defaults.go
@@ -3,3 +3,17 @@
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
+
+// SetDefaults_LandscapeKitConfiguration sets default values for LandscapeKitConfiguration fields.
+func SetDefaults_LandscapeKitConfiguration(obj *LandscapeKitConfiguration) {
+ if obj.VersionConfig == nil {
+ obj.VersionConfig = &VersionConfiguration{}
+ }
+ if obj.VersionConfig.DefaultVersionsUpdateStrategy == nil {
+ obj.VersionConfig.DefaultVersionsUpdateStrategy = new(DefaultVersionsUpdateStrategyDisabled)
+ }
+
+ if obj.MergeMode == nil {
+ obj.MergeMode = new(MergeModeHint)
+ }
+}
diff --git a/pkg/apis/config/v1alpha1/types.go b/pkg/apis/config/v1alpha1/types.go
index 7f893fa9..a6c4207d 100644
--- a/pkg/apis/config/v1alpha1/types.go
+++ b/pkg/apis/config/v1alpha1/types.go
@@ -26,6 +26,11 @@ type LandscapeKitConfiguration struct {
// VersionConfig is the configuration for versioning.
// +optional
VersionConfig *VersionConfiguration `json:"versionConfig,omitempty"`
+ // MergeMode determines how merge conflicts are resolved:
+ // - "Hint" (default): New default values from GLK are added as comments after any customized values.
+ // - "Silent": Operator-customized values are retained, new default values are omitted.
+ // +optional
+ MergeMode *MergeMode `json:"mergeMode,omitempty"`
}
// ComponentsConfiguration contains configuration for components.
@@ -119,3 +124,19 @@ type VersionConfiguration struct {
// +optional
DefaultVersionsUpdateStrategy *DefaultVersionsUpdateStrategy `json:"defaultVersionsUpdateStrategy,omitempty"`
}
+
+// MergeMode controls how operator overwrites are handled during three-way merge.
+type MergeMode string
+
+const (
+ // MergeModeHint annotates operator-overwritten values with a comment showing the current GLK default.
+ MergeModeHint MergeMode = "Hint"
+ // MergeModeSilent retains operator overwrites without annotation.
+ MergeModeSilent MergeMode = "Silent"
+)
+
+// AllowedMergeModes lists all allowed merge modes.
+var AllowedMergeModes = []string{
+ string(MergeModeHint),
+ string(MergeModeSilent),
+}
diff --git a/pkg/apis/config/v1alpha1/validation/validation.go b/pkg/apis/config/v1alpha1/validation/validation.go
index 1144b815..704cee8d 100644
--- a/pkg/apis/config/v1alpha1/validation/validation.go
+++ b/pkg/apis/config/v1alpha1/validation/validation.go
@@ -36,6 +36,10 @@ func ValidateLandscapeKitConfiguration(conf *configv1alpha1.LandscapeKitConfigur
allErrs = append(allErrs, ValidateVersionConfig(conf.VersionConfig, field.NewPath("versionConfig"))...)
}
+ if conf.MergeMode != nil && !slices.Contains(configv1alpha1.AllowedMergeModes, string(*conf.MergeMode)) {
+ allErrs = append(allErrs, field.NotSupported(field.NewPath("mergeMode"), *conf.MergeMode, configv1alpha1.AllowedMergeModes))
+ }
+
return allErrs
}
diff --git a/pkg/apis/config/v1alpha1/validation/validation_test.go b/pkg/apis/config/v1alpha1/validation/validation_test.go
index 1d8614c5..a0dcb09e 100644
--- a/pkg/apis/config/v1alpha1/validation/validation_test.go
+++ b/pkg/apis/config/v1alpha1/validation/validation_test.go
@@ -294,6 +294,43 @@ var _ = Describe("Validation", func() {
Expect(errList).To(BeEmpty())
})
})
+
+ Context("MergeMode Configuration", func() {
+ It("should pass with valid MergeMode values", func() {
+ for _, mode := range []v1alpha1.MergeMode{
+ v1alpha1.MergeModeHint,
+ v1alpha1.MergeModeSilent,
+ } {
+ conf := &v1alpha1.LandscapeKitConfiguration{
+ MergeMode: &mode,
+ }
+ errList := validation.ValidateLandscapeKitConfiguration(conf)
+ Expect(errList).To(BeEmpty(), fmt.Sprintf("MergeMode %q should be valid", mode))
+ }
+ })
+
+ It("should pass when MergeMode is not set", func() {
+ conf := &v1alpha1.LandscapeKitConfiguration{}
+ errList := validation.ValidateLandscapeKitConfiguration(conf)
+ Expect(errList).To(BeEmpty())
+ })
+
+ It("should fail with an invalid MergeMode value", func() {
+ invalid := v1alpha1.MergeMode("Invalid")
+ conf := &v1alpha1.LandscapeKitConfiguration{
+ MergeMode: &invalid,
+ }
+
+ errList := validation.ValidateLandscapeKitConfiguration(conf)
+ Expect(errList).To(ConsistOf(
+ PointTo(MatchFields(IgnoreExtras, Fields{
+ "Type": Equal(field.ErrorTypeNotSupported),
+ "Field": Equal("mergeMode"),
+ "BadValue": Equal(invalid),
+ })),
+ ))
+ })
+ })
})
})
diff --git a/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go
index 5d2b1307..3ce65028 100644
--- a/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go
@@ -112,6 +112,11 @@ func (in *LandscapeKitConfiguration) DeepCopyInto(out *LandscapeKitConfiguration
*out = new(VersionConfiguration)
(*in).DeepCopyInto(*out)
}
+ if in.MergeMode != nil {
+ in, out := &in.MergeMode, &out.MergeMode
+ *out = new(MergeMode)
+ **out = **in
+ }
return
}
diff --git a/pkg/apis/config/v1alpha1/zz_generated.defaults.go b/pkg/apis/config/v1alpha1/zz_generated.defaults.go
index dce68e63..e134802a 100644
--- a/pkg/apis/config/v1alpha1/zz_generated.defaults.go
+++ b/pkg/apis/config/v1alpha1/zz_generated.defaults.go
@@ -17,5 +17,10 @@ import (
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
+ scheme.AddTypeDefaultingFunc(&LandscapeKitConfiguration{}, func(obj interface{}) { SetObjectDefaults_LandscapeKitConfiguration(obj.(*LandscapeKitConfiguration)) })
return nil
}
+
+func SetObjectDefaults_LandscapeKitConfiguration(in *LandscapeKitConfiguration) {
+ SetDefaults_LandscapeKitConfiguration(in)
+}
diff --git a/pkg/cmd/resolve/plain/plain.go b/pkg/cmd/resolve/plain/plain.go
index 1329d140..96ce7052 100644
--- a/pkg/cmd/resolve/plain/plain.go
+++ b/pkg/cmd/resolve/plain/plain.go
@@ -6,10 +6,8 @@ package plain
import (
"context"
- "errors"
"fmt"
- "os"
- "path"
+ "strings"
"github.com/spf13/afero"
"github.com/spf13/cobra"
@@ -22,6 +20,7 @@ import (
configv1alpha1 "github.com/gardener/gardener-landscape-kit/pkg/apis/config/v1alpha1"
"github.com/gardener/gardener-landscape-kit/pkg/cmd"
utilscomponentvector "github.com/gardener/gardener-landscape-kit/pkg/utils/componentvector"
+ utilsfiles "github.com/gardener/gardener-landscape-kit/pkg/utils/files"
)
var configDecoder runtime.Decoder
@@ -111,7 +110,7 @@ func (o *Options) loadConfigFile(filename string) error {
func run(_ context.Context, opts *Options) error {
if opts.Config != nil && opts.Config.VersionConfig != nil {
- if updateStrategy := opts.Config.VersionConfig.DefaultVersionsUpdateStrategy; updateStrategy != nil && *updateStrategy == configv1alpha1.DefaultVersionsUpdateStrategyReleaseBranch {
+ if *opts.Config.VersionConfig.DefaultVersionsUpdateStrategy == configv1alpha1.DefaultVersionsUpdateStrategyReleaseBranch {
opts.Log.Info("Updating default component vector file from the release branch", "branch", utilscomponentvector.GetReleaseBranchName())
var err error
// The componentvector.DefaultComponentsYAML is intentionally overridden, so that subsequently it can be used to extract the updated default component vector versions.
@@ -122,21 +121,30 @@ func run(_ context.Context, opts *Options) error {
}
}
- compVectorFile := path.Join(opts.TargetDirPath, utilscomponentvector.ComponentVectorFilename)
- opts.Log.Info("Writing component vector file", "file", compVectorFile)
-
- var customBytes []byte
- var err error
- if customBytes, err = opts.fs.ReadFile(compVectorFile); err != nil {
- if !errors.Is(err, os.ErrNotExist) {
- return fmt.Errorf("failed to read component vector override file: %w", err)
- }
+ var (
+ err error
+ newDefaultCV utilscomponentvector.Interface
+ newDefaultBytes []byte
+ )
+ newDefaultCV, err = utilscomponentvector.NewWithOverride(componentvector.DefaultComponentsYAML)
+ if err != nil {
+ return fmt.Errorf("failed to build default component vector: %w", err)
}
-
- componentVector, err := utilscomponentvector.NewWithOverride(componentvector.DefaultComponentsYAML, customBytes)
+ newDefaultBytes, err = utilscomponentvector.NameVersionBytes(newDefaultCV)
if err != nil {
- return fmt.Errorf("failed to create component vector: %w", err)
+ return fmt.Errorf("failed to marshal default component vector: %w", err)
}
- return utilscomponentvector.WriteComponentVectorFile(opts.fs, opts.TargetDirPath, componentVector)
+ header := []byte(strings.Join([]string{
+ "# This file is updated by the gardener-landscape-kit.",
+ "# If this file is present in the root of a gardener-landscape-kit-managed repository, the component versions will be used as overrides.",
+ "# If custom component versions should be used, it is recommended to modify the specified versions here and run the `generate` command afterwards.",
+ }, "\n") + "\n")
+ newDefaultBytes = append(header, newDefaultBytes...)
+
+ if err := utilsfiles.WriteObjectsToFilesystem(map[string][]byte{utilscomponentvector.ComponentVectorFilename: newDefaultBytes}, opts.TargetDirPath, "", opts.fs, *opts.Config.MergeMode); err != nil {
+ return fmt.Errorf("failed to write updated component vector: %w", err)
+ }
+
+ return nil
}
diff --git a/pkg/components/flux/component.go b/pkg/components/flux/component.go
index 6a47c9aa..54b2e4f0 100644
--- a/pkg/components/flux/component.go
+++ b/pkg/components/flux/component.go
@@ -94,7 +94,7 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
delete(objects, "flux-system/gitignore")
delete(objects, "flux-system/doc.go")
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), DirName, opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), DirName, opts.GetFilesystem(), opts.GetMergeMode())
}
func writeGitignoreFile(options components.LandscapeOptions) error {
diff --git a/pkg/components/flux/component_test.go b/pkg/components/flux/component_test.go
index e0df97fd..562ced8d 100644
--- a/pkg/components/flux/component_test.go
+++ b/pkg/components/flux/component_test.go
@@ -54,6 +54,16 @@ var _ = Describe("Flux Component Generation", func() {
fs = afero.Afero{Fs: afero.NewMemMapFs()}
+ config := &v1alpha1.LandscapeKitConfiguration{
+ Git: &v1alpha1.GitRepository{
+ URL: repoURL,
+ Paths: v1alpha1.PathConfiguration{
+ Landscape: relativeLandscapePath,
+ },
+ },
+ }
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(config)
+
var err error
opts, err = components.NewLandscapeOptions(
&generateoptions.Options{
@@ -61,14 +71,7 @@ var _ = Describe("Flux Component Generation", func() {
Log: logr.Discard(),
},
TargetDirPath: targetPath,
- Config: &v1alpha1.LandscapeKitConfiguration{
- Git: &v1alpha1.GitRepository{
- URL: repoURL,
- Paths: v1alpha1.PathConfiguration{
- Landscape: relativeLandscapePath,
- },
- },
- },
+ Config: config,
},
fs,
)
diff --git a/pkg/components/gardener-extensions/networking-calico/component.go b/pkg/components/gardener-extensions/networking-calico/component.go
index c297146f..eeb3d194 100644
--- a/pkg/components/gardener-extensions/networking-calico/component.go
+++ b/pkg/components/gardener-extensions/networking-calico/component.go
@@ -78,7 +78,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -100,5 +100,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/gardener-extensions/networking-calico/component_test.go b/pkg/components/gardener-extensions/networking-calico/component_test.go
index 351e731c..e1b6b341 100644
--- a/pkg/components/gardener-extensions/networking-calico/component_test.go
+++ b/pkg/components/gardener-extensions/networking-calico/component_test.go
@@ -35,7 +35,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -68,6 +70,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/gardener-extensions/networking-cilium/component.go b/pkg/components/gardener-extensions/networking-cilium/component.go
index b934ce4b..e39d9699 100644
--- a/pkg/components/gardener-extensions/networking-cilium/component.go
+++ b/pkg/components/gardener-extensions/networking-cilium/component.go
@@ -78,7 +78,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -100,5 +100,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/gardener-extensions/networking-cilium/component_test.go b/pkg/components/gardener-extensions/networking-cilium/component_test.go
index 802045ef..5093442f 100644
--- a/pkg/components/gardener-extensions/networking-cilium/component_test.go
+++ b/pkg/components/gardener-extensions/networking-cilium/component_test.go
@@ -35,7 +35,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -68,6 +70,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/gardener-extensions/os-gardenlinux/component.go b/pkg/components/gardener-extensions/os-gardenlinux/component.go
index 2cc5eb0b..0ee9de54 100644
--- a/pkg/components/gardener-extensions/os-gardenlinux/component.go
+++ b/pkg/components/gardener-extensions/os-gardenlinux/component.go
@@ -78,7 +78,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -100,5 +100,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/gardener-extensions/os-gardenlinux/component_test.go b/pkg/components/gardener-extensions/os-gardenlinux/component_test.go
index 096175c5..c93dde59 100644
--- a/pkg/components/gardener-extensions/os-gardenlinux/component_test.go
+++ b/pkg/components/gardener-extensions/os-gardenlinux/component_test.go
@@ -35,7 +35,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -68,6 +70,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/gardener-extensions/os-suse-chost/component.go b/pkg/components/gardener-extensions/os-suse-chost/component.go
index 3e933845..2c7be6a1 100644
--- a/pkg/components/gardener-extensions/os-suse-chost/component.go
+++ b/pkg/components/gardener-extensions/os-suse-chost/component.go
@@ -78,7 +78,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -100,5 +100,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/gardener-extensions/os-suse-chost/component_test.go b/pkg/components/gardener-extensions/os-suse-chost/component_test.go
index fb3fa110..eb27155d 100644
--- a/pkg/components/gardener-extensions/os-suse-chost/component_test.go
+++ b/pkg/components/gardener-extensions/os-suse-chost/component_test.go
@@ -35,7 +35,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -68,6 +70,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/gardener-extensions/provider-alicloud/component.go b/pkg/components/gardener-extensions/provider-alicloud/component.go
index b703f156..3870483f 100644
--- a/pkg/components/gardener-extensions/provider-alicloud/component.go
+++ b/pkg/components/gardener-extensions/provider-alicloud/component.go
@@ -78,7 +78,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -100,5 +100,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/gardener-extensions/provider-alicloud/component_test.go b/pkg/components/gardener-extensions/provider-alicloud/component_test.go
index 499a1357..c94be716 100644
--- a/pkg/components/gardener-extensions/provider-alicloud/component_test.go
+++ b/pkg/components/gardener-extensions/provider-alicloud/component_test.go
@@ -35,7 +35,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -68,6 +70,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/gardener-extensions/provider-aws/component.go b/pkg/components/gardener-extensions/provider-aws/component.go
index 76e3408d..2d65d347 100644
--- a/pkg/components/gardener-extensions/provider-aws/component.go
+++ b/pkg/components/gardener-extensions/provider-aws/component.go
@@ -78,7 +78,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -100,5 +100,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/gardener-extensions/provider-aws/component_test.go b/pkg/components/gardener-extensions/provider-aws/component_test.go
index 17a395e2..678720d6 100644
--- a/pkg/components/gardener-extensions/provider-aws/component_test.go
+++ b/pkg/components/gardener-extensions/provider-aws/component_test.go
@@ -35,7 +35,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -68,6 +70,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/gardener-extensions/provider-azure/component.go b/pkg/components/gardener-extensions/provider-azure/component.go
index 6bbb7f71..2b75ca26 100644
--- a/pkg/components/gardener-extensions/provider-azure/component.go
+++ b/pkg/components/gardener-extensions/provider-azure/component.go
@@ -78,7 +78,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -100,5 +100,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/gardener-extensions/provider-azure/component_test.go b/pkg/components/gardener-extensions/provider-azure/component_test.go
index 481f991f..a65922f7 100644
--- a/pkg/components/gardener-extensions/provider-azure/component_test.go
+++ b/pkg/components/gardener-extensions/provider-azure/component_test.go
@@ -35,7 +35,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -68,6 +70,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/gardener-extensions/provider-gcp/component.go b/pkg/components/gardener-extensions/provider-gcp/component.go
index dbbd3271..c050ecf6 100644
--- a/pkg/components/gardener-extensions/provider-gcp/component.go
+++ b/pkg/components/gardener-extensions/provider-gcp/component.go
@@ -78,7 +78,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -100,5 +100,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/gardener-extensions/provider-gcp/component_test.go b/pkg/components/gardener-extensions/provider-gcp/component_test.go
index 7b932561..0b1dd0fd 100644
--- a/pkg/components/gardener-extensions/provider-gcp/component_test.go
+++ b/pkg/components/gardener-extensions/provider-gcp/component_test.go
@@ -35,7 +35,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -68,6 +70,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/gardener-extensions/provider-openstack/component.go b/pkg/components/gardener-extensions/provider-openstack/component.go
index 6ffc8e8f..071b468e 100644
--- a/pkg/components/gardener-extensions/provider-openstack/component.go
+++ b/pkg/components/gardener-extensions/provider-openstack/component.go
@@ -78,7 +78,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -100,5 +100,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/gardener-extensions/provider-openstack/component_test.go b/pkg/components/gardener-extensions/provider-openstack/component_test.go
index bad84c52..f4119b90 100644
--- a/pkg/components/gardener-extensions/provider-openstack/component_test.go
+++ b/pkg/components/gardener-extensions/provider-openstack/component_test.go
@@ -35,7 +35,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -68,6 +70,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/gardener-extensions/runtime-gvisor/component.go b/pkg/components/gardener-extensions/runtime-gvisor/component.go
index 3a6e0cb9..c095911f 100644
--- a/pkg/components/gardener-extensions/runtime-gvisor/component.go
+++ b/pkg/components/gardener-extensions/runtime-gvisor/component.go
@@ -78,7 +78,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -100,5 +100,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/gardener-extensions/runtime-gvisor/component_test.go b/pkg/components/gardener-extensions/runtime-gvisor/component_test.go
index 65aede92..9c73eccb 100644
--- a/pkg/components/gardener-extensions/runtime-gvisor/component_test.go
+++ b/pkg/components/gardener-extensions/runtime-gvisor/component_test.go
@@ -35,7 +35,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -68,6 +70,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/gardener-extensions/shoot-cert-service/component.go b/pkg/components/gardener-extensions/shoot-cert-service/component.go
index b8c1c6b3..03645180 100644
--- a/pkg/components/gardener-extensions/shoot-cert-service/component.go
+++ b/pkg/components/gardener-extensions/shoot-cert-service/component.go
@@ -78,7 +78,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -100,5 +100,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/gardener-extensions/shoot-cert-service/component_test.go b/pkg/components/gardener-extensions/shoot-cert-service/component_test.go
index d7d21bb2..35b75d90 100644
--- a/pkg/components/gardener-extensions/shoot-cert-service/component_test.go
+++ b/pkg/components/gardener-extensions/shoot-cert-service/component_test.go
@@ -35,7 +35,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -68,6 +70,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/gardener-extensions/shoot-dns-service/component.go b/pkg/components/gardener-extensions/shoot-dns-service/component.go
index b452074d..c0a87bd0 100644
--- a/pkg/components/gardener-extensions/shoot-dns-service/component.go
+++ b/pkg/components/gardener-extensions/shoot-dns-service/component.go
@@ -78,7 +78,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -100,5 +100,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/gardener-extensions/shoot-dns-service/component_test.go b/pkg/components/gardener-extensions/shoot-dns-service/component_test.go
index 9fb02d38..747c03d7 100644
--- a/pkg/components/gardener-extensions/shoot-dns-service/component_test.go
+++ b/pkg/components/gardener-extensions/shoot-dns-service/component_test.go
@@ -35,7 +35,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -68,6 +70,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/gardener-extensions/shoot-networking-problemdetector/component.go b/pkg/components/gardener-extensions/shoot-networking-problemdetector/component.go
index ab35bebd..9053be4d 100644
--- a/pkg/components/gardener-extensions/shoot-networking-problemdetector/component.go
+++ b/pkg/components/gardener-extensions/shoot-networking-problemdetector/component.go
@@ -78,7 +78,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -100,5 +100,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/gardener-extensions/shoot-networking-problemdetector/component_test.go b/pkg/components/gardener-extensions/shoot-networking-problemdetector/component_test.go
index 1a7e7f3c..7f4d2e5c 100644
--- a/pkg/components/gardener-extensions/shoot-networking-problemdetector/component_test.go
+++ b/pkg/components/gardener-extensions/shoot-networking-problemdetector/component_test.go
@@ -35,7 +35,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -68,6 +70,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/gardener-extensions/shoot-oidc-service/component.go b/pkg/components/gardener-extensions/shoot-oidc-service/component.go
index 9e8d9dcf..9dbcd84e 100644
--- a/pkg/components/gardener-extensions/shoot-oidc-service/component.go
+++ b/pkg/components/gardener-extensions/shoot-oidc-service/component.go
@@ -78,7 +78,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -100,5 +100,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/gardener-extensions/shoot-oidc-service/component_test.go b/pkg/components/gardener-extensions/shoot-oidc-service/component_test.go
index 667aedcb..683bad3b 100644
--- a/pkg/components/gardener-extensions/shoot-oidc-service/component_test.go
+++ b/pkg/components/gardener-extensions/shoot-oidc-service/component_test.go
@@ -35,7 +35,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -68,6 +70,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/gardener/garden/component.go b/pkg/components/gardener/garden/component.go
index eefa0f2f..5cbc3c3d 100644
--- a/pkg/components/gardener/garden/component.go
+++ b/pkg/components/gardener/garden/component.go
@@ -71,7 +71,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -88,5 +88,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/gardener/garden/component_test.go b/pkg/components/gardener/garden/component_test.go
index 781adff3..455d17f2 100644
--- a/pkg/components/gardener/garden/component_test.go
+++ b/pkg/components/gardener/garden/component_test.go
@@ -30,7 +30,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -62,6 +64,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/gardener/operator/component.go b/pkg/components/gardener/operator/component.go
index 5873ca7e..0221ed8e 100644
--- a/pkg/components/gardener/operator/component.go
+++ b/pkg/components/gardener/operator/component.go
@@ -80,7 +80,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func getTemplateValues(opts components.Options) (map[string]any, error) {
@@ -134,7 +134,7 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func getOCIImageReferenceFromComponentVector(name string, cv *utilscomponentvector.ComponentVector) (string, error) {
diff --git a/pkg/components/gardener/operator/component_test.go b/pkg/components/gardener/operator/component_test.go
index 5870a6dd..d080114a 100644
--- a/pkg/components/gardener/operator/component_test.go
+++ b/pkg/components/gardener/operator/component_test.go
@@ -37,7 +37,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -86,6 +88,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/gardener/virtual-garden-access/component.go b/pkg/components/gardener/virtual-garden-access/component.go
index 513753c2..81dc4142 100644
--- a/pkg/components/gardener/virtual-garden-access/component.go
+++ b/pkg/components/gardener/virtual-garden-access/component.go
@@ -73,7 +73,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -90,5 +90,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/gardener/virtual-garden-access/component_test.go b/pkg/components/gardener/virtual-garden-access/component_test.go
index 58c8856c..ed74d8f6 100644
--- a/pkg/components/gardener/virtual-garden-access/component_test.go
+++ b/pkg/components/gardener/virtual-garden-access/component_test.go
@@ -30,7 +30,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -70,6 +72,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/components/types.go b/pkg/components/types.go
index ca38214b..fa23a42d 100644
--- a/pkg/components/types.go
+++ b/pkg/components/types.go
@@ -15,7 +15,7 @@ import (
"github.com/spf13/afero"
"github.com/gardener/gardener-landscape-kit/componentvector"
- "github.com/gardener/gardener-landscape-kit/pkg/apis/config/v1alpha1"
+ configv1alpha1 "github.com/gardener/gardener-landscape-kit/pkg/apis/config/v1alpha1"
generateoptions "github.com/gardener/gardener-landscape-kit/pkg/cmd/generate/options"
utilscomponentvector "github.com/gardener/gardener-landscape-kit/pkg/utils/componentvector"
"github.com/gardener/gardener-landscape-kit/pkg/utils/files"
@@ -36,6 +36,8 @@ type Options interface {
GetFilesystem() afero.Afero
// GetLogger returns the logger instance.
GetLogger() logr.Logger
+ // GetMergeMode returns the configured mode to solve merge conflicts.
+ GetMergeMode() configv1alpha1.MergeMode
}
// LandscapeOptions is an interface for options passed to components for generating the landscape.
@@ -43,7 +45,7 @@ type LandscapeOptions interface {
Options
// GetGitRepository returns the git repository information.
- GetGitRepository() *v1alpha1.GitRepository
+ GetGitRepository() *configv1alpha1.GitRepository
// GetRelativeBasePath returns the base directory that is relative to the target path.
GetRelativeBasePath() string
// GetRelativeLandscapePath returns the landscape directory that is relative to the target path.
@@ -65,6 +67,7 @@ type options struct {
targetPath string
filesystem afero.Afero
logger logr.Logger
+ mergeMode configv1alpha1.MergeMode
}
// GetComponentVector returns the component vector.
@@ -87,6 +90,11 @@ func (o *options) GetLogger() logr.Logger {
return o.logger
}
+// GetMergeMode returns the configured merge mode for three-way merges.
+func (o *options) GetMergeMode() configv1alpha1.MergeMode {
+ return o.mergeMode
+}
+
// NewOptions returns a new Options instance.
func NewOptions(opts *generateoptions.Options, fs afero.Afero) (Options, error) {
var customComponentVectors [][]byte
@@ -113,11 +121,13 @@ func NewOptions(opts *generateoptions.Options, fs afero.Afero) (Options, error)
if err != nil {
return nil, fmt.Errorf("failed to create component vector: %w", err)
}
+
return &options{
componentVector: componentVector,
targetPath: path.Clean(opts.TargetDirPath),
filesystem: fs,
logger: opts.Log,
+ mergeMode: *opts.Config.MergeMode,
}, nil
}
@@ -134,11 +144,11 @@ func readCustomComponentsFile(opts *generateoptions.Options, fs afero.Afero, fil
type landscapeOptions struct {
Options
- gitRepository *v1alpha1.GitRepository
+ gitRepository *configv1alpha1.GitRepository
}
// GetGitRepository returns the git repository information.
-func (l *landscapeOptions) GetGitRepository() *v1alpha1.GitRepository {
+func (l *landscapeOptions) GetGitRepository() *configv1alpha1.GitRepository {
return l.gitRepository
}
diff --git a/pkg/components/types_test.go b/pkg/components/types_test.go
index cf468887..c08cd7d4 100644
--- a/pkg/components/types_test.go
+++ b/pkg/components/types_test.go
@@ -34,7 +34,9 @@ var _ = Describe("Types", func() {
opts = &options.Options{
Options: &cmd.Options{},
TargetDirPath: "",
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(opts.Config)
})
Describe("#GetTargetPath", func() {
@@ -105,20 +107,9 @@ var _ = Describe("Types", func() {
Expect(exists).To(BeFalse())
})
- It("should return an empty component vector when config is nil", func() {
- opts.Config = nil
-
- componentOpts, err := components.NewOptions(opts, fs)
-
- Expect(err).NotTo(HaveOccurred())
- Expect(componentOpts.GetComponentVector()).NotTo(BeNil())
-
- _, exists := componentOpts.GetComponentVector().FindComponentVersion("test-component")
- Expect(exists).To(BeFalse())
- })
-
- It("should return an empty component vector when VersionConfig is nil", func() {
+ It("should return an empty component vector when config is empty", func() {
opts.Config = &v1alpha1.LandscapeKitConfiguration{}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(opts.Config)
componentOpts, err := components.NewOptions(opts, fs)
@@ -199,7 +190,9 @@ var _ = Describe("Types", func() {
Log: logger,
},
TargetDirPath: "/path/to/target",
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(opts.Config)
result, err := components.NewOptions(opts, fs)
@@ -232,6 +225,7 @@ var _ = Describe("Types", func() {
},
TargetDirPath: "",
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(opts.Config)
})
Describe("#GetGitRepository", func() {
@@ -285,6 +279,7 @@ var _ = Describe("Types", func() {
},
},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(opts.Config)
result, err := components.NewLandscapeOptions(opts, fs)
diff --git a/pkg/components/virtual-garden/garden-config/component.go b/pkg/components/virtual-garden/garden-config/component.go
index 56227b00..1104f314 100644
--- a/pkg/components/virtual-garden/garden-config/component.go
+++ b/pkg/components/virtual-garden/garden-config/component.go
@@ -73,7 +73,7 @@ func writeBaseTemplateFiles(opts components.Options) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
@@ -90,5 +90,5 @@ func writeLandscapeTemplateFiles(opts components.LandscapeOptions) error {
return err
}
- return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem())
+ return files.WriteObjectsToFilesystem(objects, opts.GetTargetPath(), path.Join(components.DirName, ComponentDirectory), opts.GetFilesystem(), opts.GetMergeMode())
}
diff --git a/pkg/components/virtual-garden/garden-config/component_test.go b/pkg/components/virtual-garden/garden-config/component_test.go
index b17dca5e..5886dc9b 100644
--- a/pkg/components/virtual-garden/garden-config/component_test.go
+++ b/pkg/components/virtual-garden/garden-config/component_test.go
@@ -30,7 +30,9 @@ var _ = Describe("Component Generation", func() {
generateOpts = &generateoptions.Options{
TargetDirPath: "/repo/baseDir",
Options: cmdOpts,
+ Config: &v1alpha1.LandscapeKitConfiguration{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
Describe("#GenerateBase", func() {
@@ -67,6 +69,7 @@ var _ = Describe("Component Generation", func() {
generateOpts.Config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{Paths: v1alpha1.PathConfiguration{Landscape: "./landscapeDir", Base: "./baseDir"}},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
})
It("should generate only the flux kustomization into the landscape dir", func() {
diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go
index 396db1b1..d53e0ed7 100644
--- a/pkg/registry/registry_test.go
+++ b/pkg/registry/registry_test.go
@@ -35,10 +35,14 @@ var _ = Describe("Registry", func() {
config = &v1alpha1.LandscapeKitConfiguration{
Git: &v1alpha1.GitRepository{},
}
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(config)
var err error
options, err = components.NewOptions(
- &generateoptions.Options{Options: &cmd.Options{Log: logr.Discard()}},
+ &generateoptions.Options{
+ Options: &cmd.Options{Log: logr.Discard()},
+ Config: config,
+ },
afero.Afero{Fs: afero.NewMemMapFs()},
)
Expect(err).NotTo(HaveOccurred())
diff --git a/pkg/utils/componentvector/componentvector.go b/pkg/utils/componentvector/componentvector.go
index f8476c8f..9b24e6c9 100644
--- a/pkg/utils/componentvector/componentvector.go
+++ b/pkg/utils/componentvector/componentvector.go
@@ -7,18 +7,12 @@ package componentvector
import (
"fmt"
"maps"
- "path/filepath"
"reflect"
"slices"
- "strings"
- "github.com/spf13/afero"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
"sigs.k8s.io/yaml"
-
- "github.com/gardener/gardener-landscape-kit/componentvector"
- "github.com/gardener/gardener-landscape-kit/pkg/utils/files"
)
const (
@@ -220,82 +214,17 @@ func resourcesToUnstructuredMap(resources map[string]ResourceData) (map[string]a
return unstructuredMap, nil
}
-const (
- defaultVersionCommentMarker = "# <-- gardener-landscape-kit version default"
-)
-
-// stripDefaultVersionComments removes GLK-managed default-version comment lines from a components.yaml file.
-// A line is considered GLK-managed when it contains the unique GLK marker suffix.
-// Stripping them before the three-way merge ensures the canonical comment is always (re-)written on the next run, even when the user has edited the comment text.
-func stripDefaultVersionComments(data []byte) []byte {
- lines := strings.Split(string(data), "\n")
- out := make([]string, 0, len(lines))
- for _, line := range lines {
- if !strings.Contains(line, defaultVersionCommentMarker) {
- out = append(out, line)
- }
- }
- return []byte(strings.Join(out, "\n"))
-}
-
-// WriteComponentVectorFile writes the component vector file effectively used to the target directory if applicable.
-func WriteComponentVectorFile(fs afero.Afero, targetDirPath string, componentVector Interface) error {
- var (
- comp = &Components{}
- postGenerateDefaultVersionCommentFns []func(string) string
- )
- cvDefault, err := NewWithOverride(componentvector.DefaultComponentsYAML)
- if err != nil {
- return fmt.Errorf("failed to build default component vector: %w", err)
- }
- for _, componentName := range componentVector.ComponentNames() {
- componentVersion, _ := componentVector.FindComponentVersion(componentName)
- comp.Components = append(comp.Components, &ComponentVector{
- Name: componentName,
- Version: componentVersion,
- })
- defaultVersion, found := cvDefault.FindComponentVersion(componentName)
- if found && componentVersion != defaultVersion {
- defaultVersionComment := "# version: " + defaultVersion + " " + defaultVersionCommentMarker
- postGenerateDefaultVersionCommentFns = append(postGenerateDefaultVersionCommentFns, func(data string) string {
- return strings.ReplaceAll(data, componentName+"\n", componentName+"\n"+defaultVersionComment+"\n")
- })
- }
+// NameVersionBytes marshals cv into a name+version-only Components YAML, stripping all other fields.
+// This compact format is used for the .glk/defaults/ snapshot and as the three-way merge baseline in plain.go.
+func NameVersionBytes(cv Interface) ([]byte, error) {
+ stripped := &Components{}
+ for _, name := range cv.ComponentNames() {
+ version, _ := cv.FindComponentVersion(name)
+ stripped.Components = append(stripped.Components, &ComponentVector{Name: name, Version: version})
}
- data, err := yaml.Marshal(comp)
+ data, err := yaml.Marshal(stripped)
if err != nil {
- return fmt.Errorf("failed to marshal component vector: %w", err)
- }
-
- header := []byte(strings.Join([]string{
- "# This file is updated by the gardener-landscape-kit.",
- "# If this file is specified in the gardener-landscape-kit configuration file, the component versions will be used as overrides.",
- "# If custom component versions should be used, it is recommended to modify the specified versions here and run the `generate` command afterwards.",
- }, "\n") + "\n")
-
- // Before writing, strip any GLK-managed default-version comment lines from the on-disk file.
- // This resets GLK-owned annotations so the canonical comment is always (re-)applied below, even when the user has edited or removed the comment line.
- filePath := filepath.Join(targetDirPath, ComponentVectorFilename)
- if existing, readErr := fs.ReadFile(filePath); readErr == nil {
- if stripped := stripDefaultVersionComments(existing); string(stripped) != string(existing) {
- if writeErr := fs.WriteFile(filePath, stripped, 0600); writeErr != nil {
- return writeErr
- }
- }
- }
-
- // Pass 1: write without default-version comments so the three-way merge operates on
- // comment-free content. This establishes a clean baseline in the .glk/defaults/ snapshot.
- dataWithoutComments := append(header, data...)
- if err := files.WriteObjectsToFilesystem(map[string][]byte{ComponentVectorFilename: dataWithoutComments}, targetDirPath, "", fs); err != nil {
- return err
- }
-
- // Pass 2: inject default-version comments and write again. Because the .glk/defaults/ snapshot from Pass 1 has no comments,
- // the comments are always treated as "new" by the three-way merge and are therefore reliably written into the output file.
- for _, fn := range postGenerateDefaultVersionCommentFns {
- data = []byte(fn(string(data)))
+ return nil, fmt.Errorf("failed to marshal component versions: %w", err)
}
- dataWithComments := append(header, data...)
- return files.WriteObjectsToFilesystem(map[string][]byte{ComponentVectorFilename: dataWithComments}, targetDirPath, "", fs)
+ return data, nil
}
diff --git a/pkg/utils/componentvector/componentvector_test.go b/pkg/utils/componentvector/componentvector_test.go
index 753d5566..88365472 100644
--- a/pkg/utils/componentvector/componentvector_test.go
+++ b/pkg/utils/componentvector/componentvector_test.go
@@ -5,14 +5,9 @@
package componentvector_test
import (
- "strings"
-
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
- "github.com/spf13/afero"
- "sigs.k8s.io/yaml"
- "github.com/gardener/gardener-landscape-kit/componentvector"
. "github.com/gardener/gardener-landscape-kit/pkg/utils/componentvector"
)
@@ -683,115 +678,4 @@ components:
})
})
- Describe("#WriteComponentVectorFile", func() {
- const outputDir = "/output"
-
- // componentNames parses the written components.yaml and returns the list of component names.
- componentNames := func(fs afero.Afero) []string {
- data, err := fs.ReadFile(outputDir + "/components.yaml")
- ExpectWithOffset(1, err).NotTo(HaveOccurred())
- var comps struct {
- Components []struct {
- Name string `json:"name"`
- } `json:"components"`
- }
- ExpectWithOffset(1, yaml.Unmarshal(data, &comps)).NotTo(HaveOccurred())
- names := make([]string, 0, len(comps.Components))
- for _, c := range comps.Components {
- names = append(names, c.Name)
- }
- return names
- }
-
- cv := func(contents []byte) Interface {
- cv, err := NewWithOverride(contents)
- ExpectWithOffset(1, err).NotTo(HaveOccurred())
- return cv
- }
-
- BeforeEach(func() {
- componentvector.DefaultComponentsYAML = []byte(`components:
-- name: github.com/gardener/gardener
- sourceRepository: https://github.com/gardener/gardener
- version: v1.137.1
-- name: github.com/gardener/other-component
- sourceRepository: https://github.com/gardener/other-component
- version: v2.0.0
-`)
- })
-
- It("should not produce duplicate entries when the user edits the injected default-version comment", func() {
- fs := afero.Afero{Fs: afero.NewMemMapFs()}
-
- overrideCV := []byte(`components:
-- name: github.com/gardener/gardener
- sourceRepository: https://github.com/gardener/gardener
- version: v1.99.0
-- name: github.com/gardener/other-component
- sourceRepository: https://github.com/gardener/other-component
- version: v2.0.0
-`)
-
- // Run 1: write from default CV so no comment is injected.
- Expect(WriteComponentVectorFile(fs, outputDir, cv(componentvector.DefaultComponentsYAML))).To(Succeed())
-
- // User changes the gardener version.
- writtenFile := outputDir + "/components.yaml"
- writtenData, err := fs.ReadFile(writtenFile)
- Expect(err).NotTo(HaveOccurred())
- Expect(fs.WriteFile(writtenFile,
- []byte(strings.ReplaceAll(string(writtenData), "version: v1.137.1", "version: v1.99.0")),
- 0600)).To(Succeed())
-
- // Run 2: the injected default-version comment appears.
- Expect(WriteComponentVectorFile(fs, outputDir, cv(overrideCV))).To(Succeed())
-
- writtenData, err = fs.ReadFile(writtenFile)
- Expect(err).NotTo(HaveOccurred())
- Expect(string(writtenData)).To(ContainSubstring("# version: v1.137.1 # <-- gardener-landscape-kit version default"))
-
- // User edits the injected comment (e.g. adds a personal annotation).
- writtenData, err = fs.ReadFile(writtenFile)
- Expect(err).NotTo(HaveOccurred())
- Expect(fs.WriteFile(writtenFile,
- []byte(strings.ReplaceAll(string(writtenData),
- "# version: v1.137.1 # <-- gardener-landscape-kit version default",
- "# version: v1.137.1 # <-- default (my annotation)")),
- 0600)).To(Succeed())
-
- // Run 3: must not duplicate gardener entry and amend the comment with a new default version comment.
- Expect(WriteComponentVectorFile(fs, outputDir, cv(overrideCV))).To(Succeed())
-
- Expect(componentNames(fs)).To(ConsistOf(
- "github.com/gardener/gardener",
- "github.com/gardener/other-component",
- ))
-
- // The correct default-version comment must have been restored.
- writtenData, err = fs.ReadFile(writtenFile)
- Expect(err).NotTo(HaveOccurred())
- Expect(string(writtenData)).To(ContainSubstring("# <-- gardener-landscape-kit version default"))
- Expect(string(writtenData)).To(ContainSubstring("my annotation"))
- })
-
- It("should not re-add entries that the user removed from the file", func() {
- fs := afero.Afero{Fs: afero.NewMemMapFs()}
-
- // Run 1: write both entries.
- Expect(WriteComponentVectorFile(fs, outputDir, cv(componentvector.DefaultComponentsYAML))).To(Succeed())
-
- // User removes the other-component entry entirely.
- writtenFile := outputDir + "/components.yaml"
- writtenData, err := fs.ReadFile(writtenFile)
- Expect(err).NotTo(HaveOccurred())
- idx := strings.Index(string(writtenData), "- name: github.com/gardener/other-component")
- Expect(idx).To(BeNumerically(">", 0))
- Expect(fs.WriteFile(writtenFile, writtenData[:idx], 0600)).To(Succeed())
-
- // Run 2: same vector — the removed entry must not come back.
- Expect(WriteComponentVectorFile(fs, outputDir, cv(componentvector.DefaultComponentsYAML))).To(Succeed())
-
- Expect(componentNames(fs)).To(ConsistOf("github.com/gardener/gardener"))
- })
- })
})
diff --git a/pkg/utils/files/writer.go b/pkg/utils/files/writer.go
index 26f76020..755345fa 100644
--- a/pkg/utils/files/writer.go
+++ b/pkg/utils/files/writer.go
@@ -13,6 +13,7 @@ import (
"github.com/spf13/afero"
+ configv1alpha1 "github.com/gardener/gardener-landscape-kit/pkg/apis/config/v1alpha1"
"github.com/gardener/gardener-landscape-kit/pkg/utils/meta"
)
@@ -47,7 +48,7 @@ func isSecret(contents []byte) bool {
// WriteObjectsToFilesystem writes the given objects to the filesystem at the specified rootDir and relativeFilePath.
// If the manifest file already exists, it patches changes from the new default.
// Additionally, it maintains a default version of the manifest in a separate directory for future diff checks.
-func WriteObjectsToFilesystem(objects map[string][]byte, rootDir, relativeFilePath string, fs afero.Afero) error {
+func WriteObjectsToFilesystem(objects map[string][]byte, rootDir, relativeFilePath string, fs afero.Afero, mode configv1alpha1.MergeMode) error {
if err := fs.MkdirAll(path.Join(rootDir, relativeFilePath), 0700); err != nil {
return err
}
@@ -81,7 +82,7 @@ func WriteObjectsToFilesystem(objects map[string][]byte, rootDir, relativeFilePa
object = append([]byte(secretEncryptionDisclaimer), object...)
}
- output, err := meta.ThreeWayMergeManifest(oldDefaultYaml, object, currentYaml)
+ output, err := meta.ThreeWayMergeManifest(oldDefaultYaml, object, currentYaml, mode)
if err != nil {
return err
}
diff --git a/pkg/utils/files/writer_test.go b/pkg/utils/files/writer_test.go
index 8d2a91bb..d171fbf8 100644
--- a/pkg/utils/files/writer_test.go
+++ b/pkg/utils/files/writer_test.go
@@ -15,7 +15,9 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
+ configv1alpha1 "github.com/gardener/gardener-landscape-kit/pkg/apis/config/v1alpha1"
"github.com/gardener/gardener-landscape-kit/pkg/utils/files"
+ "github.com/gardener/gardener-landscape-kit/pkg/utils/meta"
)
var _ = Describe("Writer", func() {
@@ -53,7 +55,7 @@ var _ = Describe("Writer", func() {
baseDir := "/path/to"
path := "my/files"
- Expect(files.WriteObjectsToFilesystem(objects, baseDir, path, fs)).To(Succeed())
+ Expect(files.WriteObjectsToFilesystem(objects, baseDir, path, fs, configv1alpha1.MergeModeSilent)).To(Succeed())
contents, err := fs.ReadFile("/path/to/my/files/file.yaml")
Expect(err).NotTo(HaveOccurred())
@@ -65,7 +67,7 @@ var _ = Describe("Writer", func() {
})
It("should overwrite the manifest file if no meta file is present yet", func() {
- Expect(files.WriteObjectsToFilesystem(map[string][]byte{"config.yaml": objYaml}, "/landscape", "manifest", fs)).To(Succeed())
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"config.yaml": objYaml}, "/landscape", "manifest", fs, configv1alpha1.MergeModeSilent)).To(Succeed())
content, err := fs.ReadFile("/landscape/.glk/defaults/manifest/config.yaml")
Expect(err).ToNot(HaveOccurred())
@@ -77,7 +79,7 @@ var _ = Describe("Writer", func() {
})
It("should patch only changed default values on subsequent generates and retain custom modifications", func() {
- Expect(files.WriteObjectsToFilesystem(map[string][]byte{"config.yaml": objYaml}, "/landscape", "manifest", fs)).To(Succeed())
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"config.yaml": objYaml}, "/landscape", "manifest", fs, configv1alpha1.MergeModeSilent)).To(Succeed())
content, err := fs.ReadFile("/landscape/manifest/config.yaml")
Expect(err).ToNot(HaveOccurred())
@@ -96,7 +98,7 @@ var _ = Describe("Writer", func() {
objYaml, err = yaml.Marshal(obj)
Expect(err).NotTo(HaveOccurred())
- Expect(files.WriteObjectsToFilesystem(map[string][]byte{"config.yaml": objYaml}, "/landscape", "manifest", fs)).To(Succeed())
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"config.yaml": objYaml}, "/landscape", "manifest", fs, configv1alpha1.MergeModeSilent)).To(Succeed())
content, err = fs.ReadFile("/landscape/.glk/defaults/manifest/config.yaml")
Expect(err).ToNot(HaveOccurred())
@@ -112,7 +114,7 @@ var _ = Describe("Writer", func() {
objYaml, err := yaml.Marshal(obj)
Expect(err).NotTo(HaveOccurred())
- Expect(files.WriteObjectsToFilesystem(map[string][]byte{"secret.yaml": objYaml}, "/landscape", "manifest", fs)).To(Succeed())
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"secret.yaml": objYaml}, "/landscape", "manifest", fs, configv1alpha1.MergeModeSilent)).To(Succeed())
content, err := fs.ReadFile("/landscape/manifest/secret.yaml")
Expect(err).ToNot(HaveOccurred())
@@ -129,7 +131,7 @@ spec:
kind: Secret
name: my-secret`)
- Expect(files.WriteObjectsToFilesystem(map[string][]byte{"secret.yaml": objYaml}, "/landscape", "manifest", fs)).To(Succeed())
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"secret.yaml": objYaml}, "/landscape", "manifest", fs, configv1alpha1.MergeModeSilent)).To(Succeed())
content, err := fs.ReadFile("/landscape/manifest/secret.yaml")
Expect(err).ToNot(HaveOccurred())
@@ -138,6 +140,233 @@ spec:
Not(ContainSubstring(`# SECURITY ADVISORY`)),
))
})
+
+ DescribeTable("should annotate operator-overwritten values only in Hint mode",
+ func(mode configv1alpha1.MergeMode, expectAnnotation bool) {
+ initial := []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.0.0
+`)
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": initial}, "/landscape", "manifest", fs, mode)).To(Succeed())
+
+ // Operator pins to a custom version with a comment explaining why
+ Expect(fs.WriteFile("/landscape/manifest/test.yaml", []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.0.5 # pinned for production
+`), 0600)).To(Succeed())
+
+ // GLK ships a new default with a newer version
+ updated := []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.1.0
+`)
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": updated}, "/landscape", "manifest", fs, mode)).To(Succeed())
+
+ content, err := fs.ReadFile("/landscape/manifest/test.yaml")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(string(content)).To(ContainSubstring("version: v1.0.5"))
+ Expect(string(content)).To(ContainSubstring("pinned for production"))
+
+ if expectAnnotation {
+ Expect(string(content)).To(ContainSubstring(meta.GLKDefaultPrefix + "v1.1.0"))
+
+ // Re-run with the same default — annotation persists because the user did not remove it.
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": updated}, "/landscape", "manifest", fs, mode)).To(Succeed())
+ content2, err := fs.ReadFile("/landscape/manifest/test.yaml")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(string(content2)).To(Equal(string(content)))
+ } else {
+ Expect(string(content)).NotTo(ContainSubstring(meta.GLKDefaultPrefix))
+ }
+ },
+ Entry("Silent", configv1alpha1.MergeModeSilent, false),
+ Entry("Hint", configv1alpha1.MergeModeHint, true),
+ )
+
+ Context("MergeMode Hint", func() {
+ It("should not re-add the annotation after the user removed it, until the default changes again", func() {
+ initial := []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.0.0
+`)
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": initial}, "/landscape", "manifest", fs, configv1alpha1.MergeModeHint)).To(Succeed())
+
+ // Operator pins to v1.0.5
+ Expect(fs.WriteFile("/landscape/manifest/test.yaml", []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.0.5
+`), 0600)).To(Succeed())
+
+ // GLK ships v1.1.0 — annotation appears
+ v110 := []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.1.0
+`)
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": v110}, "/landscape", "manifest", fs, configv1alpha1.MergeModeHint)).To(Succeed())
+ content, err := fs.ReadFile("/landscape/manifest/test.yaml")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(string(content)).To(ContainSubstring(meta.GLKDefaultPrefix))
+
+ // User acknowledges the annotation and removes it manually
+ Expect(fs.WriteFile("/landscape/manifest/test.yaml", []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.0.5
+`), 0600)).To(Succeed())
+
+ // Re-run with the same default — annotation stays removed
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": v110}, "/landscape", "manifest", fs, configv1alpha1.MergeModeHint)).To(Succeed())
+ content, err = fs.ReadFile("/landscape/manifest/test.yaml")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(string(content)).To(ContainSubstring("version: v1.0.5"))
+ Expect(string(content)).NotTo(ContainSubstring(meta.GLKDefaultPrefix))
+
+ // GLK ships v1.2.0 — annotation re-appears because the default changed
+ v120 := []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.2.0
+`)
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": v120}, "/landscape", "manifest", fs, configv1alpha1.MergeModeHint)).To(Succeed())
+ content, err = fs.ReadFile("/landscape/manifest/test.yaml")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(string(content)).To(ContainSubstring("version: v1.0.5"))
+ Expect(string(content)).To(ContainSubstring(meta.GLKDefaultPrefix + "v1.2.0"))
+ })
+
+ It("should replace the annotation when the GLK default changes again instead of accumulating", func() {
+ initial := []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.0.0
+`)
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": initial}, "/landscape", "accum", fs, configv1alpha1.MergeModeHint)).To(Succeed())
+
+ // Operator pins to v1.0.5
+ Expect(fs.WriteFile("/landscape/accum/test.yaml", []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.0.5 # pinned for production
+`), 0600)).To(Succeed())
+
+ // GLK ships v1.1.0 — annotation appears
+ v110 := []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.1.0
+`)
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": v110}, "/landscape", "accum", fs, configv1alpha1.MergeModeHint)).To(Succeed())
+ content, err := fs.ReadFile("/landscape/accum/test.yaml")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(string(content)).To(ContainSubstring(meta.GLKDefaultPrefix + "v1.1.0"))
+
+ // GLK ships v1.2.0 — annotation is replaced, not accumulated
+ v120 := []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.2.0
+`)
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": v120}, "/landscape", "accum", fs, configv1alpha1.MergeModeHint)).To(Succeed())
+ content, err = fs.ReadFile("/landscape/accum/test.yaml")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(string(content)).To(ContainSubstring("pinned for production"))
+ Expect(string(content)).To(ContainSubstring(meta.GLKDefaultPrefix + "v1.2.0"))
+ Expect(string(content)).NotTo(ContainSubstring(meta.GLKDefaultPrefix + "v1.1.0"))
+
+ // GLK ships v1.3.0 — again replaced, never more than one annotation
+ v130 := []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.3.0
+`)
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": v130}, "/landscape", "accum", fs, configv1alpha1.MergeModeHint)).To(Succeed())
+ content, err = fs.ReadFile("/landscape/accum/test.yaml")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(string(content)).To(ContainSubstring("pinned for production"))
+ Expect(string(content)).To(ContainSubstring(meta.GLKDefaultPrefix + "v1.3.0"))
+ Expect(string(content)).NotTo(ContainSubstring(meta.GLKDefaultPrefix + "v1.2.0"))
+ Expect(string(content)).NotTo(ContainSubstring(meta.GLKDefaultPrefix + "v1.1.0"))
+ })
+
+ It("should remove the annotation entirely when the GLK default reverts to the operator's value", func() {
+ initial := []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.0.0
+`)
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": initial}, "/landscape", "revert", fs, configv1alpha1.MergeModeHint)).To(Succeed())
+
+ // Operator pins to v1.0.5
+ Expect(fs.WriteFile("/landscape/revert/test.yaml", []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.0.5
+`), 0600)).To(Succeed())
+
+ // GLK ships v1.1.0 — annotation appears
+ updated := []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.1.0
+`)
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": updated}, "/landscape", "revert", fs, configv1alpha1.MergeModeHint)).To(Succeed())
+ content, err := fs.ReadFile("/landscape/revert/test.yaml")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(string(content)).To(ContainSubstring(meta.GLKDefaultPrefix))
+
+ // GLK reverts to v1.0.5 — operator's value now matches the default, no annotation
+ reverted := []byte(`apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.0.5
+`)
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": reverted}, "/landscape", "revert", fs, configv1alpha1.MergeModeHint)).To(Succeed())
+ content, err = fs.ReadFile("/landscape/revert/test.yaml")
+ Expect(err).NotTo(HaveOccurred())
+ Expect(string(content)).NotTo(ContainSubstring(meta.GLKDefaultPrefix))
+ Expect(string(content)).NotTo(ContainSubstring("# Attention - new default:"))
+ })
+ })
})
Describe("#RelativePathFromDirDepth", func() {
diff --git a/pkg/utils/kustomization/kustomization.go b/pkg/utils/kustomization/kustomization.go
index 01e1123b..6ea15ad6 100644
--- a/pkg/utils/kustomization/kustomization.go
+++ b/pkg/utils/kustomization/kustomization.go
@@ -16,6 +16,7 @@ import (
kustomize "sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml"
+ configv1alpha1 "github.com/gardener/gardener-landscape-kit/pkg/apis/config/v1alpha1"
"github.com/gardener/gardener-landscape-kit/pkg/components"
"github.com/gardener/gardener-landscape-kit/pkg/utils/files"
)
@@ -51,14 +52,14 @@ func NewKustomization(resources []string, patches []kustomize.Patch) *kustomize.
// WriteKustomizationComponent writes the objects and a Kustomization file to the fs.
// The Kustomization file references all other objects.
// The objects map will be modified to include the Kustomization file.
-func WriteKustomizationComponent(objects map[string][]byte, baseDir, componentDir string, fs afero.Afero) error {
+func WriteKustomizationComponent(objects map[string][]byte, baseDir, componentDir string, fs afero.Afero, mode configv1alpha1.MergeMode) error {
kustomization := NewKustomization(slices.Collect(maps.Keys(objects)), nil)
content, err := yaml.Marshal(kustomization)
if err != nil {
return err
}
objects[KustomizationFileName] = content
- return files.WriteObjectsToFilesystem(objects, baseDir, componentDir, fs)
+ return files.WriteObjectsToFilesystem(objects, baseDir, componentDir, fs, mode)
}
// WriteLandscapeComponentsKustomizations traverses through the generated components directory and adds
@@ -68,10 +69,10 @@ func WriteLandscapeComponentsKustomizations(options components.Options) error {
targetDir := options.GetTargetPath()
componentsDir := filepath.Join(targetDir, components.DirName)
- return fs.Walk(componentsDir, writeKustomizationsToFileTree(fs, targetDir))
+ return fs.Walk(componentsDir, writeKustomizationsToFileTree(fs, targetDir, options.GetMergeMode()))
}
-func writeKustomizationsToFileTree(fs afero.Afero, targetDir string) func(dir string, info os.FileInfo, err error) error {
+func writeKustomizationsToFileTree(fs afero.Afero, targetDir string, mode configv1alpha1.MergeMode) func(dir string, info os.FileInfo, err error) error {
var completedPaths []string
return func(dir string, info os.FileInfo, err error) error {
@@ -115,11 +116,11 @@ func writeKustomizationsToFileTree(fs afero.Afero, targetDir string) func(dir st
}
relativePath, _ := strings.CutPrefix(dir, targetDir)
- return writeKustomizationFile(fs, targetDir, relativePath, directories)
+ return writeKustomizationFile(fs, targetDir, relativePath, directories, mode)
}
}
-func writeKustomizationFile(fs afero.Afero, landscapeDir, relativePath string, directories []string) error {
+func writeKustomizationFile(fs afero.Afero, landscapeDir, relativePath string, directories []string, mode configv1alpha1.MergeMode) error {
var (
err error
objects = make(map[string][]byte)
@@ -132,5 +133,5 @@ func writeKustomizationFile(fs afero.Afero, landscapeDir, relativePath string, d
objects[KustomizationFileName] = append([]byte(autoGenerationNotice), objects[KustomizationFileName]...)
- return files.WriteObjectsToFilesystem(objects, landscapeDir, relativePath, fs)
+ return files.WriteObjectsToFilesystem(objects, landscapeDir, relativePath, fs, mode)
}
diff --git a/pkg/utils/kustomization/kustomization_test.go b/pkg/utils/kustomization/kustomization_test.go
index 909fe1f1..b7cf67a3 100644
--- a/pkg/utils/kustomization/kustomization_test.go
+++ b/pkg/utils/kustomization/kustomization_test.go
@@ -16,6 +16,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
+ configv1alpha1 "github.com/gardener/gardener-landscape-kit/pkg/apis/config/v1alpha1"
"github.com/gardener/gardener-landscape-kit/pkg/cmd"
generateoptions "github.com/gardener/gardener-landscape-kit/pkg/cmd/generate/options"
"github.com/gardener/gardener-landscape-kit/pkg/components"
@@ -59,7 +60,7 @@ var _ = Describe("Kustomization", func() {
}
)
- Expect(WriteKustomizationComponent(objects, landscapeDir, componentDir, fs)).To(Succeed())
+ Expect(WriteKustomizationComponent(objects, landscapeDir, componentDir, fs, configv1alpha1.MergeModeSilent)).To(Succeed())
contents, err := fs.ReadFile(filepath.Join(landscapeDir, componentDir, "configmap.yaml"))
Expect(err).NotTo(HaveOccurred())
@@ -80,11 +81,15 @@ var _ = Describe("Kustomization", func() {
BeforeEach(func() {
fs = afero.Afero{Fs: afero.NewMemMapFs()}
+ config := &configv1alpha1.LandscapeKitConfiguration{}
+ configv1alpha1.SetObjectDefaults_LandscapeKitConfiguration(config)
+
var err error
opts, err = components.NewOptions(&generateoptions.Options{
Options: &cmd.Options{
Log: logr.Discard(),
},
+ Config: config,
TargetDirPath: "/absolute/path/with/../to/repo/landscape",
}, fs)
Expect(err).NotTo(HaveOccurred())
diff --git a/pkg/utils/meta/diff.go b/pkg/utils/meta/diff.go
index d5e8e100..7699ee74 100644
--- a/pkg/utils/meta/diff.go
+++ b/pkg/utils/meta/diff.go
@@ -13,6 +13,8 @@ import (
"github.com/elliotchance/orderedmap/v3"
"go.yaml.in/yaml/v4"
+
+ configv1alpha1 "github.com/gardener/gardener-landscape-kit/pkg/apis/config/v1alpha1"
)
// section represents a single section in a manifest file (either a manifest or a comment)
@@ -41,7 +43,7 @@ type manifestDiff struct {
// It performs a three-way merge between the old default template, the new default template, and the current user-modified version.
// It preserves user modifications while applying updates from the new default template.
// Contents from the current manifest are prioritized and sorted first.
-func ThreeWayMergeManifest(oldDefaultYaml, newDefaultYaml, currentYaml []byte) ([]byte, error) {
+func ThreeWayMergeManifest(oldDefaultYaml, newDefaultYaml, currentYaml []byte, mode configv1alpha1.MergeMode) ([]byte, error) {
var (
output []byte
@@ -61,7 +63,7 @@ func ThreeWayMergeManifest(oldDefaultYaml, newDefaultYaml, currentYaml []byte) (
current := sect.content
newDefault, _ := diff.newDefault.Get(sect.key)
oldDefault, _ := diff.oldDefault.Get(sect.key)
- merged, err := threeWayMergeSection(oldDefault, newDefault, current)
+ merged, err := threeWayMergeSection(oldDefault, newDefault, current, mode)
if err != nil {
return nil, err
}
@@ -75,7 +77,7 @@ func ThreeWayMergeManifest(oldDefaultYaml, newDefaultYaml, currentYaml []byte) (
continue
}
// Applying threeWayMergeSection with only the new section content to ensure proper formatting (idempotency).
- merged, err := threeWayMergeSection(nil, sect.content, nil)
+ merged, err := threeWayMergeSection(nil, sect.content, nil, mode)
if err != nil {
return nil, err
}
diff --git a/pkg/utils/meta/diff_test.go b/pkg/utils/meta/diff_test.go
index 7d626a2f..755e5dda 100644
--- a/pkg/utils/meta/diff_test.go
+++ b/pkg/utils/meta/diff_test.go
@@ -15,6 +15,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
+ configv1alpha1 "github.com/gardener/gardener-landscape-kit/pkg/apis/config/v1alpha1"
"github.com/gardener/gardener-landscape-kit/pkg/utils/meta"
)
@@ -41,7 +42,7 @@ var _ = Describe("Meta Dir Config Diff", func() {
objYaml, err := yaml.Marshal(obj)
Expect(err).NotTo(HaveOccurred())
- newContents, err := meta.ThreeWayMergeManifest(nil, objYaml, nil)
+ newContents, err := meta.ThreeWayMergeManifest(nil, objYaml, nil, configv1alpha1.MergeModeSilent)
Expect(err).NotTo(HaveOccurred())
// Modify the manifest on disk
@@ -57,7 +58,7 @@ var _ = Describe("Meta Dir Config Diff", func() {
newObjYaml, err := yaml.Marshal(obj)
Expect(err).NotTo(HaveOccurred())
- content, err = meta.ThreeWayMergeManifest(objYaml, newObjYaml, content)
+ content, err = meta.ThreeWayMergeManifest(objYaml, newObjYaml, content, configv1alpha1.MergeModeSilent)
Expect(err).NotTo(HaveOccurred())
expectedConfigMapOutputWithNewKey, err := testdata.ReadFile("testdata/expected_configmap_output_newkey.yaml")
@@ -76,7 +77,7 @@ var _ = Describe("Meta Dir Config Diff", func() {
manifestGenerated, err := testdata.ReadFile("testdata/manifest-4-expected-generated.yaml")
Expect(err).NotTo(HaveOccurred())
- mergedManifest, err := meta.ThreeWayMergeManifest(manifestDefault, manifestDefaultNew, manifestEdited)
+ mergedManifest, err := meta.ThreeWayMergeManifest(manifestDefault, manifestDefaultNew, manifestEdited, configv1alpha1.MergeModeSilent)
Expect(err).NotTo(HaveOccurred())
Expect(string(mergedManifest)).To(Equal(string(manifestGenerated)))
})
@@ -87,7 +88,7 @@ var _ = Describe("Meta Dir Config Diff", func() {
expectedConfigMapOutputWithNewKey, err := testdata.ReadFile("testdata/expected_configmap_output_newkey.yaml")
Expect(err).NotTo(HaveOccurred())
- content, err := meta.ThreeWayMergeManifest(nil, expectedConfigMapOutputWithNewKey, []byte(strings.ReplaceAll(string(expectedDefaultConfigMapOutput), "key: value", "key: newDefaultValue")))
+ content, err := meta.ThreeWayMergeManifest(nil, expectedConfigMapOutputWithNewKey, []byte(strings.ReplaceAll(string(expectedDefaultConfigMapOutput), "key: value", "key: newDefaultValue")), configv1alpha1.MergeModeSilent)
Expect(err).ToNot(HaveOccurred())
Expect(string(content)).To(Equal(strings.ReplaceAll(string(expectedConfigMapOutputWithNewKey), "key: value", "key: newDefaultValue") + "\n"))
})
@@ -102,21 +103,21 @@ var _ = Describe("Meta Dir Config Diff", func() {
multipleManifestsExpectedGenerated, err := testdata.ReadFile("testdata/multiple-manifests-4-expected-generated.yaml")
Expect(err).NotTo(HaveOccurred())
- content, err := meta.ThreeWayMergeManifest(nil, multipleManifestsInitial, nil)
+ content, err := meta.ThreeWayMergeManifest(nil, multipleManifestsInitial, nil, configv1alpha1.MergeModeSilent)
Expect(err).NotTo(HaveOccurred())
Expect(string(content)).To(Equal(string(multipleManifestsInitial)))
- content, err = meta.ThreeWayMergeManifest(multipleManifestsInitial, multipleManifestsInitial, multipleManifestsInitial)
+ content, err = meta.ThreeWayMergeManifest(multipleManifestsInitial, multipleManifestsInitial, multipleManifestsInitial, configv1alpha1.MergeModeSilent)
Expect(err).NotTo(HaveOccurred())
Expect(string(content)).To(Equal(string(multipleManifestsInitial)))
// Editing the written manifest and updating the manifest with the same default content should not overwrite anything
- content, err = meta.ThreeWayMergeManifest(multipleManifestsInitial, multipleManifestsInitial, multipleManifestsEdited)
+ content, err = meta.ThreeWayMergeManifest(multipleManifestsInitial, multipleManifestsInitial, multipleManifestsEdited, configv1alpha1.MergeModeSilent)
Expect(err).NotTo(HaveOccurred())
Expect(string(content)).To(Equal(string(multipleManifestsEdited)))
// New default manifest changes should be applied, while custom edits should be retained.
- content, err = meta.ThreeWayMergeManifest(multipleManifestsInitial, multipleManifestsNewDefault, multipleManifestsEdited)
+ content, err = meta.ThreeWayMergeManifest(multipleManifestsInitial, multipleManifestsNewDefault, multipleManifestsEdited, configv1alpha1.MergeModeSilent)
Expect(err).NotTo(HaveOccurred())
Expect(string(content)).To(Equal(string(multipleManifestsExpectedGenerated)))
})
@@ -131,7 +132,7 @@ var _ = Describe("Meta Dir Config Diff", func() {
expected, err := testdata.ReadFile("testdata/order-4-expected.yaml")
Expect(err).NotTo(HaveOccurred())
- content, err := meta.ThreeWayMergeManifest(oldDefault, newDefault, current)
+ content, err := meta.ThreeWayMergeManifest(oldDefault, newDefault, current, configv1alpha1.MergeModeSilent)
Expect(err).NotTo(HaveOccurred())
Expect(string(content)).To(Equal(string(expected)))
})
@@ -145,22 +146,22 @@ var _ = Describe("Meta Dir Config Diff", func() {
invalidYaml = []byte(`keyWith: colonSuffix:`)
)
- _, err = meta.ThreeWayMergeManifest(emptyYaml, invalidYaml, emptyYaml)
+ _, err = meta.ThreeWayMergeManifest(emptyYaml, invalidYaml, emptyYaml, configv1alpha1.MergeModeSilent)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("parsing newDefault file for manifest diff failed"))
- _, err = meta.ThreeWayMergeManifest(invalidYaml, validYaml, validYaml)
+ _, err = meta.ThreeWayMergeManifest(invalidYaml, validYaml, validYaml, configv1alpha1.MergeModeSilent)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("parsing oldDefault file for manifest diff failed"))
- _, err = meta.ThreeWayMergeManifest(validYaml, validYaml, invalidYaml)
+ _, err = meta.ThreeWayMergeManifest(validYaml, validYaml, invalidYaml, configv1alpha1.MergeModeSilent)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("parsing current file for manifest diff failed"))
- _, err = meta.ThreeWayMergeManifest(validYaml, validYaml, validYaml)
+ _, err = meta.ThreeWayMergeManifest(validYaml, validYaml, validYaml, configv1alpha1.MergeModeSilent)
Expect(err).NotTo(HaveOccurred())
- _, err = meta.ThreeWayMergeManifest(emptyYaml, emptyYaml, emptyYaml)
+ _, err = meta.ThreeWayMergeManifest(emptyYaml, emptyYaml, emptyYaml, configv1alpha1.MergeModeSilent)
Expect(err).NotTo(HaveOccurred())
})
@@ -175,7 +176,7 @@ var _ = Describe("Meta Dir Config Diff", func() {
expected, err := testdata.ReadFile("testdata/replaced-file-4-expected-generated.yaml")
Expect(err).NotTo(HaveOccurred())
- content, err := meta.ThreeWayMergeManifest(oldDefault, newDefault, current)
+ content, err := meta.ThreeWayMergeManifest(oldDefault, newDefault, current, configv1alpha1.MergeModeSilent)
Expect(err).NotTo(HaveOccurred())
Expect(string(content)).To(Equal(string(expected)))
})
@@ -190,7 +191,7 @@ var _ = Describe("Meta Dir Config Diff", func() {
expected, err := testdata.ReadFile("testdata/replaced-file-2-new-default.yaml")
Expect(err).NotTo(HaveOccurred())
- content, err := meta.ThreeWayMergeManifest(oldDefault, newDefault, current)
+ content, err := meta.ThreeWayMergeManifest(oldDefault, newDefault, current, configv1alpha1.MergeModeSilent)
Expect(err).NotTo(HaveOccurred())
Expect(string(content)).To(Equal(string(expected)))
})
@@ -201,7 +202,7 @@ var _ = Describe("Meta Dir Config Diff", func() {
// Two documents with the same structure should be treated as the same manifest across generations.
nonK8sYaml := []byte("foo: bar\nbaz: qux\n")
- content, err := meta.ThreeWayMergeManifest(nonK8sYaml, nonK8sYaml, nonK8sYaml)
+ content, err := meta.ThreeWayMergeManifest(nonK8sYaml, nonK8sYaml, nonK8sYaml, configv1alpha1.MergeModeSilent)
Expect(err).NotTo(HaveOccurred())
Expect(string(content)).To(Equal(string(nonK8sYaml)))
@@ -210,7 +211,7 @@ var _ = Describe("Meta Dir Config Diff", func() {
newDefault := []byte("foo: bar\nbaz: updated\n")
expected := []byte("foo: user-value\nbaz: updated\n")
- content, err = meta.ThreeWayMergeManifest(nonK8sYaml, newDefault, edited)
+ content, err = meta.ThreeWayMergeManifest(nonK8sYaml, newDefault, edited, configv1alpha1.MergeModeSilent)
Expect(err).NotTo(HaveOccurred())
Expect(string(content)).To(MatchYAML(string(expected)))
})
@@ -225,9 +226,64 @@ var _ = Describe("Meta Dir Config Diff", func() {
expected, err := testdata.ReadFile("testdata/replaced-file-6-different-name-merged.yaml")
Expect(err).NotTo(HaveOccurred())
- content, err := meta.ThreeWayMergeManifest(oldDefault, newDefault, current)
+ content, err := meta.ThreeWayMergeManifest(oldDefault, newDefault, current, configv1alpha1.MergeModeSilent)
Expect(err).NotTo(HaveOccurred())
Expect(string(content)).To(Equal(string(expected)))
})
+
+ It("should retain user modifications in slices during a three-way-merge", func() {
+ oldDefault, err := testdata.ReadFile("testdata/merge-slice-1-default.yaml")
+ Expect(err).NotTo(HaveOccurred())
+ newDefault, err := testdata.ReadFile("testdata/merge-slice-3-new-default.yaml")
+ Expect(err).NotTo(HaveOccurred())
+ current, err := testdata.ReadFile("testdata/merge-slice-2-edited.yaml")
+ Expect(err).NotTo(HaveOccurred())
+ expected, err := testdata.ReadFile("testdata/merge-slice-4-expected-generated.yaml")
+ Expect(err).NotTo(HaveOccurred())
+
+ content, err := meta.ThreeWayMergeManifest(oldDefault, newDefault, current, configv1alpha1.MergeModeSilent)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(string(content)).To(Equal(string(expected)))
+ })
+ })
+
+ Describe("#ThreeWayMergeManifest - MergeModeHint", func() {
+ It("should annotate a scalar value that the operator overrode and GLK updated, but not when there is no conflict", func() {
+ oldDefault := []byte(`
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.0.0
+`)
+ newDefault := []byte(`
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.1.0
+`)
+ // Operator pinned to v1.0.5 — conflicts with GLK's new default v1.1.0
+ current := []byte(`
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test
+data:
+ version: v1.0.5
+`)
+ result, err := meta.ThreeWayMergeManifest(oldDefault, newDefault, current, configv1alpha1.MergeModeHint)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(string(result)).To(ContainSubstring("version: v1.0.5"))
+ Expect(string(result)).To(ContainSubstring("# Attention - new default: v1.1.0"))
+
+ // No conflict: operator did not change the value → new default taken silently, no annotation
+ result, err = meta.ThreeWayMergeManifest(oldDefault, newDefault, oldDefault, configv1alpha1.MergeModeHint)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(string(result)).To(ContainSubstring("version: v1.1.0"))
+ Expect(string(result)).NotTo(ContainSubstring(meta.GLKDefaultPrefix))
+ })
})
})
diff --git a/pkg/utils/meta/merge.go b/pkg/utils/meta/merge.go
index 805d28a0..db04dc36 100644
--- a/pkg/utils/meta/merge.go
+++ b/pkg/utils/meta/merge.go
@@ -5,11 +5,56 @@
package meta
import (
+ "strings"
+
"go.yaml.in/yaml/v4"
+
+ configv1alpha1 "github.com/gardener/gardener-landscape-kit/pkg/apis/config/v1alpha1"
+)
+
+const (
+ // GLKDefaultPrefix is the comment prefix for GLK-managed default annotations.
+ // It is exported so callers can use it as the strip anchor when removing annotations.
+ GLKDefaultPrefix = "# Attention - new default: "
)
+// stripGLKAnnotation removes a GLK-managed annotation from a single comment string.
+// If the annotation is part of a multi-line comment, only the annotation line is removed.
+// If the annotation is appended to a value's line comment (e.g. "# user note # Attention …"), the prefix and everything after it is stripped.
+func stripGLKAnnotation(comment string) string {
+ if !strings.Contains(comment, GLKDefaultPrefix) {
+ return comment
+ }
+ lines := strings.Split(comment, "\n")
+ out := make([]string, 0, len(lines))
+ for _, line := range lines {
+ before, _, found := strings.Cut(line, GLKDefaultPrefix)
+ if !found {
+ out = append(out, line)
+ continue
+ }
+ stripped := strings.TrimRight(before, " \t")
+ if stripped != "" {
+ out = append(out, stripped)
+ }
+ }
+ return strings.Join(out, "\n")
+}
+
+// stripGLKAnnotations removes GLK-managed annotations from all comment fields of a node.
+func stripGLKAnnotations(nodes ...*yaml.Node) {
+ for _, n := range nodes {
+ if n == nil {
+ continue
+ }
+ n.HeadComment = stripGLKAnnotation(n.HeadComment)
+ n.LineComment = stripGLKAnnotation(n.LineComment)
+ n.FootComment = stripGLKAnnotation(n.FootComment)
+ }
+}
+
// threeWayMergeSection performs a three-way merge on a single YAML section
-func threeWayMergeSection(oldDefaultYaml, newDefaultYaml, currentYaml []byte) ([]byte, error) {
+func threeWayMergeSection(oldDefaultYaml, newDefaultYaml, currentYaml []byte, mode configv1alpha1.MergeMode) ([]byte, error) {
// Parse all three versions
var oldDefault, newDefault, current yaml.Node
if err := yaml.Unmarshal(newDefaultYaml, &newDefault); err != nil {
@@ -26,14 +71,14 @@ func threeWayMergeSection(oldDefaultYaml, newDefaultYaml, currentYaml []byte) ([
}
}
- return EncodeResult(threeWayMerge(&oldDefault, &newDefault, ¤t))
+ return EncodeResult(threeWayMerge(&oldDefault, &newDefault, ¤t, mode))
}
// threeWayMerge performs a three-way merge of YAML nodes
// oldDefault: the previous default template
// newDefault: the new default template
// current: the user's current version (possibly modified)
-func threeWayMerge(oldDefault, newDefault, current *yaml.Node) *yaml.Node {
+func threeWayMerge(oldDefault, newDefault, current *yaml.Node, mode configv1alpha1.MergeMode) *yaml.Node {
// Unwrap document nodes
if oldDefault.Kind == yaml.DocumentNode {
oldDefault = oldDefault.Content[0]
@@ -44,7 +89,7 @@ func threeWayMerge(oldDefault, newDefault, current *yaml.Node) *yaml.Node {
if current.Kind == yaml.DocumentNode {
return &yaml.Node{
Kind: yaml.DocumentNode,
- Content: []*yaml.Node{threeWayMerge(oldDefault, newDefault, current.Content[0])},
+ Content: []*yaml.Node{threeWayMerge(oldDefault, newDefault, current.Content[0], mode)},
}
}
@@ -97,23 +142,40 @@ func threeWayMerge(oldDefault, newDefault, current *yaml.Node) *yaml.Node {
if !oldExists {
oldValue = &yaml.Node{Kind: yaml.MappingNode}
}
- resultValue = threeWayMerge(oldValue, newValueNode, currentValue)
+ resultValue = threeWayMerge(oldValue, newValueNode, currentValue, mode)
case currentValue.Kind == yaml.SequenceNode && newValueNode.Kind == yaml.SequenceNode:
if !oldExists {
oldValue = &yaml.Node{Kind: yaml.SequenceNode}
}
- resultValue = threeWayMergeSequence(oldValue, newValueNode, currentValue)
- case oldExists && !nodesEqual(oldValue, newValueNode, false):
+ resultValue = threeWayMergeSequence(oldValue, newValueNode, currentValue, mode)
+ case oldExists && !nodesEqual(oldValue, newValueNode, false) && nodesEqual(oldValue, currentValue, false):
+ // Default changed and current was not modified: take the new default.
resultValue = &yaml.Node{
Kind: newValueNode.Kind, Value: newValueNode.Value, Style: newValueNode.Style, Tag: newValueNode.Tag,
HeadComment: currentValue.HeadComment, LineComment: currentValue.LineComment, FootComment: currentValue.FootComment,
Content: newValueNode.Content,
}
mergeNodeComments(oldValue, newValueNode, resultValue)
+ case oldExists && !nodesEqual(oldValue, newValueNode, false):
+ // Both default and current changed: keep current (user's value wins).
+ resultValue = currentValue
+ mergeNodeComments(oldValue, newValueNode, resultValue)
+ if mode == configv1alpha1.MergeModeHint {
+ if !nodesEqual(newValueNode, currentValue, false) {
+ annotateConflict(resultKeyNode, resultValue, newValueNode)
+ } else {
+ // Values converged — strip any lingering GLK annotation.
+ stripGLKAnnotations(resultKeyNode, resultValue)
+ }
+ }
default:
resultValue = currentValue
if oldExists {
mergeNodeComments(oldValue, newValueNode, resultValue)
+ if mode == configv1alpha1.MergeModeHint && nodesEqual(newValueNode, currentValue, false) {
+ // Values converged — strip any lingering GLK annotation.
+ stripGLKAnnotations(resultKeyNode, resultValue)
+ }
}
}
}
@@ -138,6 +200,43 @@ func threeWayMerge(oldDefault, newDefault, current *yaml.Node) *yaml.Node {
return result
}
+// annotateConflict adds a GLK-managed annotation comment to resultValue (or resultKeyNode for complex nodes) indicating the current GLK default.
+// This is used in MergeModeHint when an operator override conflicts with an updated GLK default, so the user is hinted about the divergence.
+//
+// For scalar nodes, the annotation is a line comment on the value node (same line as the value).
+// For complex nodes (mappings/sequences), the annotation is a head comment on the key node (line above the key).
+func annotateConflict(resultKeyNode, resultValue, newDefaultNode *yaml.Node) {
+ switch newDefaultNode.Kind {
+ case yaml.ScalarNode:
+ annotation := glkManagedLineComment(newDefaultNode.Value)
+ // Strip any pre-existing GLK annotation before appending the current one, so repeated runs replace rather than accumulate the comment.
+ stripped := stripGLKAnnotation(resultValue.LineComment)
+ if stripped != "" {
+ resultValue.LineComment = stripped + " " + annotation
+ } else {
+ resultValue.LineComment = annotation
+ }
+ default:
+ // Strip existing GLK annotation from head comment before re-annotating.
+ resultKeyNode.HeadComment = stripGLKAnnotation(resultKeyNode.HeadComment)
+ resultKeyNode.HeadComment = glkManagedHeadComment(resultKeyNode.HeadComment)
+ }
+}
+
+// glkManagedLineComment returns a GLK-managed line comment for a scalar value conflict.
+func glkManagedLineComment(newValue string) string {
+ return GLKDefaultPrefix + newValue
+}
+
+// glkManagedHeadComment returns a GLK-managed head comment for a complex node conflict.
+func glkManagedHeadComment(existingHead string) string {
+ annotation := GLKDefaultPrefix + "(complex node changed)"
+ if existingHead == "" {
+ return annotation
+ }
+ return existingHead + "\n" + annotation
+}
+
// mergeComment performs a three-way merge on a single comment string.
// If the default comment changed (old != new), the new default is applied — unless the user
// has also modified the comment (current != old), in which case the user's comment is kept
@@ -172,7 +271,7 @@ func mergeNodeComments(oldNode, newNode, resultNode *yaml.Node) {
}
// Order is preserved based on newDefault, with user additions appended at the end
-func threeWayMergeSequence(oldDefault, newDefault, current *yaml.Node) *yaml.Node {
+func threeWayMergeSequence(oldDefault, newDefault, current *yaml.Node, mode configv1alpha1.MergeMode) *yaml.Node {
if nodesEqual(oldDefault, current, true) {
return newDefault
}
@@ -229,7 +328,7 @@ func threeWayMergeSequence(oldDefault, newDefault, current *yaml.Node) *yaml.Nod
if !existsInOld {
oldItem = &yaml.Node{Kind: yaml.MappingNode}
}
- result.Content = append(result.Content, threeWayMerge(oldItem, newItem, currentItem))
+ result.Content = append(result.Content, threeWayMerge(oldItem, newItem, currentItem, mode))
}
// Append items from newDefault that are truly new (not in old) and not already in current.
@@ -249,7 +348,19 @@ func threeWayMergeSequence(oldDefault, newDefault, current *yaml.Node) *yaml.Nod
return result
}
- // No identity key: fall back to full-string set-based merge.
+ // No identity key found.
+ // When all three sequences have the same length and all items are mappings,
+ // match items by position and three-way merge each pair. This handles cases
+ // where list items are modified but their count and order stay the same
+ // (e.g., a single scalar field in a mapping item is changed).
+ if len(oldDefault.Content) == len(newDefault.Content) && len(newDefault.Content) == len(current.Content) && allMappings(oldDefault, newDefault, current) {
+ for i := range current.Content {
+ result.Content = append(result.Content, threeWayMerge(oldDefault.Content[i], newDefault.Content[i], current.Content[i], mode))
+ }
+ return result
+ }
+
+ // Fall back to full-string set-based merge.
oldSet := make(map[string]bool)
for _, item := range oldDefault.Content {
oldSet[nodeToString(item)] = true
@@ -341,3 +452,15 @@ func mappingValue(node *yaml.Node, key string) string {
}
return ""
}
+
+// allMappings reports whether every item in each of the given sequences is a mapping node.
+func allMappings(seqs ...*yaml.Node) bool {
+ for _, seq := range seqs {
+ for _, item := range seq.Content {
+ if item.Kind != yaml.MappingNode {
+ return false
+ }
+ }
+ }
+ return true
+}
diff --git a/pkg/utils/meta/preprocessing_test.go b/pkg/utils/meta/preprocessing_test.go
index f879d0e2..8e1971ae 100644
--- a/pkg/utils/meta/preprocessing_test.go
+++ b/pkg/utils/meta/preprocessing_test.go
@@ -10,6 +10,7 @@ import (
"github.com/spf13/afero"
"go.yaml.in/yaml/v4"
+ configv1alpha1 "github.com/gardener/gardener-landscape-kit/pkg/apis/config/v1alpha1"
"github.com/gardener/gardener-landscape-kit/pkg/utils/files"
"github.com/gardener/gardener-landscape-kit/pkg/utils/meta"
)
@@ -68,7 +69,7 @@ var _ = Describe("YAML Preprocessing", func() {
Expect(err).ToNot(HaveOccurred())
Expect(fs.WriteFile("/landscape/manifest/test.yaml", testFile, 0600)).To(Succeed())
- Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": {}}, "/landscape", "manifest", fs)).To(Succeed())
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": {}}, "/landscape", "manifest", fs, configv1alpha1.MergeModeSilent)).To(Succeed())
content, err := fs.ReadFile("/landscape/manifest/test.yaml")
Expect(err).ToNot(HaveOccurred())
@@ -82,7 +83,7 @@ var _ = Describe("YAML Preprocessing", func() {
Expect(err).ToNot(HaveOccurred())
Expect(fs.WriteFile("/landscape/manifest/test.yaml", testFile, 0600)).To(Succeed())
- Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": {}}, "/landscape", "manifest", fs)).To(Succeed())
+ Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": {}}, "/landscape", "manifest", fs, configv1alpha1.MergeModeSilent)).To(Succeed())
content, err := fs.ReadFile("/landscape/manifest/test.yaml")
Expect(err).ToNot(HaveOccurred())
diff --git a/pkg/utils/meta/testdata/merge-slice-1-default.yaml b/pkg/utils/meta/testdata/merge-slice-1-default.yaml
new file mode 100644
index 00000000..da79a139
--- /dev/null
+++ b/pkg/utils/meta/testdata/merge-slice-1-default.yaml
@@ -0,0 +1,3 @@
+components:
+- name: github.com/gardener/gardener
+ version: v1.139.0
diff --git a/pkg/utils/meta/testdata/merge-slice-2-edited.yaml b/pkg/utils/meta/testdata/merge-slice-2-edited.yaml
new file mode 100644
index 00000000..db6a125c
--- /dev/null
+++ b/pkg/utils/meta/testdata/merge-slice-2-edited.yaml
@@ -0,0 +1,3 @@
+components:
+- name: github.com/gardener/gardener
+ version: v1.99.0
diff --git a/pkg/utils/meta/testdata/merge-slice-3-new-default.yaml b/pkg/utils/meta/testdata/merge-slice-3-new-default.yaml
new file mode 100644
index 00000000..8542ceb7
--- /dev/null
+++ b/pkg/utils/meta/testdata/merge-slice-3-new-default.yaml
@@ -0,0 +1,3 @@
+components:
+- name: github.com/gardener/gardener
+ version: v1.139.1
diff --git a/pkg/utils/meta/testdata/merge-slice-4-expected-generated.yaml b/pkg/utils/meta/testdata/merge-slice-4-expected-generated.yaml
new file mode 100644
index 00000000..db6a125c
--- /dev/null
+++ b/pkg/utils/meta/testdata/merge-slice-4-expected-generated.yaml
@@ -0,0 +1,3 @@
+components:
+- name: github.com/gardener/gardener
+ version: v1.99.0
diff --git a/pkg/utils/test/kustomize.go b/pkg/utils/test/kustomize.go
index 780b7a57..3f5eccf1 100644
--- a/pkg/utils/test/kustomize.go
+++ b/pkg/utils/test/kustomize.go
@@ -118,6 +118,7 @@ func KustomizeComponent(
},
}
)
+ v1alpha1.SetObjectDefaults_LandscapeKitConfiguration(generateOpts.Config)
baseOpts, err := components.NewOptions(generateOpts, fs)
if err != nil {