1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package krmtotf
16
17 import (
18 "context"
19 "fmt"
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/k8s"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/label"
25 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
26 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/text"
27 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util"
28
29 tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
30 "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
31 apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
32 "sigs.k8s.io/controller-runtime/pkg/client"
33 )
34
35
36
37 func FetchLiveState(ctx context.Context, resource *Resource, provider *tfschema.Provider, kubeClient client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (*terraform.InstanceState, error) {
38
39 id, err := resource.GetImportID(kubeClient, smLoader)
40 if err != nil {
41 if _, ok := k8s.AsServerGeneratedIDNotFoundError(err); ok {
42
43
44
45
46 state := &terraform.InstanceState{}
47 state = SetBlueprintAttribution(state, resource, provider)
48 return state, nil
49 }
50 return nil, fmt.Errorf("error getting ID for resource: %w", err)
51 }
52 return fetchLiveStateFromId(ctx, id, resource, provider, kubeClient, smLoader)
53
54 }
55
56
57
58
59 func shouldGetImportIDFromSelfForDelete(resource *Resource) bool {
60 return resource.Kind == "KMSCryptoKey"
61 }
62
63 func ShouldResolveParentForDelete(resource *Resource) bool {
64 return !shouldGetImportIDFromSelfForDelete(resource) || hasEmptySelfLink(resource)
65 }
66
67 func hasEmptySelfLink(resource *Resource) bool {
68 id, err := resource.SelfLinkAsID()
69 if err != nil || id == "" {
70 return true
71 }
72 return false
73 }
74
75 func FetchLiveStateForDelete(ctx context.Context, resource *Resource, provider *tfschema.Provider, kubeClient client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (*terraform.InstanceState, error) {
76 if shouldGetImportIDFromSelfForDelete(resource) {
77 id, err := resource.SelfLinkAsID()
78 if err != nil {
79 return nil, err
80 }
81 if id != "" {
82 return fetchLiveStateFromId(ctx, id, resource, provider, kubeClient, smLoader)
83 }
84 }
85 return FetchLiveState(ctx, resource, provider, kubeClient, smLoader)
86 }
87
88 func fetchLiveStateFromId(ctx context.Context, id string, resource *Resource, provider *tfschema.Provider, kubeClient client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (*terraform.InstanceState, error) {
89
90 var state *terraform.InstanceState
91 var err error
92 if resource.ResourceConfig.SkipImport {
93 state = &terraform.InstanceState{ID: id}
94 } else {
95 state, err = ImportState(ctx, id, resource.TFInfo, provider)
96 if err != nil {
97 return nil, err
98 }
99 }
100
101
102
103 state, err = presetFieldsForRead(resource, state, kubeClient, smLoader)
104 if err != nil {
105 return nil, err
106 }
107 state = SetBlueprintAttribution(state, resource, provider)
108 state, diagnostics := resource.TFResource.RefreshWithoutUpgrade(ctx, state, provider.Meta())
109 if err := NewErrorFromDiagnostics(diagnostics); err != nil {
110 return nil, fmt.Errorf("error reading underlying resource: %v", err)
111 }
112
113
114 state = SetBlueprintAttribution(state, resource, provider)
115 return state, nil
116 }
117
118
119
120 func FetchLiveStateForCreateAndUpdate(ctx context.Context, resource *Resource, provider *tfschema.Provider, kubeClient client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (*terraform.InstanceState, error) {
121
122
123
124
125
126
127
128
129
130
131
132
133 if resource.Unreadable() &&
134 resource.ResourceConfig.SkipImport &&
135 !resource.HasServerGeneratedIDField() &&
136 resource.AllTopLevelFieldsAreImmutableOrComputed() {
137 return &terraform.InstanceState{}, nil
138 }
139
140 return FetchLiveState(ctx, resource, provider, kubeClient, smLoader)
141 }
142
143
144
145
146
147
148
149 func ImportState(ctx context.Context, id string, tfInfo *terraform.InstanceInfo, provider *tfschema.Provider) (*terraform.InstanceState, error) {
150 importedResources, err := provider.ImportState(ctx, tfInfo, id)
151 if err != nil {
152 return nil, fmt.Errorf("error importing resource: %v", err)
153 }
154 if len(importedResources) != 1 {
155 return nil, fmt.Errorf("import corresponds to more than one resource")
156 }
157 return importedResources[0], nil
158 }
159
160 func presetFieldsForRead(r *Resource, imported *terraform.InstanceState, kubeClient client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (*terraform.InstanceState, error) {
161 importedMap := InstanceStateToMap(r.TFResource, imported)
162 ret, err := WithFieldsPresetForRead(importedMap, r, kubeClient, smLoader)
163 if err != nil {
164 return nil, err
165 }
166 return MapToInstanceState(r.TFResource, ret), nil
167 }
168
169 func WithFieldsPresetForRead(imported map[string]interface{}, r *Resource, kubeClient client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (map[string]interface{}, error) {
170 var config *terraform.ResourceConfig
171 var secretVersions map[string]string
172 var err error
173
174
175
176 mustResolveSensitiveFields := !k8s.IsDeleted(&r.ObjectMeta)
177 importedAsInstanceState := MapToInstanceState(r.TFResource, imported)
178 var jsonSchema *apiextensions.JSONSchemaProps = nil
179 config, secretVersions, err = KRMResourceToTFResourceConfigFull(
180 r, kubeClient, smLoader, importedAsInstanceState, jsonSchema, mustResolveSensitiveFields, label.GetDefaultLabels(),
181 )
182 if err != nil {
183 return nil, fmt.Errorf("error converting resource config: %w", err)
184 }
185
186 ret := withImmutableFields(imported, ResourceConfigToMap(config), r.TFResource.Schema)
187 ret, err = withMutableButUnreadableFields(ret, r, secretVersions, kubeClient)
188 if err != nil {
189 return nil, fmt.Errorf("error presetting mutable but unreadable fields for read: %v", err)
190 }
191 ret = withDirectives(ret, r)
192 ret, err = withStatusFields(ret, r, kubeClient, smLoader)
193 if err != nil {
194 return nil, fmt.Errorf("error presetting status fields for read: %v", err)
195 }
196 return ret, nil
197 }
198
199 func withImmutableFields(imported, config map[string]interface{}, schemas map[string]*tfschema.Schema) map[string]interface{} {
200 ret := deepcopy.MapStringInterface(imported)
201 if ret == nil {
202 ret = make(map[string]interface{})
203 }
204 for field, schema := range schemas {
205 configVal := config[field]
206 if schema.ForceNew {
207 if configVal == nil {
208
209
210
211 ret[field] = getZeroValueForType(schema.Type)
212 } else {
213 ret[field] = configVal
214 }
215 continue
216 }
217 if configVal == nil {
218 continue
219 }
220
221
222 switch schema.Type {
223 case tfschema.TypeList, tfschema.TypeSet:
224 switch elem := schema.Elem.(type) {
225 case *tfschema.Resource:
226
227
228 configList := configVal.([]interface{})
229 importedList, _ := imported[field].([]interface{})
230 retList := make([]interface{}, 0)
231 for idx, expandedItem := range configList {
232 expandedItem := expandedItem.(map[string]interface{})
233 var importedItem map[string]interface{}
234 if len(importedList) > idx {
235 importedItem = importedList[idx].(map[string]interface{})
236 }
237 retList = append(retList, withImmutableFields(importedItem, expandedItem, elem.Schema))
238 }
239 ret[field] = retList
240 }
241 }
242 }
243 return ret
244 }
245
246 func withMutableButUnreadableFields(imported map[string]interface{}, r *Resource, currSecretVersions map[string]string, kubeClient client.Client) (map[string]interface{}, error) {
247 if len(r.ResourceConfig.MutableButUnreadableFields) == 0 {
248 return imported, nil
249 }
250
251 mutableButUnreadableFields, err := getMutableButUnreadableFieldsFromAnnotations(r)
252 if err != nil {
253 return nil, err
254 }
255 if len(mutableButUnreadableFields) == 0 {
256 return imported, nil
257 }
258
259 secretVersions, err := k8s.GetSecretVersionsFromAnnotations(&r.Resource)
260 if err != nil {
261 return nil, err
262 }
263
264
265
266
267 if secretVersions == nil {
268 secretVersions = currSecretVersions
269 }
270
271 return setMutableButUnreadableFields(imported, mutableButUnreadableFields["spec"].(map[string]interface{}), r.TFResource.Schema, secretVersions, r.GetNamespace(), kubeClient)
272 }
273
274 func setMutableButUnreadableFields(imported, mutableButUnreadableSpec map[string]interface{}, schemas map[string]*tfschema.Schema, secretVersions map[string]string, namespace string, kubeClient client.Client) (map[string]interface{}, error) {
275 ret := deepcopy.MapStringInterface(imported)
276 for k, v := range mutableButUnreadableSpec {
277 tfKey := text.AsSnakeCase(k)
278 schema, ok := schemas[tfKey]
279 if !ok {
280 return nil, fmt.Errorf("could not find a schema for field %v", tfKey)
281 }
282 switch schema.Type {
283 case tfschema.TypeString:
284 if !schema.Sensitive {
285 ret[tfKey] = v
286 continue
287 }
288
289 sensitiveField := corekccv1alpha1.SensitiveField{}
290 if err := util.Marshal(v, &sensitiveField); err != nil {
291 return nil, fmt.Errorf("error parsing %v onto a SensitiveField struct: %v", v, err)
292 }
293
294 if sensitiveField.Value != nil {
295 ret[tfKey] = *sensitiveField.Value
296 continue
297 }
298
299 secretKeyRef := sensitiveField.ValueFrom.SecretKeyRef
300 secretVal, secretVer, err := k8s.GetSecretVal(secretKeyRef, namespace, kubeClient)
301 if err != nil {
302
303
304
305
306
307
308
309 continue
310 }
311
312
313 prevSecretVer, ok := secretVersions[secretKeyRef.Name]
314 if ok && secretVer == prevSecretVer {
315 ret[tfKey] = secretVal
316 }
317 case tfschema.TypeBool, tfschema.TypeFloat, tfschema.TypeInt:
318 ret[tfKey] = v
319 case tfschema.TypeMap:
320 ret[tfKey] = deepcopy.DeepCopy(v)
321 case tfschema.TypeList, tfschema.TypeSet:
322 switch elem := schema.Elem.(type) {
323
324 case *tfschema.Schema:
325 ret[tfKey] = deepcopy.DeepCopy(v)
326
327 case *tfschema.Resource:
328
329 if schema.MaxItems != 1 {
330 panic(fmt.Errorf("error presetting field %v: presetting mutable-but-unreadable fields in objects contained in lists/sets is not yet supported", tfKey))
331 }
332
333 prevObj, ok := v.(map[string]interface{})
334 if !ok {
335 return nil, fmt.Errorf("expected field %v in %v to be a map, but it is not", k, k8s.MutableButUnreadableFieldsAnnotation)
336 }
337 importedObj, err := getObjectAtFieldInState(imported, tfKey)
338 if err != nil {
339 return nil, fmt.Errorf("error getting object at field %v from state map: %v", tfKey, err)
340 }
341 obj, err := setMutableButUnreadableFields(importedObj, prevObj, elem.Schema, secretVersions, namespace, kubeClient)
342 if err != nil {
343 return nil, err
344 }
345 if len(obj) == 0 {
346 continue
347 }
348 ret[tfKey] = []interface{}{obj}
349 }
350 }
351 }
352 return ret, nil
353 }
354
355
356 func getObjectAtFieldInState(state map[string]interface{}, tfKey string) (map[string]interface{}, error) {
357 v, ok := getNestedFieldFromState(state, tfKey)
358 if !ok {
359 return make(map[string]interface{}), nil
360 }
361 obj, ok := v.(map[string]interface{})
362 if !ok {
363 return nil, fmt.Errorf("expected field %v to be a nested object, but it is not", tfKey)
364 }
365 return obj, nil
366 }
367
368 func withDirectives(imported map[string]interface{}, r *Resource) map[string]interface{} {
369 ret := deepcopy.MapStringInterface(imported)
370 for _, d := range r.ResourceConfig.Directives {
371 key := k8s.FormatAnnotation(text.SnakeCaseToKebabCase(d))
372 if v, ok := k8s.GetAnnotation(key, r); ok {
373 ret[d] = v
374 } else {
375 if r.TFResource.Schema[d].Default != nil {
376 ret[d] = r.TFResource.Schema[d].Default
377 }
378 }
379 }
380 return ret
381 }
382
383 func withStatusFields(imported map[string]interface{}, r *Resource, kubeClient client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (map[string]interface{}, error) {
384 ret := deepcopy.MapStringInterface(imported)
385 tfStatus, err := KRMObjectToTFObject(r.Status, r.TFResource)
386 if err != nil {
387 return nil, fmt.Errorf("error converting status object: %v", err)
388 }
389 for k, v := range tfStatus {
390 ret[k] = v
391 }
392
393 if SupportsResourceIDField(&r.ResourceConfig) && IsResourceIDFieldServerGenerated(&r.ResourceConfig) {
394 idInStatus, err := r.ConstructServerGeneratedIDInStatusFromResourceID(kubeClient, smLoader)
395 if err != nil {
396 return nil, fmt.Errorf("error syncing the server-generated ID: %v", err)
397 }
398 if idInStatus != "" {
399 ret[r.ResourceConfig.ServerGeneratedIDField] = idInStatus
400 }
401 }
402
403 return ret, nil
404 }
405
406 func getZeroValueForType(valueType tfschema.ValueType) interface{} {
407 switch valueType {
408 case tfschema.TypeBool:
409 return false
410 case tfschema.TypeFloat, tfschema.TypeInt:
411 return float64(0)
412 case tfschema.TypeString:
413 return ""
414 case tfschema.TypeList, tfschema.TypeMap, tfschema.TypeSet:
415 return nil
416 default:
417 panic(fmt.Sprintf("unknown value type %v", valueType))
418 }
419 }
420
View as plain text