...

Source file src/github.com/emissary-ingress/emissary/v3/cmd/apiext/inject.go

Documentation: github.com/emissary-ingress/emissary/v3/cmd/apiext

     1  package apiext
     2  
     3  import (
     4  	"context"
     5  	"reflect"
     6  
     7  	// k8s types
     8  	k8sTypesCoreV1 "k8s.io/api/core/v1"
     9  	k8sTypesAPIExtV1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    10  	k8sTypesMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    11  
    12  	// k8s clients
    13  	k8sClientAPIExtV1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
    14  
    15  	// k8s utils
    16  	k8sErrors "k8s.io/apimachinery/pkg/api/errors"
    17  	k8sRuntime "k8s.io/apimachinery/pkg/runtime"
    18  	k8sSchema "k8s.io/apimachinery/pkg/runtime/schema"
    19  	k8sWatch "k8s.io/apimachinery/pkg/watch"
    20  	"k8s.io/client-go/rest"
    21  
    22  	"github.com/datawire/dlib/derror"
    23  	"github.com/datawire/dlib/dlog"
    24  )
    25  
    26  // ConfigureCRDs uses 'restConfig' to look at all CustomResourceDefinitions that are mentioned in
    27  // 'scheme', and adjusts each of their .spec.conversion.webhook.clientConfig.caBundle to match the
    28  // "tls.crt" field in 'caSecret'.
    29  func ConfigureCRDs(
    30  	ctx context.Context,
    31  	restConfig *rest.Config,
    32  	serviceName, serviceNamespace string,
    33  	caSecret *k8sTypesCoreV1.Secret,
    34  	scheme *k8sRuntime.Scheme,
    35  ) error {
    36  	ctx, cancel := context.WithCancel(ctx)
    37  	defer func() {
    38  		cancel()
    39  	}()
    40  	apiExtClient, err := k8sClientAPIExtV1.NewForConfig(restConfig)
    41  	if err != nil {
    42  		return err
    43  	}
    44  	crdsClient := apiExtClient.CustomResourceDefinitions()
    45  
    46  	crdList, err := crdsClient.List(ctx, k8sTypesMetaV1.ListOptions{})
    47  	if err != nil {
    48  		return err
    49  	}
    50  
    51  	webhookPath := pathWebhooksCrdConvert // because pathWebhooksCrdConvert is a 'const' and you can't take the address of a const
    52  	webhookPort := int32(443)
    53  	conversionConfig := &k8sTypesAPIExtV1.CustomResourceConversion{
    54  		Strategy: k8sTypesAPIExtV1.WebhookConverter,
    55  		Webhook: &k8sTypesAPIExtV1.WebhookConversion{
    56  			ClientConfig: &k8sTypesAPIExtV1.WebhookClientConfig{
    57  				Service: &k8sTypesAPIExtV1.ServiceReference{
    58  					Name:      serviceName,
    59  					Namespace: serviceNamespace,
    60  					Port:      &webhookPort,
    61  					Path:      &webhookPath,
    62  				},
    63  				CABundle: caSecret.Data[k8sTypesCoreV1.TLSCertKey],
    64  			},
    65  			// Which versions of the conversion API our webhook supports.  Since we use
    66  			// sigs.k8s.io/controller-runtime/pkg/webhook/conversion to implement the
    67  			// webhook this list should be kept in-sync with what that package supports.
    68  			ConversionReviewVersions: []string{
    69  				"v1beta1",
    70  			},
    71  		},
    72  	}
    73  
    74  	var errs derror.MultiError
    75  	for _, crd := range crdList.Items {
    76  		if err := updateCRD(ctx, scheme, crdsClient, crd, conversionConfig); err != nil {
    77  			errs = append(errs, err)
    78  		}
    79  	}
    80  	if len(errs) > 0 {
    81  		return errs
    82  	}
    83  
    84  	// Watching for further changes is important so that re-applications of crds.yaml don't
    85  	// break things.
    86  	dlog.Infoln(ctx, "Initial configuration complete, now watching for further changes...")
    87  
    88  	crdWatch, err := crdsClient.Watch(ctx, k8sTypesMetaV1.ListOptions{
    89  		ResourceVersion: crdList.GetResourceVersion(),
    90  	})
    91  	if err != nil {
    92  		return err
    93  	}
    94  	go func() { // Don't bother with dgroup because crdWatch.ResultChan() won't close until this goroutine returns.
    95  		<-ctx.Done()
    96  		crdWatch.Stop()
    97  	}()
    98  	for event := range crdWatch.ResultChan() {
    99  		switch event.Type {
   100  		case k8sWatch.Added, k8sWatch.Modified:
   101  			crd := *(event.Object.(*k8sTypesAPIExtV1.CustomResourceDefinition))
   102  			if err := updateCRD(ctx, scheme, crdsClient, crd, conversionConfig); err != nil {
   103  				dlog.Errorln(ctx, err)
   104  			}
   105  		}
   106  	}
   107  
   108  	return nil
   109  }
   110  
   111  func updateCRD(
   112  	ctx context.Context,
   113  	scheme *k8sRuntime.Scheme,
   114  	crdsClient k8sClientAPIExtV1.CustomResourceDefinitionInterface,
   115  	crd k8sTypesAPIExtV1.CustomResourceDefinition,
   116  	conversionConfig *k8sTypesAPIExtV1.CustomResourceConversion,
   117  ) error {
   118  	if len(crd.Spec.Versions) < 2 {
   119  		// Nothing to convert.
   120  		dlog.Debugf(ctx, "Skipping %q because it only has one version", crd.ObjectMeta.Name)
   121  		return nil
   122  	}
   123  	if !scheme.Recognizes(k8sSchema.GroupVersionKind{
   124  		Group:   crd.Spec.Group,
   125  		Version: crd.Spec.Versions[0].Name,
   126  		Kind:    crd.Spec.Names.Kind,
   127  	}) {
   128  		// Don't know how to convert.
   129  		dlog.Debugf(ctx, "Skipping %q because it not a recognized type", crd.ObjectMeta.Name)
   130  		return nil
   131  	}
   132  	if reflect.DeepEqual(crd.Spec.Conversion, conversionConfig) {
   133  		// Already done.
   134  		dlog.Infof(ctx, "Skipping %q because it is already configured", crd.ObjectMeta.Name)
   135  		return nil
   136  	}
   137  	dlog.Infof(ctx, "Configuring conversion for %q", crd.ObjectMeta.Name)
   138  	crd.Spec.Conversion = conversionConfig
   139  	_, err := crdsClient.Update(ctx, &crd, k8sTypesMetaV1.UpdateOptions{})
   140  	if err != nil && !k8sErrors.IsConflict(err) {
   141  		return err
   142  	}
   143  	return nil
   144  }
   145  

View as plain text