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