1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package k8s
16
17 import (
18 "context"
19 "fmt"
20 "sort"
21 "strings"
22
23 corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
24 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/lease/leasable"
25 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/text"
26
27 tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
28 "github.com/nasa9084/go-openapi"
29 corev1 "k8s.io/api/core/v1"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
32 "k8s.io/apimachinery/pkg/runtime/schema"
33 "k8s.io/apimachinery/pkg/types"
34 "sigs.k8s.io/controller-runtime/pkg/client"
35 )
36
37 func GetNamespacedName(obj metav1.Object) types.NamespacedName {
38 return types.NamespacedName{
39 Namespace: obj.GetNamespace(),
40 Name: obj.GetName(),
41 }
42 }
43
44 func IsManagedByKCC(gvk schema.GroupVersionKind) bool {
45 return strings.HasSuffix(gvk.Group, CNRMGroup)
46 }
47
48 func IsDeleted(objectMeta *metav1.ObjectMeta) bool {
49 return !objectMeta.DeletionTimestamp.IsZero()
50 }
51
52 func HasAbandonAnnotation(obj metav1.Object) bool {
53 val, ok := GetAnnotation(DeletionPolicyAnnotation, obj)
54 return ok && val == DeletionPolicyAbandon
55 }
56
57 func GVKListContains(gvkList []schema.GroupVersionKind, gvk schema.GroupVersionKind) bool {
58 for _, v := range gvkList {
59 if v == gvk {
60 return true
61 }
62 }
63 return false
64 }
65
66 func GVKSetToList(gvkSet map[schema.GroupVersionKind]bool) []schema.GroupVersionKind {
67 gvkList := make([]schema.GroupVersionKind, 0, len(gvkSet))
68 for gvk := range gvkSet {
69 gvkList = append(gvkList, gvk)
70 }
71 return gvkList
72 }
73
74 func SortGVKsByKind(gvks []schema.GroupVersionKind) []schema.GroupVersionKind {
75 gvksCopy := append(make([]schema.GroupVersionKind, 0), gvks...)
76 sort.Slice(gvksCopy, func(i, j int) bool {
77 return gvksCopy[i].Kind < gvksCopy[j].Kind
78 })
79 return gvksCopy
80 }
81
82
83
84
85 func ToGVR(gvk schema.GroupVersionKind) schema.GroupVersionResource {
86 return schema.GroupVersionResource{
87 Group: gvk.Group,
88 Version: gvk.Version,
89 Resource: text.Pluralize(strings.ToLower(gvk.Kind)),
90 }
91 }
92
93 func GetProjectIDForNamespace(c client.Client, ctx context.Context, namespaceName string) (string, error) {
94 var ns corev1.Namespace
95 if err := c.Get(ctx, types.NamespacedName{Name: namespaceName}, &ns); err != nil {
96 return "", fmt.Errorf("error getting namespace '%v': %v", namespaceName, err)
97 }
98 if val, ok := GetAnnotation(ProjectIDAnnotation, &ns); ok {
99 return val, nil
100 }
101 return namespaceName, nil
102 }
103
104 func GetAnnotation(annotation string, obj metav1.Object) (string, bool) {
105 annotations := obj.GetAnnotations()
106 if annotations == nil {
107 return "", false
108 }
109 val, ok := annotations[annotation]
110 return val, ok
111 }
112
113 func SetAnnotation(annotation, val string, obj metav1.Object) {
114 annotations := obj.GetAnnotations()
115 if annotations == nil {
116 annotations = make(map[string]string)
117 }
118 annotations[annotation] = val
119 obj.SetAnnotations(annotations)
120 }
121
122 func RemoveAnnotation(annotation string, obj metav1.Object) {
123 annotations := obj.GetAnnotations()
124 if annotations == nil {
125 return
126 }
127 delete(annotations, annotation)
128 obj.SetAnnotations(annotations)
129 }
130
131 func GetManagementConflictPreventionAnnotationValue(obj metav1.Object) (ManagementConflictPreventionPolicy, error) {
132 if val, ok := GetAnnotation(ManagementConflictPreventionPolicyFullyQualifiedAnnotation, obj); ok {
133 return valueToManagementConflictPreventionPolicy(val)
134 }
135 return ManagementConflictPreventionPolicyNone,
136 fmt.Errorf("attempted to get value for annotation %v, but annotation was not found", ManagementConflictPreventionPolicyFullyQualifiedAnnotation)
137 }
138
139 func EnsureManagementConflictPreventionAnnotationForTFBasedResource(c client.Client, ctx context.Context, obj metav1.Object, rc *corekccv1alpha1.ResourceConfig, tfResourceMap map[string]*tfschema.Resource) error {
140 ns := corev1.Namespace{}
141 if err := c.Get(ctx, types.NamespacedName{Name: obj.GetNamespace()}, &ns); err != nil {
142 return fmt.Errorf("error getting namespace %v: %v", obj.GetNamespace(), err)
143 }
144 return ValidateOrDefaultManagementConflictPreventionAnnotationForTFBasedResource(obj, &ns, rc, tfResourceMap)
145 }
146
147 func ValidateOrDefaultManagementConflictPreventionAnnotationForTFBasedResource(obj metav1.Object, ns *corev1.Namespace, rc *corekccv1alpha1.ResourceConfig, tfResourceMap map[string]*tfschema.Resource) error {
148 supportsLeasing, err := leasable.ResourceConfigSupportsLeasing(rc, tfResourceMap)
149 if err != nil {
150 return err
151 }
152 return validateOrDefaultManagementConflictPreventionAnnotation(obj, ns, supportsLeasing)
153 }
154
155 func ValidateOrDefaultManagementConflictPreventionAnnotationForDCLBasedResource(obj metav1.Object, ns *corev1.Namespace, schema *openapi.Schema) error {
156 supportsLeasing, err := leasable.DCLSchemaSupportsLeasing(schema)
157 if err != nil {
158 return err
159 }
160 return validateOrDefaultManagementConflictPreventionAnnotation(obj, ns, supportsLeasing)
161 }
162
163 func validateOrDefaultManagementConflictPreventionAnnotation(obj metav1.Object, ns *corev1.Namespace, supportsLeasing bool) error {
164 value, ok := GetAnnotation(ManagementConflictPreventionPolicyFullyQualifiedAnnotation, obj)
165 if ok {
166
167 policy, err := valueToManagementConflictPreventionPolicy(value)
168 if err != nil {
169 return err
170 }
171 return validateManagementConflictPolicyForResource(policy, supportsLeasing)
172 }
173 policy, err := getDefaultManagementConflictPreventAnnotationForNamespace(ns, supportsLeasing)
174 if err != nil {
175 return err
176 }
177 SetAnnotation(ManagementConflictPreventionPolicyFullyQualifiedAnnotation, string(policy), obj)
178 return nil
179 }
180
181 func getDefaultManagementConflictPreventAnnotationForNamespace(ns *corev1.Namespace, supportLeasing bool) (ManagementConflictPreventionPolicy,
182 error) {
183 value, ok := GetAnnotation(ManagementConflictPreventionPolicyFullyQualifiedAnnotation, ns)
184 if ok {
185 policy, err := valueToManagementConflictPreventionPolicy(value)
186 if err != nil {
187 return ManagementConflictPreventionPolicyNone, fmt.Errorf("unable to use default management conflict policy for namespace: %v", err)
188 }
189 if !isManagementConflictPolicyValidForResource(policy, supportLeasing) {
190 return ManagementConflictPreventionPolicyNone, nil
191 }
192 return policy, nil
193 }
194
195 return getDefaultManagementConflictPolicyForResource(supportLeasing), nil
196 }
197
198 func isManagementConflictPolicyValidForResource(policy ManagementConflictPreventionPolicy, supportLeasing bool) bool {
199 switch policy {
200 case ManagementConflictPreventionPolicyNone:
201 return true
202 case ManagementConflictPreventionPolicyResource:
203 return supportLeasing
204 default:
205 return false
206 }
207 }
208
209
210
211
212
213
214
215
216
217 func getDefaultManagementConflictPolicyForResource(supportLeasing bool) ManagementConflictPreventionPolicy {
218 return ManagementConflictPreventionPolicyNone
219 }
220
221 func validateManagementConflictPolicyForResource(policy ManagementConflictPreventionPolicy, supportLeasing bool) error {
222 switch policy {
223 case ManagementConflictPreventionPolicyNone:
224 return nil
225 case ManagementConflictPreventionPolicyResource:
226 if !supportLeasing {
227 return fmt.Errorf("the resource kind does not support usage of %v of '%v'",
228 ManagementConflictPreventionPolicyAnnotation, policy)
229 }
230 return nil
231 default:
232 return fmt.Errorf("unknown management conflict policy: %v", policy)
233 }
234 }
235
236 func valueToManagementConflictPreventionPolicy(value string) (ManagementConflictPreventionPolicy, error) {
237 for _, policy := range ManagementConflictPreventionPolicyValues {
238 if value == string(policy) {
239 return ManagementConflictPreventionPolicy(value), nil
240 }
241 }
242 return ManagementConflictPreventionPolicyNone, fmt.Errorf("invalid management conflict policy '%v', can be one of {%v}",
243 value, strings.Join(ManagementConflictPreventionPolicyValues, ", "))
244 }
245
246 func SetDefaultContainerAnnotation(obj metav1.Object, ns *corev1.Namespace, containers []corekccv1alpha1.Container) error {
247 if len(containers) == 0 {
248
249 return nil
250 }
251
252 val, _, err := GetContainerAnnotation(obj.GetAnnotations(), ContainerTypes(containers))
253 if err != nil {
254 return fmt.Errorf("error getting container annotation from object: %v", err)
255 }
256 if val != "" {
257 return nil
258 }
259
260 val, containerType, err := GetContainerAnnotation(ns.GetAnnotations(), ContainerTypes(containers))
261 if err != nil {
262 return fmt.Errorf("error getting container annotation from object: %v", err)
263 }
264 if val != "" {
265 SetAnnotation(GetAnnotationForContainerType(containerType), val, obj)
266 return nil
267 }
268
269 if IsProjectScoped(containers) {
270 SetAnnotation(ProjectIDAnnotation, ns.GetName(), obj)
271 return nil
272 }
273 possibleAnnotations := containerTypesToAnnotations(ContainerTypes(containers))
274 return fmt.Errorf("neither resource nor namespace have the required container object annotation, one of: [%v]", strings.Join(possibleAnnotations, ", "))
275 }
276
277
278
279 func GetContainerAnnotation(annotations map[string]string, containerTypes []corekccv1alpha1.ContainerType) (string, corekccv1alpha1.ContainerType, error) {
280 var containerVal string
281 var containerType corekccv1alpha1.ContainerType
282 var found bool
283 for _, c := range containerTypes {
284 val, ok := annotations[GetAnnotationForContainerType(c)]
285 if !ok {
286 continue
287 }
288 if found {
289 return "", "", fmt.Errorf("ambiguious container annotation: found for %v and %v", containerType, c)
290 }
291 containerVal = val
292 containerType = c
293 found = true
294 }
295 return containerVal, containerType, nil
296 }
297
298 func IsProjectScoped(containers []corekccv1alpha1.Container) bool {
299 for _, c := range containers {
300 if c.Type == corekccv1alpha1.ContainerTypeProject {
301 return true
302 }
303 }
304 return false
305 }
306
307 func GetAnnotationForContainerType(containerType corekccv1alpha1.ContainerType) string {
308 switch containerType {
309 case corekccv1alpha1.ContainerTypeProject:
310 return ProjectIDAnnotation
311 case corekccv1alpha1.ContainerTypeFolder:
312 return FolderIDAnnotation
313 case corekccv1alpha1.ContainerTypeOrganization:
314 return OrgIDAnnotation
315 default:
316 panic(fmt.Errorf("unrecognized container type %v", containerType))
317 }
318 }
319
320 func containerTypesToAnnotations(containerTypes []corekccv1alpha1.ContainerType) []string {
321 annotations := make([]string, 0)
322 for _, c := range containerTypes {
323 annotations = append(annotations, GetAnnotationForContainerType(c))
324 }
325 return annotations
326 }
327
328 func ContainerTypes(containers []corekccv1alpha1.Container) []corekccv1alpha1.ContainerType {
329 types := make([]corekccv1alpha1.ContainerType, 0)
330 for _, c := range containers {
331 types = append(types, c.Type)
332 }
333 return types
334 }
335
336
337
338 func TriggerManagedFieldsMetadata(ctx context.Context, c client.Client, u *unstructured.Unstructured) (
339 *unstructured.Unstructured, error) {
340 if len(u.GetManagedFields()) > 0 {
341
342 return u, nil
343 }
344
345
346
347 patchSkeleton := &unstructured.Unstructured{}
348 patchSkeleton.SetGroupVersionKind(u.GroupVersionKind())
349 patchSkeleton.SetName(u.GetName())
350 patchSkeleton.SetNamespace(u.GetNamespace())
351
352 patchU := patchSkeleton.DeepCopy()
353 patchU.SetAnnotations(map[string]string{SupportsSSAAnnotation: "true"})
354 if err := c.Patch(ctx, patchU, client.Apply, client.FieldOwner(SupportsSSAManager)); err != nil {
355 if strings.Contains(err.Error(), string(types.MergePatchType)) {
356
357
358 return u, nil
359 }
360 return nil, fmt.Errorf("error patching SSA metadata annotation: %w", err)
361 }
362
363
364 patchU = patchSkeleton.DeepCopy()
365 if err := c.Patch(ctx, patchU, client.Apply, client.FieldOwner(SupportsSSAManager)); err != nil {
366 return nil, fmt.Errorf("error removing SSA metadata annotation: %w", err)
367 }
368 return patchU, nil
369 }
370
371
372
373
374
375 func KindWithoutServicePrefix(gvk schema.GroupVersionKind) string {
376 switch gvk.Kind {
377 case "Project", "Folder", "Organization", "BillingAccount":
378
379 return gvk.Kind
380 default:
381 serviceInLowerCase := strings.TrimSuffix(gvk.Group, "."+CNRMGroup)
382 kindInLowerCase := strings.ToLower(gvk.Kind)
383 if !strings.HasPrefix(kindInLowerCase, serviceInLowerCase) {
384 panic(fmt.Errorf("kind %v unexpectedly does not begin with its service name", gvk.Kind))
385 }
386 return gvk.Kind[len(serviceInLowerCase):]
387 }
388 }
389
View as plain text