1
2
3
4 package replacement
5
6 import (
7 "fmt"
8 "strings"
9
10 "sigs.k8s.io/kustomize/api/internal/utils"
11 "sigs.k8s.io/kustomize/api/resource"
12 "sigs.k8s.io/kustomize/api/types"
13 "sigs.k8s.io/kustomize/kyaml/errors"
14 "sigs.k8s.io/kustomize/kyaml/resid"
15 kyaml_utils "sigs.k8s.io/kustomize/kyaml/utils"
16 "sigs.k8s.io/kustomize/kyaml/yaml"
17 )
18
19 type Filter struct {
20 Replacements []types.Replacement `json:"replacements,omitempty" yaml:"replacements,omitempty"`
21 }
22
23
24 func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
25 for i, r := range f.Replacements {
26 if r.Source == nil || r.Targets == nil {
27 return nil, fmt.Errorf("replacements must specify a source and at least one target")
28 }
29 value, err := getReplacement(nodes, &f.Replacements[i])
30 if err != nil {
31 return nil, err
32 }
33 nodes, err = applyReplacement(nodes, value, r.Targets)
34 if err != nil {
35 return nil, err
36 }
37 }
38 return nodes, nil
39 }
40
41 func getReplacement(nodes []*yaml.RNode, r *types.Replacement) (*yaml.RNode, error) {
42 source, err := selectSourceNode(nodes, r.Source)
43 if err != nil {
44 return nil, err
45 }
46
47 if r.Source.FieldPath == "" {
48 r.Source.FieldPath = types.DefaultReplacementFieldPath
49 }
50 fieldPath := kyaml_utils.SmarterPathSplitter(r.Source.FieldPath, ".")
51
52 rn, err := source.Pipe(yaml.Lookup(fieldPath...))
53 if err != nil {
54 return nil, fmt.Errorf("error looking up replacement source: %w", err)
55 }
56 if rn.IsNilOrEmpty() {
57 return nil, fmt.Errorf("fieldPath `%s` is missing for replacement source %s", r.Source.FieldPath, r.Source.ResId)
58 }
59
60 return getRefinedValue(r.Source.Options, rn)
61 }
62
63
64
65 func selectSourceNode(nodes []*yaml.RNode, selector *types.SourceSelector) (*yaml.RNode, error) {
66 var matches []*yaml.RNode
67 for _, n := range nodes {
68 ids, err := utils.MakeResIds(n)
69 if err != nil {
70 return nil, fmt.Errorf("error getting node IDs: %w", err)
71 }
72 for _, id := range ids {
73 if id.IsSelectedBy(selector.ResId) {
74 if len(matches) > 0 {
75 return nil, fmt.Errorf(
76 "multiple matches for selector %s", selector)
77 }
78 matches = append(matches, n)
79 break
80 }
81 }
82 }
83 if len(matches) == 0 {
84 return nil, fmt.Errorf("nothing selected by %s", selector)
85 }
86 return matches[0], nil
87 }
88
89 func getRefinedValue(options *types.FieldOptions, rn *yaml.RNode) (*yaml.RNode, error) {
90 if options == nil || options.Delimiter == "" {
91 return rn, nil
92 }
93 if rn.YNode().Kind != yaml.ScalarNode {
94 return nil, fmt.Errorf("delimiter option can only be used with scalar nodes")
95 }
96 value := strings.Split(yaml.GetValue(rn), options.Delimiter)
97 if options.Index >= len(value) || options.Index < 0 {
98 return nil, fmt.Errorf("options.index %d is out of bounds for value %s", options.Index, yaml.GetValue(rn))
99 }
100 n := rn.Copy()
101 n.YNode().Value = value[options.Index]
102 return n, nil
103 }
104
105 func applyReplacement(nodes []*yaml.RNode, value *yaml.RNode, targetSelectors []*types.TargetSelector) ([]*yaml.RNode, error) {
106 for _, selector := range targetSelectors {
107 if selector.Select == nil {
108 return nil, errors.Errorf("target must specify resources to select")
109 }
110 if len(selector.FieldPaths) == 0 {
111 selector.FieldPaths = []string{types.DefaultReplacementFieldPath}
112 }
113 for _, possibleTarget := range nodes {
114 ids, err := utils.MakeResIds(possibleTarget)
115 if err != nil {
116 return nil, err
117 }
118
119
120 selectByAnnoAndLabel, err := selectByAnnoAndLabel(possibleTarget, selector)
121 if err != nil {
122 return nil, err
123 }
124 if !selectByAnnoAndLabel {
125 continue
126 }
127
128
129 for _, id := range ids {
130 if id.IsSelectedBy(selector.Select.ResId) && !containsRejectId(selector.Reject, ids) {
131 err := copyValueToTarget(possibleTarget, value, selector)
132 if err != nil {
133 return nil, err
134 }
135 break
136 }
137 }
138 }
139 }
140 return nodes, nil
141 }
142
143 func selectByAnnoAndLabel(n *yaml.RNode, t *types.TargetSelector) (bool, error) {
144 if matchesSelect, err := matchesAnnoAndLabelSelector(n, t.Select); !matchesSelect || err != nil {
145 return false, err
146 }
147 for _, reject := range t.Reject {
148 if reject.AnnotationSelector == "" && reject.LabelSelector == "" {
149 continue
150 }
151 if m, err := matchesAnnoAndLabelSelector(n, reject); m || err != nil {
152 return false, err
153 }
154 }
155 return true, nil
156 }
157
158 func matchesAnnoAndLabelSelector(n *yaml.RNode, selector *types.Selector) (bool, error) {
159 r := resource.Resource{RNode: *n}
160 annoMatch, err := r.MatchesAnnotationSelector(selector.AnnotationSelector)
161 if err != nil {
162 return false, err
163 }
164 labelMatch, err := r.MatchesLabelSelector(selector.LabelSelector)
165 if err != nil {
166 return false, err
167 }
168 return annoMatch && labelMatch, nil
169 }
170
171 func containsRejectId(rejects []*types.Selector, ids []resid.ResId) bool {
172 for _, r := range rejects {
173 if r.ResId.IsEmpty() {
174 continue
175 }
176 for _, id := range ids {
177 if id.IsSelectedBy(r.ResId) {
178 return true
179 }
180 }
181 }
182 return false
183 }
184
185 func copyValueToTarget(target *yaml.RNode, value *yaml.RNode, selector *types.TargetSelector) error {
186 for _, fp := range selector.FieldPaths {
187 createKind := yaml.Kind(0)
188 if selector.Options != nil && selector.Options.Create {
189 createKind = value.YNode().Kind
190 }
191 targetFieldList, err := target.Pipe(&yaml.PathMatcher{
192 Path: kyaml_utils.SmarterPathSplitter(fp, "."),
193 Create: createKind})
194 if err != nil {
195 return errors.WrapPrefixf(err, fieldRetrievalError(fp, createKind != 0))
196 }
197 targetFields, err := targetFieldList.Elements()
198 if err != nil {
199 return errors.WrapPrefixf(err, fieldRetrievalError(fp, createKind != 0))
200 }
201 if len(targetFields) == 0 {
202 return errors.Errorf(fieldRetrievalError(fp, createKind != 0))
203 }
204
205 for _, t := range targetFields {
206 if err := setFieldValue(selector.Options, t, value); err != nil {
207 return err
208 }
209 }
210 }
211 return nil
212 }
213
214 func fieldRetrievalError(fieldPath string, isCreate bool) string {
215 if isCreate {
216 return fmt.Sprintf("unable to find or create field %q in replacement target", fieldPath)
217 }
218 return fmt.Sprintf("unable to find field %q in replacement target", fieldPath)
219 }
220
221 func setFieldValue(options *types.FieldOptions, targetField *yaml.RNode, value *yaml.RNode) error {
222 value = value.Copy()
223 if options != nil && options.Delimiter != "" {
224 if targetField.YNode().Kind != yaml.ScalarNode {
225 return fmt.Errorf("delimiter option can only be used with scalar nodes")
226 }
227 tv := strings.Split(targetField.YNode().Value, options.Delimiter)
228 v := yaml.GetValue(value)
229
230 switch {
231 case options.Index < 0:
232 tv = append([]string{v}, tv...)
233 case options.Index >= len(tv):
234 tv = append(tv, v)
235 default:
236 tv[options.Index] = v
237 }
238 value.YNode().Value = strings.Join(tv, options.Delimiter)
239 }
240
241 if targetField.YNode().Kind == yaml.ScalarNode {
242
243 targetField.YNode().Value = value.YNode().Value
244 } else {
245 targetField.SetYNode(value.YNode())
246 }
247
248 return nil
249 }
250
View as plain text