1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package webhook
16
17 import (
18 "context"
19 "fmt"
20 "net/http"
21
22 corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
23 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl"
24 dclcontainer "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/extension/container"
25 dclmetadata "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/metadata"
26 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/schema/dclschemaloader"
27 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
28 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/krmtotf"
29 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
30
31 corev1 "k8s.io/api/core/v1"
32 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
33 apimachinerytypes "k8s.io/apimachinery/pkg/types"
34 "k8s.io/klog/v2"
35 "sigs.k8s.io/controller-runtime/pkg/client"
36 "sigs.k8s.io/controller-runtime/pkg/runtime/inject"
37 "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
38 )
39
40 type containerAnnotationHandler struct {
41 client client.Client
42 dclSchemaLoader dclschemaloader.DCLSchemaLoader
43 serviceMetadataLoader dclmetadata.ServiceMetadataLoader
44 smLoader *servicemappingloader.ServiceMappingLoader
45 }
46
47 func NewContainerAnnotationHandler(smLoader *servicemappingloader.ServiceMappingLoader, dclSchemaLoader dclschemaloader.DCLSchemaLoader, serviceMetadataLoader dclmetadata.ServiceMetadataLoader) *containerAnnotationHandler {
48 return &containerAnnotationHandler{
49 smLoader: smLoader,
50 serviceMetadataLoader: serviceMetadataLoader,
51 dclSchemaLoader: dclSchemaLoader,
52 }
53 }
54
55
56 var _ inject.Client = &containerAnnotationHandler{}
57
58
59 func (a *containerAnnotationHandler) InjectClient(c client.Client) error {
60 a.client = c
61 return nil
62 }
63
64 func (a *containerAnnotationHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
65 deserializer := codecs.UniversalDeserializer()
66 obj := &unstructured.Unstructured{}
67 if _, _, err := deserializer.Decode(req.AdmissionRequest.Object.Raw, nil, obj); err != nil {
68 klog.Error(err)
69 return admission.Errored(http.StatusBadRequest,
70 fmt.Errorf("error decoding object: %v", err))
71 }
72 ns := &corev1.Namespace{}
73 if err := a.client.Get(ctx, apimachinerytypes.NamespacedName{Name: obj.GetNamespace()}, ns); err != nil {
74 return admission.Errored(http.StatusInternalServerError,
75 fmt.Errorf("error getting Namespace %v: %v", obj.GetNamespace(), err))
76 }
77 if dclmetadata.IsDCLBasedResourceKind(obj.GroupVersionKind(), a.serviceMetadataLoader) {
78 return handleContainerAnnotationsForDCLBasedResources(obj, ns, a.dclSchemaLoader, a.serviceMetadataLoader)
79 }
80 return handleContainerAnnotationsForTFBasedResources(obj, ns, a.smLoader)
81 }
82
83 func handleContainerAnnotationsForDCLBasedResources(obj *unstructured.Unstructured, ns *corev1.Namespace, dclSchemaLoader dclschemaloader.DCLSchemaLoader, serviceMetadataLoader dclmetadata.ServiceMetadataLoader) admission.Response {
84 gvk := obj.GroupVersionKind()
85 r, found := serviceMetadataLoader.GetResourceWithGVK(gvk)
86 if !found {
87 return admission.Errored(http.StatusInternalServerError,
88 fmt.Errorf("ServiceMetadata for resource with GroupVersionKind %v not found", gvk))
89 }
90 containers, err := dclcontainer.GetContainersForGVK(gvk, serviceMetadataLoader, dclSchemaLoader)
91 if err != nil {
92 return admission.Errored(http.StatusInternalServerError,
93 fmt.Errorf("error getting containers supported by GroupVersionKind %v: %v", gvk, err))
94 }
95
96
97
98 if !r.SupportsHierarchicalReferences {
99 return setDefaultContainerAnnotation(obj, ns, containers)
100 }
101
102 hierarchicalRefs, err := dcl.GetHierarchicalReferencesForGVK(gvk, serviceMetadataLoader, dclSchemaLoader)
103 if err != nil {
104 return admission.Errored(http.StatusInternalServerError,
105 fmt.Errorf("error getting hierarchical references supported by GroupVersionKind %v: %v", gvk, err))
106 }
107 return setDefaultHierarchicalReference(obj, ns, hierarchicalRefs, containers)
108 }
109
110 func handleContainerAnnotationsForTFBasedResources(obj *unstructured.Unstructured, ns *corev1.Namespace, smLoader *servicemappingloader.ServiceMappingLoader) admission.Response {
111 rc, err := smLoader.GetResourceConfig(obj)
112 if err != nil {
113 return admission.Errored(http.StatusBadRequest,
114 fmt.Errorf("error getting ResourceConfig for kind %v: %v", obj.GetKind(), err))
115 }
116
117
118
119 if !krmtotf.SupportsHierarchicalReferences(rc) {
120 return setDefaultContainerAnnotation(obj, ns, rc.Containers)
121 }
122 return setDefaultHierarchicalReference(obj, ns, rc.HierarchicalReferences, rc.Containers)
123 }
124
125 func setDefaultContainerAnnotation(obj *unstructured.Unstructured, ns *corev1.Namespace, containers []corekccv1alpha1.Container) admission.Response {
126 newObj := obj.DeepCopy()
127 if err := k8s.SetDefaultContainerAnnotation(newObj, ns, containers); err != nil {
128 return admission.Errored(http.StatusBadRequest, fmt.Errorf("error setting container annotation: %v", err))
129 }
130 return constructPatchResponse(obj, newObj)
131 }
132
133 func setDefaultHierarchicalReference(obj *unstructured.Unstructured, ns *corev1.Namespace, hierarchicalRefs []corekccv1alpha1.HierarchicalReference, containers []corekccv1alpha1.Container) admission.Response {
134 resource, err := k8s.NewResource(obj)
135 if err != nil {
136 return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error converting object to k8s resource: %v", err))
137 }
138 if err := k8s.SetDefaultHierarchicalReference(resource, ns, hierarchicalRefs, containers); err != nil {
139 return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error setting hierarchical reference: %v", err))
140 }
141 newObj, err := resource.MarshalAsUnstructured()
142 if err != nil {
143 return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error marshalling k8s resource to unstructured: %v", err))
144 }
145 return constructPatchResponse(obj, newObj)
146 }
147
148 func constructPatchResponse(obj, newObj *unstructured.Unstructured) admission.Response {
149 objRaw, err := obj.MarshalJSON()
150 if err != nil {
151 return admission.Errored(http.StatusInternalServerError,
152 fmt.Errorf("error marshaling object as JSON: %w", err))
153 }
154 newObjRaw, err := newObj.MarshalJSON()
155 if err != nil {
156 return admission.Errored(http.StatusInternalServerError,
157 fmt.Errorf("error marshaling new object as JSON: %w", err))
158 }
159 return admission.PatchResponseFromRaw(objRaw, newObjRaw)
160 }
161
View as plain text