1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package krmtotf
16
17 import (
18 "fmt"
19 "strings"
20
21 corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
22 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/deepcopy"
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/gcp"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
25 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/label"
26 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
27 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/text"
28 tfresource "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/tf/resource"
29 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util"
30
31 tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
32 "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
33 apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
34 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
35 "sigs.k8s.io/controller-runtime/pkg/client"
36 )
37
38
39
40
41
42
43
44
45
46 func KRMResourceToTFResourceConfig(r *Resource, c client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (tfConfig *terraform.ResourceConfig, secretVersions map[string]string, err error) {
47 return KRMResourceToTFResourceConfigFull(r, c, smLoader, nil, nil, true, label.GetDefaultLabels())
48 }
49
50
51
52
53
54
55
56
57 func KRMResourceToTFResourceConfigFull(r *Resource, c client.Client, smLoader *servicemappingloader.ServiceMappingLoader,
58 liveState *terraform.InstanceState, jsonSchema *apiextensions.JSONSchemaProps, mustResolveSensitiveFields bool, defaultLabels map[string]string) (tfConfig *terraform.ResourceConfig, secretVersions map[string]string, err error) {
59 config := deepcopy.MapStringInterface(r.Spec)
60 if config == nil {
61 config = make(map[string]interface{})
62 }
63 if jsonSchema != nil {
64 if err := ResolveLegacyGCPManagedFields(r, liveState, config); err != nil {
65 return nil, nil, fmt.Errorf("error resolving legacy GCP-managed fields: %w", err)
66 }
67 config, err = resolveUnmanagedFields(config, r, liveState, jsonSchema)
68 if err != nil {
69 return nil, nil, fmt.Errorf("error resolving externally-managed fields: %w", err)
70 }
71 }
72 if err := handleUserSpecifiedID(config, r, smLoader, c); err != nil {
73 return nil, nil, err
74 }
75 if r.ResourceConfig.MetadataMapping.Labels != "" {
76 path := text.SnakeCaseToLowerCamelCase(r.ResourceConfig.MetadataMapping.Labels)
77 labels := label.NewGCPLabelsFromK8SLabels(r.GetLabels(), defaultLabels)
78 if err := setValue(config, path, labels); err != nil {
79 return nil, nil, fmt.Errorf("error mapping 'metadata.labels': %v", err)
80 }
81 }
82 if r.ResourceConfig.Locationality != "" {
83 switch r.ResourceConfig.Locationality {
84 case gcp.Global:
85 delete(config, "location")
86 case gcp.Regional:
87 config["region"] = config["location"]
88 delete(config, "location")
89 case gcp.Zonal:
90 config["zone"] = config["location"]
91 delete(config, "location")
92 default:
93 return nil, nil, fmt.Errorf("INTERNAL_ERROR: %v locationality is not supported", r.ResourceConfig.Locationality)
94 }
95 }
96 for _, refConfig := range r.ResourceConfig.ResourceReferences {
97 if err := handleResourceReference(config, refConfig, r, c, smLoader); err != nil {
98 return nil, nil, err
99 }
100 }
101 config, secretVersions, err = resolveSensitiveFields(config, r.TFResource, r.GetNamespace(), c, mustResolveSensitiveFields)
102 if err != nil {
103 return nil, nil, err
104 }
105 config, err = KRMObjectToTFObjectWithConfigurableFieldsOnly(config, r.TFResource)
106 if err != nil {
107 return nil, nil, fmt.Errorf("error converting to config: %v", err)
108 }
109 for _, d := range r.ResourceConfig.Directives {
110 key := k8s.FormatAnnotation(text.SnakeCaseToKebabCase(d))
111 if val, ok := k8s.GetAnnotation(key, r); ok {
112 if val == "" {
113 return nil, nil, fmt.Errorf("the value for directive '%v' must not be empty", key)
114 }
115 if err := setValue(config, d, val); err != nil {
116 return nil, nil, fmt.Errorf("error mapping directive '%v': %v", d, err)
117 }
118 }
119 }
120 if err := resolveContainerValue(config, r, c, smLoader); err != nil {
121 return nil, nil, fmt.Errorf("error resolving container value: %v", err)
122 }
123 config, err = withCustomFlatteners(config, r.Kind)
124 if err != nil {
125 return nil, nil, fmt.Errorf("error running custom flatteners: %w", err)
126 }
127 state := InstanceStateToMap(r.TFResource, liveState)
128 config, err = withResourceCustomResolvers(config, state, r.Kind, r.TFResource)
129 if err != nil {
130 return nil, nil, fmt.Errorf("error running resource custom resolver: %w", err)
131 }
132 return MapToResourceConfig(r.TFResource, config), secretVersions, nil
133 }
134
135 func KRMObjectToTFObject(obj map[string]interface{}, resource *tfschema.Resource) (map[string]interface{}, error) {
136 return krmObjectToTFObject(obj, resource, false)
137 }
138
139 func KRMObjectToTFObjectWithConfigurableFieldsOnly(obj map[string]interface{}, resource *tfschema.Resource) (map[string]interface{}, error) {
140 return krmObjectToTFObject(obj, resource, true)
141 }
142
143 func krmObjectToTFObject(obj map[string]interface{}, resource *tfschema.Resource, includeConfigurableFieldsOnly bool) (map[string]interface{}, error) {
144 var err error
145 if obj == nil {
146 return nil, nil
147 }
148 ret := make(map[string]interface{})
149 for k, v := range obj {
150 tfKey := text.AsSnakeCase(k)
151 schema, ok := resource.Schema[tfKey]
152 if !ok {
153
154
155 continue
156 }
157 if includeConfigurableFieldsOnly && !tfresource.IsConfigurableField(schema) {
158 continue
159 }
160 ret[tfKey], err = convertToTF(v, schema, includeConfigurableFieldsOnly)
161 if err != nil {
162 return nil, fmt.Errorf("error converting '%v': %v", k, err)
163 }
164 }
165 return ret, nil
166 }
167
168 func convertToTF(obj interface{}, schema *tfschema.Schema, includeConfigurableFieldsOnly bool) (interface{}, error) {
169 switch schema.Type {
170 case tfschema.TypeBool, tfschema.TypeFloat, tfschema.TypeString, tfschema.TypeInt:
171
172 return obj, nil
173 case tfschema.TypeMap:
174
175 return deepcopy.DeepCopy(obj), nil
176 case tfschema.TypeList, tfschema.TypeSet:
177 items, err := toList(obj, schema)
178 if err != nil {
179 return nil, err
180 }
181 retList := make([]interface{}, 0)
182 for _, item := range items {
183 var processedItem interface{}
184 switch elem := schema.Elem.(type) {
185 case *tfschema.Schema:
186 processedItem, err = convertToTF(item, elem, includeConfigurableFieldsOnly)
187 if err != nil {
188 return nil, fmt.Errorf("error converting list item: %v", err)
189 }
190 case *tfschema.Resource:
191 itemAsMap, ok := item.(map[string]interface{})
192 if !ok {
193 return nil, fmt.Errorf("expected list item to be map but was not")
194 }
195 processedItem, err = krmObjectToTFObject(itemAsMap, elem, includeConfigurableFieldsOnly)
196 if err != nil {
197 return nil, fmt.Errorf("error converting map list item: %v", err)
198 }
199 default:
200 return nil, fmt.Errorf("unknown elem type")
201 }
202 retList = append(retList, processedItem)
203 }
204 return retList, nil
205 case tfschema.TypeInvalid:
206 return nil, fmt.Errorf("schema type is invalid")
207 default:
208 return nil, fmt.Errorf("unrecognized schema type %v", schema.Type)
209 }
210 }
211
212
213
214
215
216
217
218 func handleUserSpecifiedID(config map[string]interface{}, r *Resource, smLoader *servicemappingloader.ServiceMappingLoader, c client.Client) error {
219 if SupportsResourceIDField(&r.ResourceConfig) && !IsResourceIDFieldServerGenerated(&r.ResourceConfig) && r.HasResourceIDField() {
220 path := text.SnakeCaseToLowerCamelCase(r.ResourceConfig.ResourceID.TargetField)
221 resourceID, err := resolveResourceID(r, c, smLoader)
222 if err != nil {
223 return fmt.Errorf("error resolving resource ID: %v", err)
224 }
225 if err := setValue(config, path, resourceID); err != nil {
226 return fmt.Errorf("error mapping user-specified %v: %v", k8s.ResourceIDFieldPath, err)
227 }
228 } else if r.ResourceConfig.MetadataMapping.Name != "" && r.GetName() != "" {
229 path := text.SnakeCaseToLowerCamelCase(r.ResourceConfig.MetadataMapping.Name)
230 name, err := resolveNameMetadataMapping(r, c, smLoader)
231 if err != nil {
232 return fmt.Errorf("error resolving metadata.name mapping: %v", err)
233 }
234 if err := setValue(config, path, name); err != nil {
235 return fmt.Errorf("error mapping metadata.name: %v", err)
236 }
237 }
238 return nil
239 }
240
241 func resolveSensitiveFields(config map[string]interface{}, resource *tfschema.Resource, namespace string, c client.Client, mustResolveSensitiveFields bool) (resolvedConfig map[string]interface{}, secretVersions map[string]string, err error) {
242 resolvedConfig = deepcopy.MapStringInterface(config)
243 secretVersions = make(map[string]string)
244 for k, v := range config {
245 tfKey := text.AsSnakeCase(k)
246 schema, ok := resource.Schema[tfKey]
247 if !ok {
248 continue
249 }
250 switch schema.Type {
251 case tfschema.TypeString:
252 if !tfresource.IsSensitiveConfigurableField(schema) {
253 continue
254 }
255
256 field := corekccv1alpha1.SensitiveField{}
257 if err := util.Marshal(v, &field); err != nil {
258 return nil, nil, fmt.Errorf("error parsing %v onto a SensitiveField struct: %v", v, err)
259 }
260
261 if field.Value != nil {
262 resolvedConfig[k] = *field.Value
263 continue
264 }
265
266 secretKeyRef := field.ValueFrom.SecretKeyRef
267 secretVal, secretVer, err := k8s.GetSecretVal(secretKeyRef, namespace, c)
268 if err != nil {
269 if mustResolveSensitiveFields {
270 return nil, nil, err
271 }
272 delete(resolvedConfig, k)
273 continue
274 }
275 resolvedConfig[k] = secretVal
276 secretVersions[secretKeyRef.Name] = secretVer
277 default:
278 resolvedObj, secretVers, err := resolveSensitiveFieldsInObj(v, schema, namespace, c, mustResolveSensitiveFields)
279 if err != nil {
280 return nil, nil, err
281 }
282 resolvedConfig[k] = resolvedObj
283 secretVersions = addToMap(secretVersions, secretVers)
284 }
285 }
286 return resolvedConfig, secretVersions, nil
287 }
288
289 func resolveSensitiveFieldsInObj(obj interface{}, schema *tfschema.Schema, namespace string, c client.Client, mustResolveSensitiveFields bool) (resolvedObj interface{}, secretVersions map[string]string, err error) {
290 secretVersions = make(map[string]string)
291 switch schema.Type {
292 case tfschema.TypeList, tfschema.TypeSet:
293 items, err := toList(obj, schema)
294 if err != nil {
295 return nil, nil, err
296 }
297 resolvedItems := make([]interface{}, 0)
298 for _, item := range items {
299 var resolvedItem interface{}
300 var secretVers map[string]string
301 var err error
302
303 switch elem := schema.Elem.(type) {
304 case *tfschema.Schema:
305 resolvedItem, secretVers, err = resolveSensitiveFieldsInObj(item, elem, namespace, c, mustResolveSensitiveFields)
306 if err != nil {
307 return nil, nil, err
308 }
309 case *tfschema.Resource:
310 itemAsMap, ok := item.(map[string]interface{})
311 if !ok {
312 return nil, nil, fmt.Errorf("expected list item to be map but was not")
313 }
314 resolvedItem, secretVers, err = resolveSensitiveFields(itemAsMap, elem, namespace, c, mustResolveSensitiveFields)
315 if err != nil {
316 return nil, nil, err
317 }
318 }
319
320 resolvedItems = append(resolvedItems, resolvedItem)
321 secretVersions = addToMap(secretVersions, secretVers)
322 }
323 return resolvedItems, secretVersions, nil
324 default:
325 return obj, secretVersions, nil
326 }
327 }
328
329 func toList(obj interface{}, schema *tfschema.Schema) ([]interface{}, error) {
330 if obj == nil {
331 return nil, nil
332 }
333 switch obj := obj.(type) {
334 case []interface{}:
335 return obj, nil
336 case map[string]interface{}:
337
338
339
340 if schema.MaxItems == 1 {
341 return []interface{}{obj}, nil
342 }
343 return nil, fmt.Errorf("cannot interpret map as list without maxItems == 1")
344 default:
345 return nil, fmt.Errorf("cannot interpret non-list %T as list", obj)
346 }
347 }
348
349 func setValue(m map[string]interface{}, path string, value interface{}) error {
350 return unstructured.SetNestedField(m, value, strings.Split(path, ".")...)
351 }
352
353
354
355
356 func addToMap(left map[string]string, right map[string]string) map[string]string {
357 left = deepcopy.StringStringMap(left)
358 for k, v := range right {
359 left[k] = v
360 }
361 return left
362 }
363
364 func resolveContainerValue(config map[string]interface{}, r *Resource, c client.Client, smLoader *servicemappingloader.ServiceMappingLoader) error {
365 if len(r.ResourceConfig.Containers) == 0 {
366 return nil
367 }
368 if SupportsHierarchicalReferences(&r.ResourceConfig) {
369
370
371
372
373 return nil
374 }
375 for _, container := range r.ResourceConfig.Containers {
376 val, ok := k8s.GetAnnotation(k8s.GetAnnotationForContainerType(container.Type), r)
377 if !ok {
378 continue
379 }
380 val, err := ResolveValueTemplate(container.ValueTemplate, val, r, c, smLoader)
381 if err != nil {
382 return fmt.Errorf("error resolving templated value: %v", err)
383 }
384 if err := setValue(config, container.TFField, val); err != nil {
385 return fmt.Errorf("error setting container value: %v", err)
386 }
387 return nil
388 }
389 return fmt.Errorf("no annotation found that matches one of the required containers")
390 }
391
392 func resolveNameMetadataMapping(r *Resource, c client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (string, error) {
393 name := r.GetName()
394 if name == "" {
395 return "", fmt.Errorf("invalid empty value for name")
396 }
397 return ResolveValueTemplate(r.ResourceConfig.MetadataMapping.NameValueTemplate, name, r, c, smLoader)
398 }
399
400 func resolveResourceID(r *Resource, c client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (string, error) {
401 resourceID, err := r.GetResourceID()
402 if err != nil {
403 return "", err
404 }
405
406 return ResolveValueTemplate(r.ResourceConfig.ResourceID.ValueTemplate, resourceID, r, c, smLoader)
407 }
408
View as plain text