1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package k8s
16
17 import (
18 "context"
19 "errors"
20 "fmt"
21 "strings"
22
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
24 corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
25 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util"
26
27 corev1 "k8s.io/api/core/v1"
28 apierrors "k8s.io/apimachinery/pkg/api/errors"
29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30 "k8s.io/apimachinery/pkg/runtime/schema"
31 "k8s.io/apimachinery/pkg/types"
32 "sigs.k8s.io/controller-runtime/pkg/client"
33 )
34
35 var errContainerAnnotationNotFound = fmt.Errorf("no container annotation found")
36
37 func GetReferencedResourceIfReady(resourceRef *corekccv1alpha1.ResourceReference, gvk schema.GroupVersionKind, resourceNamespace string, kubeClient client.Client) (*Resource, error) {
38 r, err := GetReferencedResource(resourceRef, gvk, resourceNamespace, kubeClient)
39 if err != nil {
40 return nil, err
41 }
42 if !IsResourceReady(r) {
43 return nil, NewReferenceNotReadyErrorForResource(r)
44 }
45 return r, nil
46 }
47
48 func GetReferencedResource(resourceRef *corekccv1alpha1.ResourceReference, gvk schema.GroupVersionKind, resourceNamespace string, kubeClient client.Client) (*Resource, error) {
49 u, err := GetReferencedResourceAsUnstruct(resourceRef, gvk, resourceNamespace, kubeClient)
50 if err != nil {
51 return nil, err
52 }
53 r := &Resource{}
54 if err := util.Marshal(u, r); err != nil {
55 return nil, fmt.Errorf("error marshalling unstruct for referenced resource %v with GroupVersionKind %v to k8s resource: %w",
56 GetNamespacedName(u), u.GroupVersionKind(), err)
57 }
58 return r, nil
59 }
60
61 func GetReferencedResourceAsUnstruct(resourceRef *corekccv1alpha1.ResourceReference, gvk schema.GroupVersionKind, resourceNamespace string, kubeClient client.Client) (*unstructured.Unstructured, error) {
62 name := resourceRef.Name
63 if name == "" {
64 return nil, fmt.Errorf("resource reference is missing required 'name' field")
65 }
66 namespace := resourceRef.Namespace
67 if namespace == "" {
68 namespace = resourceNamespace
69 }
70 nn := types.NamespacedName{Name: name, Namespace: namespace}
71 u := &unstructured.Unstructured{}
72 u.SetGroupVersionKind(gvk)
73 if err := kubeClient.Get(context.TODO(), nn, u); err != nil {
74 if apierrors.IsNotFound(err) {
75 return nil, NewReferenceNotFoundError(gvk, nn)
76 }
77 return nil, fmt.Errorf("error getting referenced resource %v with GroupVersionKind %v from API server: %w", nn, gvk, err)
78 }
79 return u, nil
80 }
81
82
83
84 func EnsureHierarchicalReference(ctx context.Context, resource *Resource, hierarchicalRefs []corekccv1alpha1.HierarchicalReference, containers []corekccv1alpha1.Container, c client.Client) error {
85 if len(hierarchicalRefs) == 0 {
86 return nil
87 }
88
89 ns := corev1.Namespace{}
90 if err := c.Get(ctx, types.NamespacedName{Name: resource.GetNamespace()}, &ns); err != nil {
91 return fmt.Errorf("error getting namespace %v: %v", resource.GetNamespace(), err)
92 }
93
94 return SetDefaultHierarchicalReference(resource, &ns, hierarchicalRefs, containers)
95 }
96
97
98
99
100
101
102
103 func SetDefaultHierarchicalReference(resource *Resource, ns *corev1.Namespace, hierarchicalRefs []corekccv1alpha1.HierarchicalReference, containers []corekccv1alpha1.Container) error {
104 if len(hierarchicalRefs) == 0 {
105
106 return nil
107 }
108
109
110
111 ref, _, err := GetHierarchicalReference(resource, hierarchicalRefs)
112 if err != nil {
113 return fmt.Errorf("error getting hierarchical reference from object: %v", err)
114 }
115 if ref != nil {
116 return nil
117 }
118
119
120
121 if len(containers) > 0 {
122 annotations := resource.GetAnnotations()
123 containerTypes := ContainerTypes(containers)
124 err := setHierarchicalReferenceUsingContainerAnnotation(resource, annotations, hierarchicalRefs, containerTypes)
125 if err != nil && !errors.Is(err, errContainerAnnotationNotFound) {
126 return fmt.Errorf("error setting hierarchical reference using resource-level container annotation: %v", err)
127 } else if err == nil {
128 return nil
129 }
130 }
131
132
133
134
135
136
137 nsAnnotations := ns.GetAnnotations()
138 nsContainerTypes := ContainerTypesFor(hierarchicalRefs)
139 err = setHierarchicalReferenceUsingContainerAnnotation(resource, nsAnnotations, hierarchicalRefs, nsContainerTypes)
140 if err != nil && !errors.Is(err, errContainerAnnotationNotFound) {
141 return fmt.Errorf("error setting hierarchical reference using namespace-level container annotation: %v", err)
142 } else if err == nil {
143 return nil
144 }
145
146
147 h := HierarchicalReferenceWithType(hierarchicalRefs, corekccv1alpha1.HierarchicalReferenceTypeProject)
148 if h != nil {
149 if err := SetHierarchicalReference(resource, h, ns.GetName()); err != nil {
150 return fmt.Errorf("error setting hierarchical reference from using namespace name: %v", err)
151 }
152 return nil
153 }
154
155 possibleFields := HierarchicalReferencesToFields(hierarchicalRefs)
156 possibleAnnotations := containerTypesToAnnotations(nsContainerTypes)
157 return fmt.Errorf("resource must have one field among [%v], or namespace must have one annotation among [%v]",
158 strings.Join(possibleFields, ", "), strings.Join(possibleAnnotations, ", "))
159 }
160
161
162
163
164
165
166
167 func GetHierarchicalReference(resource *Resource, hierarchicalRefs []corekccv1alpha1.HierarchicalReference) (
168 *corekccv1alpha1.ResourceReference, corekccv1alpha1.HierarchicalReference, error) {
169 return GetHierarchicalReferenceFromSpec(resource.Spec, hierarchicalRefs)
170 }
171
172 func GetHierarchicalReferenceFromSpec(spec map[string]interface{}, hierarchicalRefs []corekccv1alpha1.HierarchicalReference) (
173 *corekccv1alpha1.ResourceReference, corekccv1alpha1.HierarchicalReference, error) {
174 var resourceRef *corekccv1alpha1.ResourceReference
175 var hierarchicalRef corekccv1alpha1.HierarchicalReference
176 for _, h := range hierarchicalRefs {
177 val, ok, err := unstructured.NestedMap(spec, h.Key)
178 if err != nil {
179 return nil, corekccv1alpha1.HierarchicalReference{},
180 fmt.Errorf("error reading spec.%v: %v", h.Key, err)
181 }
182 if !ok {
183 continue
184 }
185
186 if resourceRef != nil {
187 return nil, corekccv1alpha1.HierarchicalReference{},
188 fmt.Errorf("only one of spec.%v and spec.%v can be specified", h.Key, hierarchicalRef.Key)
189 }
190
191 ref := &corekccv1alpha1.ResourceReference{}
192 if err = util.Marshal(val, ref); err != nil {
193 return nil, corekccv1alpha1.HierarchicalReference{},
194 fmt.Errorf("error marshalling spec.%v into a resource reference: %v", h.Key, err)
195 }
196 resourceRef = ref
197 hierarchicalRef = h
198 }
199 return resourceRef, hierarchicalRef, nil
200 }
201
202 func setHierarchicalReferenceUsingContainerAnnotation(resource *Resource, annotations map[string]string,
203 hierarchicalRefs []corekccv1alpha1.HierarchicalReference, containerTypes []corekccv1alpha1.ContainerType) error {
204
205 containerVal, containerType, err := GetContainerAnnotation(annotations, containerTypes)
206 if err != nil {
207 return fmt.Errorf("error getting container annotation from annotations: %v", err)
208 }
209 if containerVal == "" {
210 return errContainerAnnotationNotFound
211 }
212
213 h := HierarchicalReferenceWithType(hierarchicalRefs, HierarchicalReferenceTypeFor(containerType))
214 if h == nil {
215 return fmt.Errorf("no hierarchical reference found for container type %v", containerType)
216 }
217
218 return SetHierarchicalReference(resource, h, containerVal)
219 }
220
221 func SetHierarchicalReference(resource *Resource, hierarchicalRef *corekccv1alpha1.HierarchicalReference, externalVal string) error {
222 val := corekccv1alpha1.ResourceReference{
223 External: externalVal,
224 }
225 var valAsMap map[string]interface{}
226 if err := util.Marshal(val, &valAsMap); err != nil {
227 return fmt.Errorf("error marshalling resource reference to map: %v", err)
228 }
229 if resource.Spec == nil {
230 resource.Spec = make(map[string]interface{})
231 }
232 return unstructured.SetNestedMap(resource.Spec, valAsMap, hierarchicalRef.Key)
233 }
234
235 func HierarchicalReferenceWithType(hierarchicalRefs []corekccv1alpha1.HierarchicalReference, hType corekccv1alpha1.HierarchicalReferenceType) *corekccv1alpha1.HierarchicalReference {
236 for _, h := range hierarchicalRefs {
237 if h.Type == hType {
238 return &h
239 }
240 }
241 return nil
242 }
243
244 func HierarchicalReferencesToFields(hierarchicalRefs []corekccv1alpha1.HierarchicalReference) []string {
245 fields := make([]string, 0)
246 for _, h := range hierarchicalRefs {
247 fields = append(fields, "spec."+h.Key)
248 }
249 return fields
250 }
251
252 func HierarchicalReferenceTypeFor(containerType corekccv1alpha1.ContainerType) corekccv1alpha1.HierarchicalReferenceType {
253 switch containerType {
254 case v1alpha1.ContainerTypeProject:
255 return v1alpha1.HierarchicalReferenceTypeProject
256 case v1alpha1.ContainerTypeFolder:
257 return v1alpha1.HierarchicalReferenceTypeFolder
258 case v1alpha1.ContainerTypeOrganization:
259 return v1alpha1.HierarchicalReferenceTypeOrganization
260 default:
261 panic(fmt.Errorf("unrecognized container type: %v", containerType))
262 }
263 }
264
265 func ContainerTypesFor(hierarchicalRefs []corekccv1alpha1.HierarchicalReference) []corekccv1alpha1.ContainerType {
266 types := make([]corekccv1alpha1.ContainerType, 0)
267 for _, h := range hierarchicalRefs {
268 switch h.Type {
269 case corekccv1alpha1.HierarchicalReferenceTypeBillingAccount:
270
271
272
273 continue
274 default:
275 types = append(types, containerTypeFor(h))
276 }
277 }
278 return types
279 }
280
281 func containerTypeFor(hierarchicalRef corekccv1alpha1.HierarchicalReference) corekccv1alpha1.ContainerType {
282 switch hierarchicalRef.Type {
283 case corekccv1alpha1.HierarchicalReferenceTypeProject:
284 return corekccv1alpha1.ContainerTypeProject
285 case corekccv1alpha1.HierarchicalReferenceTypeFolder:
286 return corekccv1alpha1.ContainerTypeFolder
287 case corekccv1alpha1.HierarchicalReferenceTypeOrganization:
288 return corekccv1alpha1.ContainerTypeOrganization
289 case corekccv1alpha1.HierarchicalReferenceTypeBillingAccount:
290 panic(fmt.Errorf("there is no container type equivalent to the hierarchical reference type %v", hierarchicalRef.Type))
291 default:
292 panic(fmt.Errorf("unrecognized hierarchical reference type %v", hierarchicalRef.Type))
293 }
294 }
295
View as plain text