1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package kcclite
16
17 import (
18 "fmt"
19 "path"
20 "regexp"
21 "strings"
22
23 corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl"
25 dclextension "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/extension"
26 dclmetadata "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/metadata"
27 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/schema/dclschemaloader"
28 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
29 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/krmtotf"
30 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
31 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util"
32
33 "github.com/nasa9084/go-openapi"
34 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
35 "sigs.k8s.io/controller-runtime/pkg/client"
36 )
37
38 var fieldRegex = regexp.MustCompile("{{([0-9A-Za-z]+)}}")
39
40 func CanonicalizeReferencedResourceName(name string, nameValueTemplate string, refResource *k8s.Resource,
41 smLoader dclmetadata.ServiceMetadataLoader, schemaLoader dclschemaloader.DCLSchemaLoader,
42 serviceMappingLoader *servicemappingloader.ServiceMappingLoader, kubeClient client.Client) (string, error) {
43 ret := strings.ReplaceAll(nameValueTemplate, "{{name}}", name)
44 var resolutionError error
45 resolveFunc := func(s string) string {
46 field := fieldRegex.FindStringSubmatch(s)[1]
47 if dcl.IsParentReferenceField([]string{field}) {
48 val, err := resolveParentReferenceFieldValue(field, refResource, smLoader, schemaLoader, serviceMappingLoader, kubeClient)
49 if err != nil {
50 resolutionError = decorateErrorForResolvingReferenceField(err, field, refResource)
51 return ""
52 }
53 return val
54 }
55 val, exists, err := unstructured.NestedString(refResource.Spec, field)
56 if err != nil {
57 resolutionError = fmt.Errorf("error getting value for DCL field %v in spec of referenced resource %v with GroupVersionKind %v: %w",
58 field, k8s.GetNamespacedName(refResource), refResource.GroupVersionKind(), err)
59 return ""
60 }
61 if exists {
62 return val
63 }
64
65 val, exists, err = unstructured.NestedString(refResource.Status, field)
66 if err != nil {
67 resolutionError = fmt.Errorf("error getting value for DCL field %v in status of referenced resource %v with GroupVersionKind %v: %w",
68 field, k8s.GetNamespacedName(refResource), refResource.GroupVersionKind(), err)
69 return ""
70 }
71 if exists {
72 return val
73 }
74 resolutionError = fmt.Errorf("no value found for DCL field %v in referenced resource %v with GroupVersionKind %v",
75 field, k8s.GetNamespacedName(refResource), refResource.GroupVersionKind())
76 return ""
77 }
78 return fieldRegex.ReplaceAllStringFunc(ret, resolveFunc), resolutionError
79 }
80
81 func resolveParentReferenceFieldValue(field string, resource *k8s.Resource, smLoader dclmetadata.ServiceMetadataLoader,
82 schemaLoader dclschemaloader.DCLSchemaLoader, serviceMappingLoader *servicemappingloader.ServiceMappingLoader, kubeClient client.Client) (string, error) {
83 if dclmetadata.IsDCLBasedResourceKind(resource.GroupVersionKind(), smLoader) {
84 return resolveParentReferenceFieldValueForDCLResource(field, resource, smLoader, schemaLoader, kubeClient)
85 }
86
87 return resolveParentReferenceFieldValueForTFResource(field, resource, serviceMappingLoader, kubeClient)
88 }
89
90 func resolveParentReferenceFieldValueForDCLResource(field string, resource *k8s.Resource, smLoader dclmetadata.ServiceMetadataLoader,
91 schemaLoader dclschemaloader.DCLSchemaLoader, kubeClient client.Client) (string, error) {
92 r, found := smLoader.GetResourceWithGVK(resource.GroupVersionKind())
93 if !found {
94 return "", fmt.Errorf("ServiceMetadata for resource with GroupVersionKind %v not found", resource.GroupVersionKind())
95 }
96 if !r.Releasable {
97 return "", fmt.Errorf("expected resource with GroupVersionKind %v to be supported via DCL, but it is not", resource.GroupVersionKind())
98 }
99
100
101
102 if !r.SupportsHierarchicalReferences {
103 return resolveParentFieldFromContainerAnnotation(field, resource)
104 }
105
106 schema, err := dclschemaloader.GetDCLSchemaForGVK(resource.GroupVersionKind(), smLoader, schemaLoader)
107 if err != nil {
108 return "", fmt.Errorf("error getting DCL schema for GroupVersionKind %v: %w", resource.GroupVersionKind(), err)
109 }
110 fieldSchema, ok := schema.Properties[field]
111 if !ok {
112 return "", fmt.Errorf("could not find schema for DCL field '%v' for GroupVersionKind %v", field, resource.GroupVersionKind())
113 }
114 if dcl.IsMultiTypeParentReferenceField([]string{field}) {
115 return resolveMultiTypeParentReferenceFieldValueForDCLResource(field, fieldSchema, resource, smLoader, kubeClient)
116 }
117 return resolveSingleTypeParentReferenceFieldValueForDCLResource(field, fieldSchema, resource, smLoader, kubeClient)
118 }
119
120 func resolveMultiTypeParentReferenceFieldValueForDCLResource(field string, schema *openapi.Schema, resource *k8s.Resource,
121 smLoader dclmetadata.ServiceMetadataLoader, kubeClient client.Client) (string, error) {
122 rawVal, tc, err := dcl.GetHierarchicalRefFromConfigForMultiParentResource(resource.Spec, schema, smLoader)
123 if err != nil {
124 return "", fmt.Errorf("error getting hierarchical reference from config for multi-parent resource: %w", err)
125 }
126 if rawVal == nil {
127 return "", fmt.Errorf("no hierarchical reference found for multi-parent resource")
128 }
129 refField := tc.Key
130 refObj, ok := rawVal.(map[string]interface{})
131 if !ok {
132 return "", fmt.Errorf("expected the value to be map[string]interface{} for reference field %v but was actually %T", refField, rawVal)
133 }
134 val, err := resolveReferenceObject(refObj, tc, resource.GetNamespace(), kubeClient)
135 if err != nil {
136 return "", err
137 }
138 name := path.Base(val)
139 return fmt.Sprintf("%v%v", dcl.ParentPrefixForKind(tc.GVK.Kind), name), nil
140 }
141
142 func resolveSingleTypeParentReferenceFieldValueForDCLResource(field string, schema *openapi.Schema, resource *k8s.Resource,
143 smLoader dclmetadata.ServiceMetadataLoader, kubeClient client.Client) (string, error) {
144 refField, err := dclextension.GetReferenceFieldName([]string{field}, schema)
145 if err != nil {
146 return "", fmt.Errorf("error getting the reference field name for DCL field '%v': %w", field, err)
147 }
148 rawVal, ok := resource.Spec[refField]
149 if !ok || rawVal == nil {
150 return "", fmt.Errorf("no hierarchical reference found for single-parent resource")
151 }
152 refObj, ok := rawVal.(map[string]interface{})
153 if !ok {
154 return "", fmt.Errorf("expected the value to be map[string]interface{} for reference field %v but was actually %T", refField, rawVal)
155 }
156 tcs, err := dcl.GetReferenceTypeConfigs(schema, smLoader)
157 if err != nil {
158 return "", fmt.Errorf("error getting type configs for DCL field '%v': %w", field, err)
159 }
160 if len(tcs) > 1 {
161 return "", fmt.Errorf("unexpectedly got more than one type config for DCL field '%v' which is supposed to be a single-type parent reference field", field)
162 }
163 val, err := resolveReferenceObject(refObj, &tcs[0], resource.GetNamespace(), kubeClient)
164 if err != nil {
165 return "", err
166 }
167 name := path.Base(val)
168 return name, nil
169 }
170
171 func resolveParentReferenceFieldValueForTFResource(field string, resource *k8s.Resource,
172 serviceMappingLoader *servicemappingloader.ServiceMappingLoader, kubeClient client.Client) (string, error) {
173 u, err := resource.MarshalAsUnstructured()
174 if err != nil {
175 return "", fmt.Errorf("error marshalling resource to unstructured: %w", err)
176 }
177 rc, err := serviceMappingLoader.GetResourceConfig(u)
178 if err != nil {
179 return "", fmt.Errorf("error getting resource config for resource: %w", err)
180 }
181 krmResource := &krmtotf.Resource{
182 Resource: *resource,
183 ResourceConfig: *rc,
184 }
185
186
187 if !krmtotf.SupportsHierarchicalReferences(rc) {
188 return resolveParentFieldFromContainerAnnotation(field, resource)
189 }
190 ref, hierarchicalRef, err := k8s.GetHierarchicalReference(resource, rc.HierarchicalReferences)
191 if err != nil {
192 return "", fmt.Errorf("error getting hierarchical reference: %w", err)
193 }
194 refConfig, err := krmtotf.GetReferenceConfigForHierarchicalReference(hierarchicalRef, rc)
195 if err != nil {
196 return "", fmt.Errorf("error getting reference config for hierarchical reference: %w", err)
197 }
198 var refObj map[string]interface{}
199 if err := util.Marshal(ref, &refObj); err != nil {
200 return "", fmt.Errorf("error marshalling hierarchical reference to map[string]interface{}: %w", err)
201 }
202 rawVal, err := krmtotf.ResolveReferenceObject(refObj, *refConfig, krmResource, kubeClient, serviceMappingLoader)
203 if err != nil {
204 return "", fmt.Errorf("error resolving resource reference object representing resource's hierarchical reference: %w", err)
205 }
206 val, ok := rawVal.(string)
207 if !ok {
208 return "", fmt.Errorf("expected the resolved value of the resource reference object to be string, but was actually %T", rawVal)
209 }
210 name := path.Base(val)
211 if dcl.IsMultiTypeParentReferenceField([]string{field}) {
212 return fmt.Sprintf("%v%v", dcl.ParentPrefixForKind(refConfig.GVK.Kind), name), nil
213 }
214 return name, nil
215 }
216
217 func resolveParentFieldFromContainerAnnotation(field string, resource *k8s.Resource) (string, error) {
218 annotation := k8s.GetAnnotationForContainerType(corekccv1alpha1.ContainerType(field))
219 val, ok := k8s.GetAnnotation(annotation, resource)
220 if !ok || val == "" {
221 return "", fmt.Errorf("no value found for annotation %v in resource %v",
222 annotation, k8s.GetNamespacedName(resource))
223 }
224 return val, nil
225 }
226
227 func decorateErrorForResolvingReferenceField(err error, field string, refResource *k8s.Resource) error {
228 if unwrappedErr, ok := k8s.AsReferenceNotFoundError(err); ok {
229 return k8s.NewTransitiveDependencyNotFoundError(unwrappedErr.RefResourceGVK, unwrappedErr.RefResource)
230 }
231 if unwrappedErr, ok := k8s.AsReferenceNotReadyError(err); ok {
232 return k8s.NewTransitiveDependencyNotReadyError(unwrappedErr.RefResourceGVK, unwrappedErr.RefResource)
233 }
234 return fmt.Errorf("error getting value for DCL field %v in referenced resource %v with GroupVersionKind %v: %w",
235 field, k8s.GetNamespacedName(refResource), refResource.GroupVersionKind(), err)
236 }
237
View as plain text