github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/webhooks/appconfig_defaulter.go (about)

     1  // Copyright (c) 2020, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package webhooks
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"net/http"
    10  
    11  	oamv1 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
    12  	"github.com/verrazzano/verrazzano/application-operator/metricsexporter"
    13  	vzlog "github.com/verrazzano/verrazzano/pkg/log"
    14  	"go.uber.org/zap"
    15  	istioversionedclient "istio.io/client-go/pkg/clientset/versioned"
    16  	v1 "k8s.io/api/admission/v1"
    17  	"k8s.io/client-go/kubernetes"
    18  	"sigs.k8s.io/controller-runtime/pkg/client"
    19  	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    20  )
    21  
    22  // AppConfigDefaulterPath specifies the path of AppConfigDefaulter
    23  const AppConfigDefaulterPath = "/appconfig-defaulter"
    24  
    25  // +kubebuilder:webhook:verbs=create;update,path=/appconfig-defaulter,mutating=true,failurePolicy=fail,groups=core.oam.dev,resources=ApplicationConfigurations,versions=v1alpha2,name=appconfig-defaulter.kb.io
    26  
    27  // AppConfigWebhook uses a list of AppConfigDefaulters to supply appconfig default values
    28  type AppConfigWebhook struct {
    29  	decoder     *admission.Decoder
    30  	Client      client.Client
    31  	KubeClient  kubernetes.Interface
    32  	IstioClient istioversionedclient.Interface
    33  	Defaulters  []AppConfigDefaulter
    34  }
    35  
    36  // AppConfigDefaulter supplies appconfig default values
    37  type AppConfigDefaulter interface {
    38  	Default(appConfig *oamv1.ApplicationConfiguration, dryRun bool, log *zap.SugaredLogger) error
    39  	Cleanup(appConfig *oamv1.ApplicationConfiguration, dryRun bool, log *zap.SugaredLogger) error
    40  }
    41  
    42  // InjectDecoder injects admission.Decoder
    43  func (a *AppConfigWebhook) InjectDecoder(d *admission.Decoder) error {
    44  	a.decoder = d
    45  	return nil
    46  }
    47  
    48  var appconfigMarshalFunc = json.Marshal
    49  
    50  // Handle handles appconfig mutate Request
    51  func (a *AppConfigWebhook) Handle(ctx context.Context, req admission.Request) admission.Response {
    52  	counterMetricObject, errorCounterMetricObject, handleDurationMetricObject, zapLogForMetrics, err := metricsexporter.ExposeControllerMetrics("AppConfigDefaulter", metricsexporter.AppconfigHandleCounter, metricsexporter.AppconfigHandleError, metricsexporter.AppconfigHandleDuration)
    53  	if err != nil {
    54  		return admission.Response{}
    55  	}
    56  	handleDurationMetricObject.TimerStart()
    57  	defer handleDurationMetricObject.TimerStop()
    58  
    59  	log := zap.S().With(vzlog.FieldResourceNamespace, req.Namespace, vzlog.FieldResourceName, req.Name, vzlog.FieldWebhook, "appconfig-defaulter")
    60  
    61  	dryRun := req.DryRun != nil && *req.DryRun
    62  	appConfig := &oamv1.ApplicationConfiguration{}
    63  	//This json can be used to curl -X POST the webhook endpoint
    64  	log.Debugw("admission.Request", "request", req)
    65  	log.Infow("Handling appconfig default",
    66  		"requestOperation", req.Operation, "requestName", req.Name)
    67  
    68  	// if the operation is Delete then decode the old object and call the defaulter to cleanup any app conf defaults
    69  	if req.Operation == v1.Delete {
    70  		err := a.decoder.DecodeRaw(req.OldObject, appConfig)
    71  		if err != nil {
    72  			return admission.Errored(http.StatusBadRequest, err)
    73  		}
    74  		for _, defaulter := range a.Defaulters {
    75  			err = defaulter.Cleanup(appConfig, dryRun, log)
    76  			if err != nil {
    77  				errorCounterMetricObject.Inc(zapLogForMetrics, err)
    78  				return admission.Errored(http.StatusInternalServerError, err)
    79  			}
    80  		}
    81  		if !dryRun {
    82  			err = a.cleanupAppConfig(appConfig, log)
    83  			if err != nil {
    84  				errorCounterMetricObject.Inc(zapLogForMetrics, err)
    85  				log.Errorf("Failed cleaning up app config %s: %v", req.Name, err)
    86  			}
    87  		}
    88  		return admission.Allowed("cleaned up appconfig default")
    89  	}
    90  
    91  	err = a.decoder.Decode(req, appConfig)
    92  	if err != nil {
    93  		return admission.Errored(http.StatusBadRequest, err)
    94  	}
    95  	//mutate the fields in appConfig
    96  	for _, defaulter := range a.Defaulters {
    97  		err = defaulter.Default(appConfig, dryRun, log)
    98  		if err != nil {
    99  			errorCounterMetricObject.Inc(zapLogForMetrics, err)
   100  			return admission.Errored(http.StatusInternalServerError, err)
   101  		}
   102  	}
   103  	marshaledAppConfig, err := appconfigMarshalFunc(appConfig)
   104  	if err != nil {
   105  		errorCounterMetricObject.Inc(zapLogForMetrics, err)
   106  		return admission.Errored(http.StatusInternalServerError, err)
   107  	}
   108  	counterMetricObject.Inc(zapLogForMetrics, err)
   109  	return admission.PatchResponseFromRaw(req.Object.Raw, marshaledAppConfig)
   110  }
   111  
   112  // cleanupAppConfig cleans up the generated certificates and secrets associated with the given app config
   113  func (a *AppConfigWebhook) cleanupAppConfig(appConfig *oamv1.ApplicationConfiguration, log *zap.SugaredLogger) (err error) {
   114  	// Fixup Istio Authorization policies within a project
   115  	ap := &AuthorizationPolicy{
   116  		Client:      a.Client,
   117  		KubeClient:  a.KubeClient,
   118  		IstioClient: a.IstioClient,
   119  	}
   120  	return ap.cleanupAuthorizationPoliciesForProjects(appConfig.Namespace, appConfig.Name, log)
   121  }