1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package krmtotf
16
17 import (
18 "encoding/json"
19 "fmt"
20
21 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
22 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/text"
23
24 "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
25 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
26 )
27
28 func ResolveLegacyGCPManagedFields(r *Resource, liveState *terraform.InstanceState, config map[string]interface{}) error {
29
30
31
32 if liveState.Empty() {
33
34
35 return nil
36 }
37 switch r.GroupVersionKind().Kind {
38 case "SQLInstance":
39 return resolveSQLInstanceDiskSize(r, config)
40 case "ContainerCluster":
41 if err := resolveContainerClusterNodeVersion(r, config); err != nil {
42 return err
43 }
44 if err := resolveContainerClusterNodeConfig(r, liveState, config); err != nil {
45 return err
46 }
47 return nil
48 case "ContainerNodePool":
49 if err := resolveContainerNodePoolVersion(r, config); err != nil {
50 return err
51 }
52 if err := resolveContainerNodePoolInitialNodeCount(r, config); err != nil {
53 return err
54 }
55 if err := resolveContainerNodePoolNodeCount(r, config); err != nil {
56 return err
57 }
58 return nil
59 case "ComputeBackendService":
60 return resolveComputeBackendServiceBackend(r, config)
61 case "BigtableInstance":
62 return resolveBigtableInstanceNumNodes(r, config)
63 default:
64 return nil
65 }
66 }
67
68 func isGCPManagedField(kind, field string) bool {
69
70
71
72 switch kind {
73 case "SQLInstance":
74 return field == "settings.disk_size"
75 case "ContainerCluster":
76 return field == "node_version"
77 case "ContainerNodePool":
78 switch field {
79 case "version", "node_count", "initial_node_count":
80 return true
81 default:
82 return false
83 }
84 }
85 return false
86 }
87
88 func resolveSQLInstanceDiskSize(r *Resource, config map[string]interface{}) error {
89
90
91
92
93 autoresizeEnabled, found, err := unstructured.NestedBool(config, "settings", "diskAutoresize")
94 if err != nil {
95 return fmt.Errorf("error determining if disk autoresize is set: %w", err)
96 }
97 if !found || !autoresizeEnabled {
98
99 return nil
100 }
101 if err := removeFromConfigIfNotApplied(r, config, "settings", "diskSize"); err != nil {
102 return fmt.Errorf("error resolving disk size in config: %w", err)
103 }
104 return nil
105 }
106
107 func resolveContainerClusterNodeVersion(r *Resource, config map[string]interface{}) error {
108
109
110 releaseChannel, found, err := unstructured.NestedMap(config, "releaseChannel")
111 if err != nil {
112 return fmt.Errorf("error determining if release channel is set: %w", err)
113 }
114 if !found || releaseChannel == nil {
115
116 return nil
117 }
118 if err := removeFromConfigIfNotApplied(r, config, "nodeVersion"); err != nil {
119 return fmt.Errorf("error resolving node version in config: %w", err)
120 }
121 return nil
122 }
123
124 func resolveContainerNodePoolVersion(r *Resource, config map[string]interface{}) error {
125 autoUpgrade, found, err := unstructured.NestedBool(config, "management", "autoUpgrade")
126 if err != nil {
127 return fmt.Errorf("error determining if autoupgrade is set: %w", err)
128 }
129 if !found || !autoUpgrade {
130
131 return nil
132 }
133 field := "version"
134 if err := removeFromConfigIfNotApplied(r, config, field); err != nil {
135 return fmt.Errorf("error resolving field '%v' in config: %w", field, err)
136 }
137 return nil
138 }
139
140
141
142
143
144
145
146
147
148
149 func resolveContainerClusterNodeConfig(r *Resource, liveState *terraform.InstanceState, config map[string]interface{}) error {
150 removeDefaultNodePoolDirective := "remove-default-node-pool"
151 nodeConfigFieldInTFState := "node_config"
152 nodeConfigFieldInKRMConfig := text.SnakeCaseToLowerCamelCase(nodeConfigFieldInTFState)
153
154 key := k8s.FormatAnnotation(removeDefaultNodePoolDirective)
155 val, ok := k8s.GetAnnotation(key, r)
156 if !ok || val != "true" {
157 return nil
158 }
159
160 liveStateMap := InstanceStateToMap(r.TFResource, liveState)
161 exists, err := topLevelObjectFieldExistsInStateMap(liveStateMap, nodeConfigFieldInTFState)
162 if err != nil {
163 return fmt.Errorf("error resolving field '%v' in 'ContainerCluster': %w", nodeConfigFieldInKRMConfig, err)
164 }
165 if exists {
166 return nil
167 }
168
169 if err := removeFromConfigIfNotApplied(r, config, nodeConfigFieldInKRMConfig); err != nil {
170 return fmt.Errorf("error removing field '%v' in config: %w", nodeConfigFieldInKRMConfig, err)
171 }
172 return nil
173 }
174
175 func topLevelObjectFieldExistsInStateMap(state map[string]interface{}, field string) (bool, error) {
176 value, ok := state[field]
177 if !ok {
178 return false, nil
179 }
180 listVal, ok := value.([]interface{})
181 if !ok {
182 return false, fmt.Errorf("field '%v' is not an object field", field)
183 }
184
185 if len(listVal) == 0 {
186 return false, nil
187 }
188
189 return listVal[0] != nil, nil
190 }
191
192 func resolveContainerNodePoolInitialNodeCount(r *Resource, config map[string]interface{}) error {
193
194
195
196 if err := removeFromConfigIfNotApplied(r, config, "initialNodeCount"); err != nil {
197 return fmt.Errorf("error resolving initialNodeCount in config: %w", err)
198 }
199 return nil
200 }
201
202 func resolveContainerNodePoolNodeCount(r *Resource, config map[string]interface{}) error {
203
204
205 if val := config["autoscaling"]; val == nil {
206
207 return nil
208 }
209
210 if err := removeFromConfigIfNotApplied(r, config, "nodeCount"); err != nil {
211 return fmt.Errorf("error resolving nodeCount in config: %w", err)
212 }
213 return nil
214 }
215
216 func resolveComputeBackendServiceBackend(r *Resource, config map[string]interface{}) error {
217
218
219 if err := removeFromConfigIfNotApplied(r, config, "backend"); err != nil {
220 return fmt.Errorf("error resolving backend in config: %w", err)
221 }
222 return nil
223 }
224
225 func resolveBigtableInstanceNumNodes(r *Resource, config map[string]interface{}) error {
226
227
228 applied, found, err := getLastAppliedValue(r, "cluster")
229 if err != nil {
230 return fmt.Errorf("error determining last applied clusters: %w", err)
231 }
232 if !found {
233 return nil
234 }
235 appliedClusters, ok := applied.([]interface{})
236 if !ok {
237 return fmt.Errorf("cannot decode last applied clusters")
238 }
239 for _, c := range appliedClusters {
240 c, ok := c.(map[string]interface{})
241 if !ok {
242 return fmt.Errorf("cannot decode cluster")
243 }
244 clusterId, found, err := unstructured.NestedString(c, "clusterId")
245 if err != nil {
246 return fmt.Errorf("error determining clusterId: %w", err)
247 } else if !found {
248 return fmt.Errorf("cannot determine clusterId")
249 }
250 _, found, err = unstructured.NestedFloat64(c, "numNodes")
251 if err != nil {
252 return fmt.Errorf("error determining numNodes: %w", err)
253 }
254 if !found {
255
256 if err = removeNumNodesFromBigtableCluster(config, clusterId); err != nil {
257 return fmt.Errorf("error removing numNodes: %w", err)
258 }
259 }
260 }
261 return nil
262 }
263
264
265
266 func removeNumNodesFromBigtableCluster(config map[string]interface{}, cluster string) error {
267
268 clusters, found, err := unstructured.NestedSlice(config, "cluster")
269 if err != nil {
270 return fmt.Errorf("error finding clusters in config: %w", err)
271 }
272 if !found {
273 return nil
274 }
275 for _, c := range clusters {
276 c, ok := c.(map[string]interface{})
277 if !ok {
278 return fmt.Errorf("cannot decode cluster")
279 }
280 id, found, _ := unstructured.NestedString(c, "clusterId")
281 if !found {
282 return fmt.Errorf("cannot determine cluster id")
283 }
284 if id == cluster {
285 unstructured.RemoveNestedField(c, "numNodes")
286 }
287 }
288 if err := unstructured.SetNestedSlice(config, clusters, "cluster"); err != nil {
289 return fmt.Errorf("error setting cluster list: %w", err)
290 }
291 return nil
292 }
293
294 func getLastAppliedValue(r *Resource, path ...string) (val interface{}, found bool, err error) {
295
296
297
298
299 lastAppliedConfigRaw, ok := k8s.GetAnnotation(k8s.LastAppliedConfigurationAnnotation, r)
300 if !ok {
301 return nil, false, nil
302 }
303 lastAppliedConfig := make(map[string]interface{})
304 if err := json.Unmarshal([]byte(lastAppliedConfigRaw), &lastAppliedConfig); err != nil {
305 return nil, false, fmt.Errorf("error unmarshaling last applied configuration: %w", err)
306 }
307 specPath := append([]string{"spec"}, path...)
308 return unstructured.NestedFieldCopy(lastAppliedConfig, specPath...)
309 }
310
311
312
313 func removeFromConfigIfNotApplied(r *Resource, config map[string]interface{}, path ...string) error {
314
315
316 _, found, err := getLastAppliedValue(r, path...)
317 if err != nil {
318 return fmt.Errorf("error finding last applied value for disk size: %w", err)
319 }
320 if !found {
321
322
323
324 unstructured.RemoveNestedField(config, path...)
325 }
326 return nil
327 }
328
View as plain text