Skip to content

Commit 49b7ce2

Browse files
authored
Avoid deleting local resources removed from upstream if there are local changes (#2240)
* Avoid deleting local resources removed from upstream if there are local changes * Addressed comments
1 parent c807b9b commit 49b7ce2

File tree

5 files changed

+281
-164
lines changed

5 files changed

+281
-164
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ require (
2222
k8s.io/kubectl v0.21.1
2323
sigs.k8s.io/cli-utils v0.25.1-0.20210603052138-670dee18a123
2424
sigs.k8s.io/kustomize/api v0.8.10 // indirect
25-
sigs.k8s.io/kustomize/kyaml v0.10.21
25+
sigs.k8s.io/kustomize/kyaml v0.10.22-0.20210614195535-7e8ba62e9fd9
2626
)

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -992,8 +992,8 @@ sigs.k8s.io/kustomize/cmd/config v0.9.10/go.mod h1:Mrby0WnRH7hA6OwOYnYpfpiY0WJIM
992992
sigs.k8s.io/kustomize/kustomize/v4 v4.1.2/go.mod h1:PxBvo4WGYlCLeRPL+ziT64wBXqbgfcalOS/SXa/tcyo=
993993
sigs.k8s.io/kustomize/kyaml v0.10.17/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
994994
sigs.k8s.io/kustomize/kyaml v0.10.20/go.mod h1:TYWhGwW9vjoRh3rWqBwB/ZOXyEGRVWe7Ggc3+KZIO+c=
995-
sigs.k8s.io/kustomize/kyaml v0.10.21 h1:KdoEgz3HzmcaLUTFqs6aaqFpsaA9MVRIwOZbi8vMaD0=
996-
sigs.k8s.io/kustomize/kyaml v0.10.21/go.mod h1:TYWhGwW9vjoRh3rWqBwB/ZOXyEGRVWe7Ggc3+KZIO+c=
995+
sigs.k8s.io/kustomize/kyaml v0.10.22-0.20210614195535-7e8ba62e9fd9 h1:voAjfxarCKo1j3Kh34T3dSqbTi6EpfcVn/xGDvcWotE=
996+
sigs.k8s.io/kustomize/kyaml v0.10.22-0.20210614195535-7e8ba62e9fd9/go.mod h1:TYWhGwW9vjoRh3rWqBwB/ZOXyEGRVWe7Ggc3+KZIO+c=
997997
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
998998
sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8=
999999
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=

internal/util/merge/merge3.go

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,11 @@ func (m Merge3) Merge() error {
9797
})
9898

9999
rmMatcher := ResourceMergeMatcher{MergeOnPath: m.MergeOnPath}
100-
kyamlMerge := filters.Merge3{Matcher: &rmMatcher}
100+
resourceHandler := resourceHandler{}
101+
kyamlMerge := filters.Merge3{
102+
Matcher: &rmMatcher,
103+
Handler: &resourceHandler,
104+
}
101105

102106
return kio.Pipeline{
103107
Inputs: inputs,
@@ -268,3 +272,79 @@ func metadataComment(node *yaml.RNode) string {
268272
}
269273
return mf.Key.YNode().LineComment
270274
}
275+
276+
// resourceHandler is an implementation of the ResourceHandler interface from
277+
// kyaml. It is used to decide how a resource should be handled during the
278+
// 3-way merge. This differs from the default implementation in that if a
279+
// resource is deleted from upstream, it will only be deleted from local if
280+
// there is no diff between origin and local.
281+
type resourceHandler struct {
282+
keptResources []*yaml.RNode
283+
}
284+
285+
func (r *resourceHandler) Handle(origin, upstream, local *yaml.RNode) (filters.ResourceMergeStrategy, error) {
286+
var strategy filters.ResourceMergeStrategy
287+
switch {
288+
// Keep the resource if added locally.
289+
case origin == nil && upstream == nil && local != nil:
290+
strategy = filters.KeepDest
291+
// Add the resource if added in upstream.
292+
case origin == nil && upstream != nil && local == nil:
293+
strategy = filters.KeepUpdated
294+
// Do not re-add the resource if deleted from both upstream and local
295+
case upstream == nil && local == nil:
296+
strategy = filters.Skip
297+
// If deleted from upstream, only delete if local fork does not have changes.
298+
case origin != nil && upstream == nil:
299+
equal, err := r.equals(origin, local)
300+
if err != nil {
301+
return strategy, err
302+
}
303+
if equal {
304+
strategy = filters.Skip
305+
} else {
306+
r.keptResources = append(r.keptResources, local)
307+
strategy = filters.KeepDest
308+
}
309+
// Do not re-add if deleted from local.
310+
case origin != nil && local == nil:
311+
strategy = filters.Skip
312+
default:
313+
strategy = filters.Merge
314+
}
315+
return strategy, nil
316+
}
317+
318+
func (*resourceHandler) equals(r1, r2 *yaml.RNode) (bool, error) {
319+
// We need to create new copies of the resources since we need to
320+
// mutate them before comparing them.
321+
r1Clone, err := yaml.Parse(r1.MustString())
322+
if err != nil {
323+
return false, err
324+
}
325+
r2Clone, err := yaml.Parse(r2.MustString())
326+
if err != nil {
327+
return false, err
328+
}
329+
330+
// The resources include annotations with information used during the merge
331+
// process. We need to remove those before comparing the resources.
332+
if err := stripKyamlAnnos(r1Clone); err != nil {
333+
return false, err
334+
}
335+
if err := stripKyamlAnnos(r2Clone); err != nil {
336+
return false, err
337+
}
338+
339+
return r1Clone.MustString() == r2Clone.MustString(), nil
340+
}
341+
342+
func stripKyamlAnnos(n *yaml.RNode) error {
343+
for _, a := range []string{mergeSourceAnnotation, kioutil.PathAnnotation, kioutil.IndexAnnotation} {
344+
err := n.PipeE(yaml.ClearAnnotation(a))
345+
if err != nil {
346+
return err
347+
}
348+
}
349+
return nil
350+
}

0 commit comments

Comments
 (0)