1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package krmtotf
16
17 import (
18 "fmt"
19 "regexp"
20 "strings"
21
22 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/gcp"
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
24
25 corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
26 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
27 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/text"
28
29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30 "sigs.k8s.io/controller-runtime/pkg/client"
31 )
32
33 var fieldRegex = regexp.MustCompile("{{([0-9A-Za-z_.?]+)}}")
34
35 func ResolveValueTemplate(template string, val string, r *Resource, c client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (string, error) {
36 if template == "" {
37
38 return val, nil
39 }
40 ret := strings.ReplaceAll(template, "{{value}}", val)
41
42
43 if fieldRegex.MatchString(ret) {
44 return expandTemplate(ret, r, c, smLoader)
45 }
46 return ret, nil
47 }
48
49 func resolveValueTemplateFromInterface(template string, val interface{}, r *Resource, c client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (interface{}, error) {
50 if template == "" {
51
52 return val, nil
53 }
54 switch valAsType := val.(type) {
55 case string:
56 return ResolveValueTemplate(template, valAsType, r, c, smLoader)
57 default:
58 return nil, fmt.Errorf("cannot resolve value template for non-string type")
59 }
60 }
61
62
63
64
65 func valueMatchesTemplate(template string, value string) bool {
66 if template == "" {
67 return true
68 }
69 template = strings.ReplaceAll(template, "{{value}}", ".*")
70 r := regexp.MustCompile(template)
71 return r.MatchString(value)
72 }
73
74 func expandTemplate(template string, r *Resource, c client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (string, error) {
75 leftBracketIdx := strings.Index(template, "[")
76 if leftBracketIdx == -1 {
77 if isORTemplate(template) {
78 return expandOrTemplate(template, r, c, smLoader)
79 }
80 return expandFieldTemplate(template, r, c, smLoader)
81 }
82 rightBracketIdx := strings.LastIndex(template, "]")
83 if rightBracketIdx == -1 {
84 return "", fmt.Errorf("template '%v' has a left bracket but is missing corresponding right bracket", template)
85 }
86 subTemplate := template[leftBracketIdx+1 : rightBracketIdx]
87 result, err := expandTemplate(subTemplate, r, c, smLoader)
88 if err != nil {
89 return "", fmt.Errorf("error resolving sub-template: %w", err)
90 }
91 resolvedTemplate := fmt.Sprintf("%v%v%v", template[:leftBracketIdx], result, template[rightBracketIdx+1:])
92 return expandTemplate(resolvedTemplate, r, c, smLoader)
93 }
94
95 func isORTemplate(template string) bool {
96 return strings.Contains(template, "|")
97 }
98
99
100
101
102 func expandFieldTemplate(template string, r *Resource, c client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (string, error) {
103 var resolutionError error
104 resolveFunc := func(s string) string {
105 field := fieldRegex.FindStringSubmatch(s)[1]
106 isRequired := true
107 if strings.HasSuffix(field, "?") {
108 isRequired = false
109 field = strings.TrimSuffix(field, "?")
110 }
111 if isRef, refConfig := IsReferenceField(field, &r.ResourceConfig); isRef {
112 val, found, err := getValueFromReference(refConfig, r, c, smLoader)
113 if err != nil {
114 resolutionError = fmt.Errorf("error getting value from reference: %w", err)
115 return ""
116 }
117 if !found {
118 if isRequired {
119 resolutionError = fmt.Errorf("required reference '%v' could not be found in spec", field)
120 }
121 return ""
122 }
123 return val
124 }
125 if field == r.ResourceConfig.ResourceID.TargetField {
126 val, err := resolveResourceID(r, c, smLoader)
127 if err != nil {
128 resolutionError = fmt.Errorf("error resolving resource ID: %w", err)
129 return ""
130 }
131 return val
132 }
133 if field == r.ResourceConfig.MetadataMapping.Name {
134 val, err := resolveNameMetadataMapping(r, c, smLoader)
135 if err != nil {
136 resolutionError = fmt.Errorf("error resolving metadata name mapping value: %w", err)
137 return ""
138 }
139 return val
140 }
141
142 if field == r.ResourceConfig.MetadataMapping.Labels {
143 resolutionError = fmt.Errorf("cannot map labels (map[string]string) to string field '%v'", field)
144 return ""
145 }
146 if field == "region" && r.ResourceConfig.Locationality == gcp.Regional ||
147 field == "zone" && r.ResourceConfig.Locationality == gcp.Zonal {
148 if val, exists, _ := unstructured.NestedString(r.Spec, "location"); exists {
149 return val
150 }
151 }
152 if !SupportsHierarchicalReferences(&r.ResourceConfig) {
153
154
155 for _, c := range r.ResourceConfig.Containers {
156 if field == c.TFField {
157 annotation := k8s.GetAnnotationForContainerType(c.Type)
158 val, ok := k8s.GetAnnotation(annotation, r)
159 if (!ok || val == "") && isRequired {
160 resolutionError = fmt.Errorf("no value found for annotation %v", annotation)
161 return ""
162 }
163 return val
164 }
165 }
166 }
167 for _, d := range r.ResourceConfig.Directives {
168 if field == d {
169 annotation := k8s.FormatAnnotation(text.SnakeCaseToKebabCase(d))
170 val, ok := k8s.GetAnnotation(annotation, r)
171 if (!ok || val == "") && isRequired {
172 resolutionError = fmt.Errorf("no value found for annotation %v", annotation)
173 return ""
174 }
175 return val
176 }
177 }
178 path := text.SnakeCaseToLowerCamelCase(field)
179 if val, exists, _ := unstructured.NestedString(r.Spec, strings.Split(path, ".")...); exists {
180 return val
181 }
182 if val, exists, _ := unstructured.NestedString(r.Status, strings.Split(path, ".")...); exists {
183 return val
184 }
185 if isRequired {
186 resolutionError = fmt.Errorf("unable to resolve missing value: %v", field)
187 }
188 return ""
189 }
190 return fieldRegex.ReplaceAllStringFunc(template, resolveFunc), resolutionError
191 }
192
193 func expandOrTemplate(template string, r *Resource, c client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (string, error) {
194 templates := strings.Split(template, "|")
195 expectedCount := 2
196 if len(templates) != expectedCount {
197 return "", fmt.Errorf("unexpected template format: after splitting on '|' there are '%v' templates when %v were expected",
198 len(templates), expectedCount)
199 }
200 t1 := templates[0]
201 t2 := templates[1]
202 result, err := expandTemplate(t1, r, c, smLoader)
203 if err == nil {
204 return result, nil
205 }
206 result, err = expandTemplate(t2, r, c, smLoader)
207 if err != nil {
208 return "", fmt.Errorf("error resolving both sides of an '|' template: %w", err)
209 }
210 return result, nil
211 }
212
213 func getValueFromReference(refConfig *corekccv1alpha1.ReferenceConfig, r *Resource, c client.Client,
214 smLoader *servicemappingloader.ServiceMappingLoader) (val string, found bool, err error) {
215 pathToRef := getPathToReferenceKey(refConfig)
216 refObj, ok, err := unstructured.NestedMap(r.Spec, pathToRef...)
217 if err != nil {
218 return "", false, fmt.Errorf("error getting reference object '%v': %v", strings.Join(pathToRef, "."), err)
219 }
220 if !ok {
221 return "", false, nil
222 }
223 retRaw, err := ResolveReferenceObject(refObj, *refConfig, r, c, smLoader)
224 if err != nil {
225 return "", false, fmt.Errorf("error resolving reference field: %w", err)
226 }
227 ret, ok := retRaw.(string)
228 if !ok {
229 return "", false, fmt.Errorf("could not parse reference resolution value '%+v' as string", retRaw)
230 }
231 return ret, true, nil
232 }
233
234 func extractValueSegmentFromIDInStatus(idInStatus, template string) (string, error) {
235 if template == "" {
236 return idInStatus, nil
237 }
238
239
240 template = strings.ReplaceAll(template, "{{value}}", "([^/]+)")
241
242
243
244
245
246
247
248 if strings.HasPrefix(template, "{{") {
249 re := regexp.MustCompile(`^({{[a-z]([a-z_]*[a-z])*}})/.*$`)
250 matched := re.FindStringSubmatch(template)
251 if len(matched) == 0 {
252 return "", fmt.Errorf("error extracting the parent field name from resource ID template %v", template)
253 }
254 parentField := matched[1]
255 template = strings.ReplaceAll(template, parentField, "[^/](.*[^/])*")
256 }
257
258 template = fieldRegex.ReplaceAllString(template, "[^/]+")
259 template = fmt.Sprintf("%s%s%s", "^", template, "$")
260
261
262 templateRegex := regexp.MustCompile(template)
263 subMatches := templateRegex.FindStringSubmatch(idInStatus)
264 if len(subMatches) < 2 {
265 return "", fmt.Errorf("error extracting out the value segment "+
266 "from the idInStatus template '%s' using template '%s'", idInStatus,
267 template)
268 }
269
270 return subMatches[len(subMatches)-1], nil
271 }
272
View as plain text