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 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
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 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util"
27
28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
29 "sigs.k8s.io/controller-runtime/pkg/client"
30 )
31
32 func GetReferencedResource(r *Resource, typeConfig corekccv1alpha1.TypeConfig,
33 resourceRef *v1alpha1.ResourceReference, kubeClient client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (rsrc *Resource, err error) {
34 if resourceRef.External != "" {
35 return nil, fmt.Errorf("reference is external: %v", resourceRef.External)
36 }
37 u, err := k8s.GetReferencedResourceAsUnstruct(resourceRef, typeConfig.GVK, r.GetNamespace(), kubeClient)
38 if err != nil {
39 return nil, err
40 }
41 rsrc = &Resource{}
42 if err := util.Marshal(u, rsrc); err != nil {
43 return nil, fmt.Errorf("error parsing %v", u.GetName())
44 }
45 if typeConfig.DCLBasedResource {
46 return rsrc, nil
47 }
48
49 rc, err := smLoader.GetResourceConfig(u)
50 if err != nil {
51 return nil, fmt.Errorf("error getting ResourceConfig for referenced resource %v: %w", r.GetName(), err)
52 }
53 rsrc.ResourceConfig = *rc
54 return rsrc, nil
55 }
56
57 func handleResourceReference(config map[string]interface{}, refConfig v1alpha1.ReferenceConfig, r *Resource, c client.Client, smLoader *servicemappingloader.ServiceMappingLoader) error {
58 path := strings.Split(refConfig.TFField, ".")
59 return ResolveResourceReference(path, config, refConfig, r, c, smLoader)
60 }
61
62 func ResolveResourceReference(path []string, obj interface{}, refConfig v1alpha1.ReferenceConfig,
63 r *Resource, kubeClient client.Client, smLoader *servicemappingloader.ServiceMappingLoader) error {
64 if obj == nil {
65 return nil
66 }
67
68
69 var config map[string]interface{}
70 switch objAsType := obj.(type) {
71 case []interface{}:
72 for _, item := range objAsType {
73 if err := ResolveResourceReference(path, item, refConfig, r, kubeClient, smLoader); err != nil {
74 return err
75 }
76 }
77 return nil
78 case map[string]interface{}:
79 config = objAsType
80 default:
81 return fmt.Errorf("error resolving reference: cannot iterate through type that is not object or list of objects")
82 }
83
84 field := text.SnakeCaseToLowerCamelCase(path[0])
85
86
87 if len(path) > 1 {
88 return ResolveResourceReference(path[1:], config[field], refConfig, r, kubeClient, smLoader)
89 }
90
91
92 key := field
93 if refConfig.Key != "" {
94 key = refConfig.Key
95 }
96 ref := config[key]
97 if ref == nil {
98 return nil
99 }
100
101 var resolvedVal interface{}
102 switch refAsType := ref.(type) {
103 case map[string]interface{}:
104 var err error
105 resolvedVal, err = ResolveReferenceObject(refAsType, refConfig, r, kubeClient, smLoader)
106 if err != nil {
107 return err
108 }
109 case []interface{}:
110 resolvedList := make([]interface{}, 0)
111 for _, item := range refAsType {
112 itemAsMap, ok := item.(map[string]interface{})
113 if !ok {
114 return fmt.Errorf("expected reference %v to be object but was not", key)
115 }
116 resolvedVal, err := ResolveReferenceObject(itemAsMap, refConfig, r, kubeClient, smLoader)
117 if err != nil {
118 return err
119 }
120 resolvedList = append(resolvedList, resolvedVal)
121 }
122 resolvedVal = resolvedList
123 default:
124 return fmt.Errorf("unexpected type for reference field %v", path[0])
125 }
126 config[field] = resolvedVal
127 if field != key {
128 delete(config, key)
129 }
130 return nil
131 }
132
133 func ResolveReferenceObject(resourceRefValRaw map[string]interface{},
134 refConfig corekccv1alpha1.ReferenceConfig, r *Resource, kubeClient client.Client, smLoader *servicemappingloader.ServiceMappingLoader) (interface{}, error) {
135 typeConfig := refConfig.TypeConfig
136 if len(refConfig.Types) > 0 {
137 var (
138 nestedRefValRaw interface{}
139 err error
140 ok bool
141 found bool
142 )
143 for _, typeConfig = range refConfig.Types {
144 nestedRefValRaw, found, err = unstructured.NestedFieldNoCopy(resourceRefValRaw, typeConfig.Key)
145 if err != nil {
146 return nil, err
147 }
148 if found {
149 if typeConfig.JSONSchemaType != "" {
150
151 return resolveValueTemplateFromInterface(typeConfig.ValueTemplate, nestedRefValRaw, r, kubeClient, smLoader)
152 }
153 resourceRefValRaw, ok = nestedRefValRaw.(map[string]interface{})
154 if !ok {
155 return nil, fmt.Errorf("expected reference to be object")
156 }
157 break
158 }
159 }
160 if !found {
161 return nil, nil
162 }
163 }
164 resourceRef := &v1alpha1.ResourceReference{}
165 if err := util.Marshal(resourceRefValRaw, resourceRef); err != nil {
166 return nil, fmt.Errorf("field %v is a wrong format", typeConfig.Key)
167 }
168
169
170
171
172
173 if resourceRef.External != "" {
174 return resourceRef.External, nil
175 }
176
177
178
179
180 deleting := k8s.IsDeleted(&r.ObjectMeta)
181
182 refResource, err := GetReferencedResource(r, typeConfig, resourceRef, kubeClient, smLoader)
183 if err != nil {
184 if k8s.IsReferenceNotFoundError(err) {
185 if deleting {
186 return nil, nil
187 }
188 return nil, err
189 }
190 return nil, fmt.Errorf("error getting referenced resource from API server: %v", err)
191 }
192
193 if !deleting && !k8s.IsResourceReady(&refResource.Resource) {
194 return nil, k8s.NewReferenceNotReadyErrorForResource(&refResource.Resource)
195 }
196
197 resolvedVal, err := resolveTargetFieldValue(refResource, typeConfig)
198 if err != nil {
199 return nil, fmt.Errorf("error resolving value of target field of "+
200 "referenced resource %v %v: %v", refResource.GroupVersionKind(),
201 refResource.GetNamespacedName(), err)
202 }
203
204 if deleting && typeConfig.TargetField == "" && typeConfig.ValueTemplate == "" {
205 return resolvedVal, nil
206 }
207
208 return resolveValueTemplateFromInterface(typeConfig.ValueTemplate, resolvedVal, refResource, kubeClient, smLoader)
209 }
210
211 func resolveTargetFieldValue(r *Resource, tc corekccv1alpha1.TypeConfig) (interface{}, error) {
212 key := text.SnakeCaseToLowerCamelCase(tc.TargetField)
213 switch key {
214 case "":
215 return resolveDefaultTargetFieldValue(r, tc)
216 default:
217 if val, exists, _ := unstructured.NestedString(r.Spec, strings.Split(key, ".")...); exists {
218 return val, nil
219 }
220 if val, exists, _ := unstructured.NestedString(r.Status, strings.Split(key, ".")...); exists {
221 return val, nil
222 }
223
224
225
226 return nil, fmt.Errorf("referenced resource's target field %v is unsupported", tc.TargetField)
227 }
228 }
229
230 func resolveDefaultTargetFieldValue(r *Resource, tc corekccv1alpha1.TypeConfig) (interface{}, error) {
231 if !tc.DCLBasedResource && !SupportsResourceIDField(&r.ResourceConfig) {
232 return r.GetName(), nil
233 }
234
235 id, err := r.GetResourceID()
236 if err != nil {
237 return "", err
238 }
239
240 return id, nil
241 }
242
243 func IsReferenceField(qualifiedName string, rc *corekccv1alpha1.ResourceConfig) (bool, *corekccv1alpha1.ReferenceConfig) {
244 for _, refConfig := range rc.ResourceReferences {
245 if qualifiedName == refConfig.TFField {
246 return true, &refConfig
247 }
248 }
249 return false, nil
250 }
251
252 func containsReferenceField(qualifiedName string, rc *corekccv1alpha1.ResourceConfig) bool {
253 for _, refConfig := range rc.ResourceReferences {
254 if strings.HasPrefix(refConfig.TFField, qualifiedName) {
255 return true
256 }
257 }
258 return false
259 }
260
261 func GetKeyForReferenceField(refConfig *corekccv1alpha1.ReferenceConfig) string {
262 if refConfig.Key != "" {
263 return refConfig.Key
264 }
265 parts := strings.Split(refConfig.TFField, ".")
266 containerField := text.SnakeCaseToLowerCamelCase(parts[len(parts)-1])
267 return containerField
268 }
269
270 func getPathToReferenceKey(refConfig *corekccv1alpha1.ReferenceConfig) []string {
271 fieldCamelCase := text.SnakeCaseToLowerCamelCase(refConfig.TFField)
272 path := strings.Split(fieldCamelCase, ".")
273 if refConfig.Key != "" {
274 path[len(path)-1] = refConfig.Key
275 }
276 return path
277 }
278
279 func IsHierarchicalReference(ref corekccv1alpha1.ReferenceConfig, hierarchicalRefs []corekccv1alpha1.HierarchicalReference) bool {
280
281
282 if strings.Contains(ref.TFField, ".") {
283 return false
284 }
285 key := GetKeyForReferenceField(&ref)
286 for _, h := range hierarchicalRefs {
287 if h.Key == key {
288 return true
289 }
290 }
291 return false
292 }
293
294 func IsRequiredParentReference(ref corekccv1alpha1.ReferenceConfig, resource *Resource) bool {
295 if ref.Parent {
296 return true
297 }
298 if !IsHierarchicalReference(ref, resource.ResourceConfig.HierarchicalReferences) {
299 return false
300 }
301
302
303 if resource.Kind == "Project" || resource.Kind == "Folder" {
304 return false
305 }
306 return true
307 }
308
309 func GetReferenceConfigForHierarchicalReference(hierarchicalRef corekccv1alpha1.HierarchicalReference, rc *corekccv1alpha1.ResourceConfig) (*corekccv1alpha1.ReferenceConfig, error) {
310 for _, ref := range rc.ResourceReferences {
311
312
313 if strings.Contains(ref.TFField, ".") {
314 continue
315 }
316 key := GetKeyForReferenceField(&ref)
317 if key == hierarchicalRef.Key {
318 return &ref, nil
319 }
320 }
321 return nil, fmt.Errorf("no reference config found for hierarchical reference field %v", hierarchicalRef.Key)
322 }
323
View as plain text