Skip to content

Commit 3ddf8fb

Browse files
committed
Address review comments
1 parent a513021 commit 3ddf8fb

File tree

10 files changed

+219
-102
lines changed

10 files changed

+219
-102
lines changed

docs/api-reference/landscapekit-v1alpha1.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,23 @@ _Appears in:_
8181

8282

8383

84+
#### MergeMode
85+
86+
_Underlying type:_ _string_
87+
88+
MergeMode controls how operator overwrites are handled during three-way merge.
89+
90+
91+
92+
_Appears in:_
93+
- [LandscapeKitConfiguration](#landscapekitconfiguration)
94+
95+
| Field | Description |
96+
| --- | --- |
97+
| `Informative` | MergeModeInformative annotates operator-overwritten values with a comment showing the current GLK default.<br /> |
98+
| `Silent` | MergeModeSilent retains operator overwrites without annotation.<br /> |
99+
100+
84101
#### OCMComponent
85102

86103

example/20-componentconfig-glk.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ kind: LandscapeKitConfiguration
1919
# - component-name
2020
# versionConfig:
2121
# defaultVersionsUpdateStrategy: ReleaseBranch
22+
# mergeMode: Informative

pkg/apis/config/v1alpha1/types.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ type LandscapeKitConfiguration struct {
2626
// VersionConfig is the configuration for versioning.
2727
// +optional
2828
VersionConfig *VersionConfiguration `json:"versionConfig,omitempty"`
29-
// MergeMode controls how operator overrides conflicting with updated GLK defaults are handled during three-way merge.
30-
// Possible values are "Informative" (default) and "Silent".
29+
// MergeMode determines how merge conflicts are resolved:
30+
// - "Informative" (default): New default values from GLK are added as comments after any customized values.
31+
// - "Silent": Operator-customized values are retained, new default values are omitted.
3132
// +optional
3233
MergeMode *MergeMode `json:"mergeMode,omitempty"`
3334
}
@@ -124,13 +125,13 @@ type VersionConfiguration struct {
124125
DefaultVersionsUpdateStrategy *DefaultVersionsUpdateStrategy `json:"defaultVersionsUpdateStrategy,omitempty"`
125126
}
126127

127-
// MergeMode controls how operator overrides are handled during three-way merge.
128+
// MergeMode controls how operator overwrites are handled during three-way merge.
128129
type MergeMode string
129130

130131
const (
131-
// MergeModeInformative annotates operator-overridden values with a comment showing the current GLK default.
132+
// MergeModeInformative annotates operator-overwritten values with a comment showing the current GLK default.
132133
MergeModeInformative MergeMode = "Informative"
133-
// MergeModeSilent retains operator overrides without annotation.
134+
// MergeModeSilent retains operator overwrites without annotation.
134135
MergeModeSilent MergeMode = "Silent"
135136
)
136137

@@ -141,10 +142,9 @@ var AllowedMergeModes = []string{
141142
}
142143

143144
// GetMergeMode returns the configured MergeMode, defaulting to MergeModeInformative.
144-
// It is safe to call on a nil receiver.
145145
func (c *LandscapeKitConfiguration) GetMergeMode() MergeMode {
146-
if c != nil && c.MergeMode != nil {
147-
return *c.MergeMode
146+
if c == nil || c.MergeMode == nil {
147+
return MergeModeInformative
148148
}
149-
return MergeModeInformative
149+
return *c.MergeMode
150150
}

pkg/apis/config/v1alpha1/validation/validation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func ValidateLandscapeKitConfiguration(conf *configv1alpha1.LandscapeKitConfigur
3737
}
3838

3939
if conf.MergeMode != nil && !slices.Contains(configv1alpha1.AllowedMergeModes, string(*conf.MergeMode)) {
40-
allErrs = append(allErrs, field.Invalid(field.NewPath("mergeMode"), *conf.MergeMode, "allowed values are: "+strings.Join(configv1alpha1.AllowedMergeModes, ", ")))
40+
allErrs = append(allErrs, field.NotSupported(field.NewPath("mergeMode"), *conf.MergeMode, configv1alpha1.AllowedMergeModes))
4141
}
4242

4343
return allErrs

pkg/apis/config/v1alpha1/validation/validation_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ var _ = Describe("Validation", func() {
324324
errList := validation.ValidateLandscapeKitConfiguration(conf)
325325
Expect(errList).To(ConsistOf(
326326
PointTo(MatchFields(IgnoreExtras, Fields{
327-
"Type": Equal(field.ErrorTypeInvalid),
327+
"Type": Equal(field.ErrorTypeNotSupported),
328328
"Field": Equal("mergeMode"),
329329
"BadValue": Equal(invalid),
330330
})),

pkg/components/types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ type Options interface {
3636
GetFilesystem() afero.Afero
3737
// GetLogger returns the logger instance.
3838
GetLogger() logr.Logger
39-
// GetMergeMode returns the configured merge mode for three-way merges.
39+
// GetMergeMode returns the configured mode to solve merge conflicts.
4040
GetMergeMode() configv1alpha1.MergeMode
4141
}
4242

pkg/utils/files/writer.go

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"os"
1111
"path"
1212
"path/filepath"
13-
"strings"
1413

1514
"github.com/spf13/afero"
1615

@@ -46,30 +45,6 @@ func isSecret(contents []byte) bool {
4645
return bytes.HasPrefix(contents, []byte(kindSecret)) || bytes.Contains(contents, []byte("\n"+kindSecret))
4746
}
4847

49-
// stripGLKManagedAnnotations removes GLK-managed annotation comments from YAML bytes.
50-
// A line is annotated when it contains both GLKDefaultPrefix and GLKManagedMarker.
51-
func stripGLKManagedAnnotations(data []byte) []byte {
52-
lines := strings.Split(string(data), "\n")
53-
out := make([]string, 0, len(lines))
54-
for _, line := range lines {
55-
if !strings.Contains(line, meta.GLKManagedMarker) {
56-
out = append(out, line)
57-
continue
58-
}
59-
// Strip from the start of the GLK annotation prefix.
60-
if idx := strings.Index(line, meta.GLKDefaultPrefix); idx >= 0 {
61-
stripped := strings.TrimRight(line[:idx], " \t")
62-
// If the entire line was a head comment annotation, drop the line entirely.
63-
if strings.TrimSpace(stripped) == "" {
64-
continue
65-
}
66-
out = append(out, stripped)
67-
}
68-
// If for some reason the marker is present but the prefix is not, drop the line.
69-
}
70-
return []byte(strings.Join(out, "\n"))
71-
}
72-
7348
// WriteObjectsToFilesystem writes the given objects to the filesystem at the specified rootDir and relativeFilePath.
7449
// If the manifest file already exists, it patches changes from the new default.
7550
// Additionally, it maintains a default version of the manifest in a separate directory for future diff checks.
@@ -107,9 +82,6 @@ func WriteObjectsToFilesystem(objects map[string][]byte, rootDir, relativeFilePa
10782
object = append([]byte(secretEncryptionDisclaimer), object...)
10883
}
10984

110-
// Strip GLK-managed annotations from the current file before merging, so they are always re-evaluated (idempotency: the annotation reflects the *current* GLK default).
111-
currentYaml = stripGLKManagedAnnotations(currentYaml)
112-
11385
output, err := meta.ThreeWayMergeManifest(oldDefaultYaml, object, currentYaml, mode)
11486
if err != nil {
11587
return err

pkg/utils/files/writer_test.go

Lines changed: 112 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -140,102 +140,168 @@ spec:
140140
Not(ContainSubstring(`# SECURITY ADVISORY`)),
141141
))
142142
})
143-
})
144143

145-
Describe("#WriteObjectsToFilesystem - MergeModeInformative", func() {
146-
It("should annotate operator-overridden scalar values with the GLK default and preserve user comments idempotently", func() {
147-
initial := []byte(`apiVersion: v1
144+
DescribeTable("should annotate operator-overwritten values only in Informative mode",
145+
func(mode configv1alpha1.MergeMode, expectAnnotation bool) {
146+
initial := []byte(`apiVersion: v1
148147
kind: ConfigMap
149148
metadata:
150149
name: test
151150
data:
152151
version: v1.0.0
153152
`)
154-
// First generate: establish defaults
155-
Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": initial}, "/landscape", "manifest", fs, configv1alpha1.MergeModeInformative)).To(Succeed())
153+
Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": initial}, "/landscape", "manifest", fs, mode)).To(Succeed())
156154

157-
// Operator pins to a custom version with a comment explaining why
158-
Expect(fs.WriteFile("/landscape/manifest/test.yaml", []byte(`apiVersion: v1
155+
// Operator pins to a custom version with a comment explaining why
156+
Expect(fs.WriteFile("/landscape/manifest/test.yaml", []byte(`apiVersion: v1
159157
kind: ConfigMap
160158
metadata:
161159
name: test
162160
data:
163161
version: v1.0.5 # pinned for production
164162
`), 0600)).To(Succeed())
165163

166-
// GLK ships a new default with a newer version
167-
updated := []byte(`apiVersion: v1
164+
// GLK ships a new default with a newer version
165+
updated := []byte(`apiVersion: v1
168166
kind: ConfigMap
169167
metadata:
170168
name: test
171169
data:
172170
version: v1.1.0
173171
`)
174-
Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": updated}, "/landscape", "manifest", fs, configv1alpha1.MergeModeInformative)).To(Succeed())
172+
Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": updated}, "/landscape", "manifest", fs, mode)).To(Succeed())
173+
174+
content, err := fs.ReadFile("/landscape/manifest/test.yaml")
175+
Expect(err).NotTo(HaveOccurred())
176+
Expect(string(content)).To(ContainSubstring("version: v1.0.5"))
177+
Expect(string(content)).To(ContainSubstring("pinned for production"))
178+
179+
if expectAnnotation {
180+
Expect(string(content)).To(ContainSubstring(meta.GLKDefaultPrefix + "v1.1.0"))
181+
182+
// Re-run with the same default — annotation persists because the user did not remove it.
183+
Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": updated}, "/landscape", "manifest", fs, mode)).To(Succeed())
184+
content2, err := fs.ReadFile("/landscape/manifest/test.yaml")
185+
Expect(err).NotTo(HaveOccurred())
186+
Expect(string(content2)).To(Equal(string(content)))
187+
} else {
188+
Expect(string(content)).NotTo(ContainSubstring(meta.GLKDefaultPrefix))
189+
}
190+
},
191+
Entry("Silent", configv1alpha1.MergeModeSilent, false),
192+
Entry("Informative", configv1alpha1.MergeModeInformative, true),
193+
)
175194

176-
content, err := fs.ReadFile("/landscape/manifest/test.yaml")
177-
Expect(err).NotTo(HaveOccurred())
178-
// Operator's override is preserved
179-
Expect(string(content)).To(ContainSubstring("version: v1.0.5"))
180-
// User comment is preserved
181-
Expect(string(content)).To(ContainSubstring("pinned for production"))
182-
// GLK default annotation is added
183-
Expect(string(content)).To(ContainSubstring("# glk default: v1.1.0"))
184-
Expect(string(content)).To(ContainSubstring(meta.GLKManagedMarker))
185-
186-
// Re-run with the same inputs — annotation and user comment must not be doubled
187-
Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": updated}, "/landscape", "manifest", fs, configv1alpha1.MergeModeInformative)).To(Succeed())
188-
189-
content2, err := fs.ReadFile("/landscape/manifest/test.yaml")
190-
Expect(err).NotTo(HaveOccurred())
191-
Expect(string(content2)).To(Equal(string(content)))
192-
})
195+
Context("MergeMode Informative", func() {
196+
It("should not re-add the annotation after the user removed it, until the default changes again", func() {
197+
initial := []byte(`apiVersion: v1
198+
kind: ConfigMap
199+
metadata:
200+
name: test
201+
data:
202+
version: v1.0.0
203+
`)
204+
Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": initial}, "/landscape", "manifest", fs, configv1alpha1.MergeModeInformative)).To(Succeed())
205+
206+
// Operator pins to v1.0.5
207+
Expect(fs.WriteFile("/landscape/manifest/test.yaml", []byte(`apiVersion: v1
208+
kind: ConfigMap
209+
metadata:
210+
name: test
211+
data:
212+
version: v1.0.5
213+
`), 0600)).To(Succeed())
214+
215+
// GLK ships v1.1.0 — annotation appears
216+
v110 := []byte(`apiVersion: v1
217+
kind: ConfigMap
218+
metadata:
219+
name: test
220+
data:
221+
version: v1.1.0
222+
`)
223+
Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": v110}, "/landscape", "manifest", fs, configv1alpha1.MergeModeInformative)).To(Succeed())
224+
content, err := fs.ReadFile("/landscape/manifest/test.yaml")
225+
Expect(err).NotTo(HaveOccurred())
226+
Expect(string(content)).To(ContainSubstring(meta.GLKDefaultPrefix))
227+
228+
// User acknowledges the annotation and removes it manually
229+
Expect(fs.WriteFile("/landscape/manifest/test.yaml", []byte(`apiVersion: v1
230+
kind: ConfigMap
231+
metadata:
232+
name: test
233+
data:
234+
version: v1.0.5
235+
`), 0600)).To(Succeed())
236+
237+
// Re-run with the same default — annotation stays removed
238+
Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": v110}, "/landscape", "manifest", fs, configv1alpha1.MergeModeInformative)).To(Succeed())
239+
content, err = fs.ReadFile("/landscape/manifest/test.yaml")
240+
Expect(err).NotTo(HaveOccurred())
241+
Expect(string(content)).To(ContainSubstring("version: v1.0.5"))
242+
Expect(string(content)).NotTo(ContainSubstring(meta.GLKDefaultPrefix))
193243

194-
It("should remove the annotation entirely when the GLK default reverts to the operator's value", func() {
195-
initial := []byte(`apiVersion: v1
244+
// GLK ships v1.2.0 — annotation re-appears because the default changed
245+
v120 := []byte(`apiVersion: v1
246+
kind: ConfigMap
247+
metadata:
248+
name: test
249+
data:
250+
version: v1.2.0
251+
`)
252+
Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": v120}, "/landscape", "manifest", fs, configv1alpha1.MergeModeInformative)).To(Succeed())
253+
content, err = fs.ReadFile("/landscape/manifest/test.yaml")
254+
Expect(err).NotTo(HaveOccurred())
255+
Expect(string(content)).To(ContainSubstring("version: v1.0.5"))
256+
Expect(string(content)).To(ContainSubstring(meta.GLKDefaultPrefix + "v1.2.0"))
257+
})
258+
259+
It("should remove the annotation entirely when the GLK default reverts to the operator's value", func() {
260+
initial := []byte(`apiVersion: v1
196261
kind: ConfigMap
197262
metadata:
198263
name: test
199264
data:
200265
version: v1.0.0
201266
`)
202-
Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": initial}, "/landscape", "revert", fs, configv1alpha1.MergeModeInformative)).To(Succeed())
267+
Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": initial}, "/landscape", "revert", fs, configv1alpha1.MergeModeInformative)).To(Succeed())
203268

204-
// Operator pins to v1.0.5
205-
Expect(fs.WriteFile("/landscape/revert/test.yaml", []byte(`apiVersion: v1
269+
// Operator pins to v1.0.5
270+
Expect(fs.WriteFile("/landscape/revert/test.yaml", []byte(`apiVersion: v1
206271
kind: ConfigMap
207272
metadata:
208273
name: test
209274
data:
210275
version: v1.0.5
211276
`), 0600)).To(Succeed())
212277

213-
// GLK ships v1.1.0 — annotation appears
214-
updated := []byte(`apiVersion: v1
278+
// GLK ships v1.1.0 — annotation appears
279+
updated := []byte(`apiVersion: v1
215280
kind: ConfigMap
216281
metadata:
217282
name: test
218283
data:
219284
version: v1.1.0
220285
`)
221-
Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": updated}, "/landscape", "revert", fs, configv1alpha1.MergeModeInformative)).To(Succeed())
222-
content, err := fs.ReadFile("/landscape/revert/test.yaml")
223-
Expect(err).NotTo(HaveOccurred())
224-
Expect(string(content)).To(ContainSubstring(meta.GLKManagedMarker))
286+
Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": updated}, "/landscape", "revert", fs, configv1alpha1.MergeModeInformative)).To(Succeed())
287+
content, err := fs.ReadFile("/landscape/revert/test.yaml")
288+
Expect(err).NotTo(HaveOccurred())
289+
Expect(string(content)).To(ContainSubstring(meta.GLKDefaultPrefix))
225290

226-
// GLK reverts to v1.0.5 — operator's value now matches the default, no annotation
227-
reverted := []byte(`apiVersion: v1
291+
// GLK reverts to v1.0.5 — operator's value now matches the default, no annotation
292+
reverted := []byte(`apiVersion: v1
228293
kind: ConfigMap
229294
metadata:
230295
name: test
231296
data:
232297
version: v1.0.5
233298
`)
234-
Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": reverted}, "/landscape", "revert", fs, configv1alpha1.MergeModeInformative)).To(Succeed())
235-
content, err = fs.ReadFile("/landscape/revert/test.yaml")
236-
Expect(err).NotTo(HaveOccurred())
237-
Expect(string(content)).NotTo(ContainSubstring(meta.GLKManagedMarker))
238-
Expect(string(content)).NotTo(ContainSubstring("# glk default:"))
299+
Expect(files.WriteObjectsToFilesystem(map[string][]byte{"test.yaml": reverted}, "/landscape", "revert", fs, configv1alpha1.MergeModeInformative)).To(Succeed())
300+
content, err = fs.ReadFile("/landscape/revert/test.yaml")
301+
Expect(err).NotTo(HaveOccurred())
302+
Expect(string(content)).NotTo(ContainSubstring(meta.GLKDefaultPrefix))
303+
Expect(string(content)).NotTo(ContainSubstring("# Attention - new default:"))
304+
})
239305
})
240306
})
241307

pkg/utils/meta/diff_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,14 +277,13 @@ data:
277277
result, err := meta.ThreeWayMergeManifest(oldDefault, newDefault, current, configv1alpha1.MergeModeInformative)
278278
Expect(err).NotTo(HaveOccurred())
279279
Expect(string(result)).To(ContainSubstring("version: v1.0.5"))
280-
Expect(string(result)).To(ContainSubstring("# glk default: v1.1.0"))
281-
Expect(string(result)).To(ContainSubstring(meta.GLKManagedMarker))
280+
Expect(string(result)).To(ContainSubstring("# Attention - new default: v1.1.0"))
282281

283282
// No conflict: operator did not change the value → new default taken silently, no annotation
284283
result, err = meta.ThreeWayMergeManifest(oldDefault, newDefault, oldDefault, configv1alpha1.MergeModeInformative)
285284
Expect(err).NotTo(HaveOccurred())
286285
Expect(string(result)).To(ContainSubstring("version: v1.1.0"))
287-
Expect(string(result)).NotTo(ContainSubstring(meta.GLKManagedMarker))
286+
Expect(string(result)).NotTo(ContainSubstring(meta.GLKDefaultPrefix))
288287
})
289288
})
290289
})

0 commit comments

Comments
 (0)