...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/webhook/container_annotation_handler.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/pkg/webhook

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    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  // containerAnnotationHandler implements inject.Client.
    56  var _ inject.Client = &containerAnnotationHandler{}
    57  
    58  // InjectClient injects the client into the containerAnnotationHandler
    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  	// TODO(b/186159460): Delete this if-block once all resources support
    97  	// hierarchical references.
    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  	// TODO(b/193177782): Delete this if-block once all resources support
   118  	// hierarchical references.
   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