1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package krmtotf
16
17 import (
18 "fmt"
19 "reflect"
20 "strings"
21
22 corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
25 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/text"
26
27 tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
28 "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30 "k8s.io/apimachinery/pkg/runtime/schema"
31 "sigs.k8s.io/controller-runtime/pkg/client"
32 )
33
34
35
36
37 type Resource struct {
38 k8s.Resource `json:",inline"`
39
40 Original *k8s.Resource `json:"-"`
41
42
43 TFInfo *terraform.InstanceInfo `json:"-"`
44 ResourceConfig corekccv1alpha1.ResourceConfig `json:"-"`
45 TFResource *tfschema.Resource `json:"-"`
46 }
47
48
49
50 func NewResource(u *unstructured.Unstructured, sm *corekccv1alpha1.ServiceMapping, p *tfschema.Provider) (*Resource, error) {
51 rc, err := servicemappingloader.GetResourceConfig(sm, u)
52 if err != nil {
53 return nil, err
54 }
55 resource, err := NewResourceFromResourceConfig(rc, p)
56 if err != nil {
57 return nil, err
58 }
59
60 r, err := k8s.NewResource(u)
61 if err != nil {
62 return nil, err
63 }
64 resource.Resource = *r
65
66
67 resource.Original, err = k8s.NewResource(u)
68 if err != nil {
69 return nil, err
70 }
71
72 if err := resource.ValidateResourceIDIfSupported(); err != nil {
73 return nil, err
74 }
75
76 return resource, nil
77 }
78
79 func NewResourceFromResourceConfig(rc *corekccv1alpha1.ResourceConfig, p *tfschema.Provider) (*Resource, error) {
80 tfResource, ok := p.ResourcesMap[rc.Name]
81 if !ok {
82 return nil, fmt.Errorf("error getting TF resource: unknown resource %v", rc.Name)
83 }
84 resource := &Resource{
85 TFInfo: &terraform.InstanceInfo{
86 Type: rc.Name,
87 },
88 ResourceConfig: *rc,
89 TFResource: tfResource,
90 }
91 return resource, nil
92 }
93
94 func getServerGeneratedIDFromStatus(rc *corekccv1alpha1.ResourceConfig, status map[string]interface{}) (string, bool, error) {
95 splitPath := text.SnakeCaseStrsToLowerCamelCaseStrs(
96 strings.Split(rc.ServerGeneratedIDField, "."))
97
98 return unstructured.NestedString(status, splitPath...)
99 }
100
101 func (r *Resource) ValidateResourceIDIfSupported() error {
102 if !SupportsResourceIDField(&r.ResourceConfig) {
103 return nil
104 }
105
106 _, err := r.IsResourceIDConfigured()
107 if err != nil {
108 return fmt.Errorf("error validating '%s' field: %v", k8s.ResourceIDFieldPath, err)
109 }
110 return nil
111 }
112
113 func (r *Resource) ConstructServerGeneratedIDInStatusFromResourceID(c client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (string, error) {
114 resourceID, foundInSpec, err := unstructured.NestedString(r.Spec, k8s.ResourceIDFieldName)
115 if err != nil {
116 return "", fmt.Errorf("error getting '%s': %w",
117 k8s.ResourceIDFieldPath, err)
118 }
119
120 if !foundInSpec {
121 return "", nil
122 }
123
124 if foundInSpec && resourceID == "" {
125 return "", fmt.Errorf("the value of '%s' is invalid: '' (empty "+
126 "string)", k8s.ResourceIDFieldPath)
127 }
128
129 resourceID, err = ResolveValueTemplate(
130 r.ResourceConfig.ResourceID.ValueTemplate, resourceID, r, c, smLoader)
131 if err != nil {
132 return "", fmt.Errorf("error expanding resource ID: %w", err)
133 }
134
135 return resourceID, nil
136 }
137
138 func (r *Resource) SelfLinkAsID() (string, error) {
139 selfLink, found, err := unstructured.NestedString(r.Status, k8s.SelfLinkFieldName)
140 if err != nil {
141 return "", fmt.Errorf("error getting '%s': %w",
142 k8s.SelfLinkFieldName, err)
143 }
144 if !found {
145 return "", fmt.Errorf("resource %s doesn't have a '%s' field", r.Name, k8s.SelfLinkFieldName)
146 }
147 return selfLink, nil
148 }
149
150
151
152 func (r *Resource) GetImportID(c client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (string, error) {
153 template := r.ResourceConfig.IDTemplate
154 if r.HasServerGeneratedIDField() {
155
156
157 if template == "" {
158 template = r.serverGeneratedIdToTemplate()
159 if _, err := r.GetServerGeneratedID(); err != nil {
160 return "", err
161 }
162 } else if r.serverGeneratedIdInIdTemplate() {
163 if _, err := r.GetServerGeneratedID(); err != nil {
164 return "", err
165 }
166 }
167 } else {
168 if template == "" {
169 template = fmt.Sprintf("{{project?}}/{{%v}}", r.ResourceConfig.MetadataMapping.Name)
170 }
171 }
172 value, err := expandTemplate(template, r, c, smLoader)
173 if err != nil {
174
175
176
177
178 if r.shouldFallBackToServerGeneratedIdIfImportIdFails() {
179 template = r.serverGeneratedIdToTemplate()
180 return expandTemplate(template, r, c, smLoader)
181 }
182 return "", err
183 }
184 return value, nil
185 }
186
187 func (r *Resource) HasIDTemplate() bool {
188 return r.ResourceConfig.IDTemplate != ""
189 }
190
191 func (r *Resource) HasServerGeneratedIDField() bool {
192 return r.ResourceConfig.ServerGeneratedIDField != ""
193 }
194
195 func (r *Resource) serverGeneratedIdToTemplate() string {
196 return ServerGeneratedIdToTemplate(&r.ResourceConfig)
197 }
198
199 func (r *Resource) shouldFallBackToServerGeneratedIdIfImportIdFails() bool {
200 return r.HasServerGeneratedIDField() && !r.serverGeneratedIdInIdTemplate()
201 }
202
203 func (r *Resource) serverGeneratedIdInIdTemplate() bool {
204 if !r.HasIDTemplate() || !r.HasServerGeneratedIDField() {
205 return false
206 }
207 idTemplateFormOfServerGeneratedId := fmt.Sprintf("{{%v}}", r.ResourceConfig.ServerGeneratedIDField)
208 return strings.Contains(r.ResourceConfig.IDTemplate, idTemplateFormOfServerGeneratedId)
209 }
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224 func (r *Resource) GetServerGeneratedID() (string, error) {
225 if SupportsResourceIDField(&r.ResourceConfig) && IsResourceIDFieldServerGenerated(&r.ResourceConfig) {
226 id, exists, err := unstructured.NestedString(r.Spec, k8s.ResourceIDFieldName)
227 if err != nil {
228 return "", fmt.Errorf("error getting server-generated resource ID: %w", err)
229 }
230 if exists {
231 if id == "" {
232 return "", fmt.Errorf("invalid empty value for \"spec.%s\"",
233 k8s.ResourceIDFieldName)
234 }
235 return id, nil
236 }
237 }
238
239
240
241 idInStatus, exists, err := getServerGeneratedIDFromStatus(&r.ResourceConfig, r.Status)
242 if err != nil {
243 return "", fmt.Errorf("error getting server-generated ID: %v", err)
244 }
245 if !exists {
246 return "", k8s.NewServerGeneratedIDNotFoundError(r.GroupVersionKind(),
247 k8s.GetNamespacedName(r))
248 }
249
250 if idInStatus == "" {
251 return "", fmt.Errorf("invalid empty value for \"status.%s\"",
252 text.SnakeCaseToLowerCamelCase(r.ResourceConfig.ServerGeneratedIDField))
253 }
254
255 if SupportsResourceIDField(&r.ResourceConfig) && IsResourceIDFieldServerGenerated(&r.ResourceConfig) {
256 id, err := extractValueSegmentFromIDInStatus(idInStatus,
257 r.ResourceConfig.ResourceID.ValueTemplate)
258 if err != nil {
259 return "", fmt.Errorf("error getting server-generated "+
260 "resource ID from the value of '%s': %w", fmt.Sprintf("status.%s",
261 text.SnakeCaseToLowerCamelCase(r.ResourceConfig.ServerGeneratedIDField)), err)
262 }
263
264 if id == "" {
265 return "", fmt.Errorf("invalid empty value for server-generated resource ID")
266 }
267 return id, nil
268 }
269 return idInStatus, nil
270 }
271
272
273
274
275
276
277
278
279
280 func (r *Resource) GetResourceID() (string, error) {
281 resourceID, exists, err := unstructured.NestedString(r.Spec, k8s.ResourceIDFieldName)
282 if err != nil {
283 return "", fmt.Errorf("error getting the value of "+
284 "\"spec.%s\": %w", k8s.ResourceIDFieldName, err)
285 }
286
287 if !exists {
288 if !IsResourceIDFieldServerGenerated(&r.ResourceConfig) {
289 resourceID = r.GetName()
290 } else {
291 resourceID, err = r.GetServerGeneratedID()
292 if err != nil {
293 return "", err
294 }
295 }
296 }
297
298 if resourceID == "" {
299 return "", fmt.Errorf("invalid empty value for resource ID")
300 }
301 return resourceID, nil
302 }
303
304 func (r *Resource) Unreadable() bool {
305 return r.ResourceConfig.Unreadable != nil && *r.ResourceConfig.Unreadable
306 }
307
308
309
310 func (r *Resource) AllTopLevelFieldsAreImmutableOrComputed() bool {
311 for _, schema := range r.TFResource.Schema {
312 if !schema.Computed && !schema.ForceNew {
313 return false
314 }
315 }
316 return true
317 }
318
319 func SupportsResourceIDField(rc *corekccv1alpha1.ResourceConfig) bool {
320 return rc.ResourceID.TargetField != ""
321 }
322
323 func IsResourceIDFieldServerGenerated(rc *corekccv1alpha1.ResourceConfig) bool {
324 return rc.ResourceID.TargetField == rc.ServerGeneratedIDField
325 }
326
327 func SupportsServerGeneratedIDField(rc *corekccv1alpha1.ResourceConfig) bool {
328 return rc.ServerGeneratedIDField != ""
329 }
330
331 func SupportsHierarchicalReferences(rc *corekccv1alpha1.ResourceConfig) bool {
332 return len(rc.HierarchicalReferences) > 0
333 }
334
335 func SupportsIAM(rc *corekccv1alpha1.ResourceConfig) bool {
336 emptyIAMConfig := corekccv1alpha1.IAMConfig{}
337 return !reflect.DeepEqual(rc.IAMConfig, emptyIAMConfig)
338 }
339
340 func GVKForResource(sm *corekccv1alpha1.ServiceMapping, rc *corekccv1alpha1.ResourceConfig) schema.GroupVersionKind {
341 return schema.GroupVersionKind{
342 Group: sm.Name,
343 Version: sm.GetVersionFor(rc),
344 Kind: rc.Kind,
345 }
346 }
347
348 func ServerGeneratedIdToTemplate(rc *corekccv1alpha1.ResourceConfig) string {
349 return fmt.Sprintf("{{%v}}", rc.ServerGeneratedIDField)
350 }
351
View as plain text