1// Copyright 2019 The Kubernetes Authors.
2// SPDX-License-Identifier: Apache-2.0
3
4package merge2
5
6import (
7	"fmt"
8
9	"sigs.k8s.io/kustomize/kyaml/yaml"
10)
11
12// A strategic merge patch directive.
13// See https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md
14//
15//go:generate stringer -type=smpDirective -linecomment
16type smpDirective int
17
18const (
19	smpUnknown smpDirective = iota // unknown
20	smpReplace                     // replace
21	smpDelete                      // delete
22	smpMerge                       // merge
23)
24
25const strategicMergePatchDirectiveKey = "$patch"
26
27// Examine patch for a strategic merge patch directive.
28// If found, return it, and remove the directive from the patch.
29func determineSmpDirective(patch *yaml.RNode) (smpDirective, error) {
30	if patch == nil {
31		return smpMerge, nil
32	}
33	switch patch.YNode().Kind {
34	case yaml.SequenceNode:
35		return determineSequenceNodePatchStrategy(patch)
36	case yaml.MappingNode:
37		return determineMappingNodePatchStrategy(patch)
38	default:
39		return smpUnknown, fmt.Errorf(
40			"no implemented strategic merge patch strategy for '%s' ('%s')",
41			patch.YNode().ShortTag(), patch.MustString())
42	}
43}
44
45func determineSequenceNodePatchStrategy(patch *yaml.RNode) (smpDirective, error) {
46	// get the $patch element
47	node, err := patch.Pipe(yaml.GetElementByKey(strategicMergePatchDirectiveKey))
48	// if there are more than 1 key/value pair in the map, then this $patch
49	// is not for the sequence
50	if err != nil || node == nil || node.YNode() == nil || len(node.Content()) > 2 {
51		return smpMerge, nil
52	}
53	// get the value
54	value, err := node.Pipe(yaml.Get(strategicMergePatchDirectiveKey))
55	if err != nil || value == nil || value.YNode() == nil {
56		return smpMerge, nil
57	}
58	v := value.YNode().Value
59	if v == smpDelete.String() {
60		return smpDelete, elideSequencePatchDirective(patch, v)
61	}
62	if v == smpReplace.String() {
63		return smpReplace, elideSequencePatchDirective(patch, v)
64	}
65	if v == smpMerge.String() {
66		return smpMerge, elideSequencePatchDirective(patch, v)
67	}
68	return smpUnknown, fmt.Errorf(
69		"unknown patch strategy '%s'", v)
70}
71
72func determineMappingNodePatchStrategy(patch *yaml.RNode) (smpDirective, error) {
73	node, err := patch.Pipe(yaml.Get(strategicMergePatchDirectiveKey))
74	if err != nil || node == nil || node.YNode() == nil {
75		return smpMerge, nil
76	}
77	v := node.YNode().Value
78	if v == smpDelete.String() {
79		return smpDelete, elideMappingPatchDirective(patch)
80	}
81	if v == smpReplace.String() {
82		return smpReplace, elideMappingPatchDirective(patch)
83	}
84	if v == smpMerge.String() {
85		return smpMerge, elideMappingPatchDirective(patch)
86	}
87	return smpUnknown, fmt.Errorf(
88		"unknown patch strategy '%s'", v)
89}
90
91func elideMappingPatchDirective(patch *yaml.RNode) error {
92	return patch.PipeE(yaml.Clear(strategicMergePatchDirectiveKey))
93}
94
95func elideSequencePatchDirective(patch *yaml.RNode, value string) error {
96	return patch.PipeE(yaml.ElementSetter{
97		Element: nil,
98		Keys:    []string{strategicMergePatchDirectiveKey},
99		Values:  []string{value},
100	})
101}
102