...

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

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

     1  package apiext
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/tls"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/http/httptest"
    12  
    13  	// k8s utils
    14  	k8sRuntime "k8s.io/apimachinery/pkg/runtime"
    15  	"sigs.k8s.io/controller-runtime/pkg/webhook/conversion"
    16  
    17  	"github.com/datawire/dlib/dhttp"
    18  	"github.com/datawire/dlib/dlog"
    19  )
    20  
    21  const (
    22  	pathWebhooksCrdConvert = "/webhooks/crd-convert"
    23  	pathProbesReady        = "/probes/ready"
    24  	pathProbesLive         = "/probes/live"
    25  )
    26  
    27  // conversionWithLogging is a wrapper around our real conversion method that logs the JSON
    28  // input and output for the conversion request. It's used only when we have debug logging
    29  // enabled.
    30  func conversionWithLogging(wh *conversion.Webhook, w http.ResponseWriter, r *http.Request) {
    31  	// This is a little more obnoxious than you'd think because r.Body is a ReadCloser,
    32  	// not an io.Reader, and because the handler expects to be handed an io.Writer for
    33  	// response. So we need to buffer both directions (obviously, this works partly
    34  	// because we know that requests and responses are fairly small... ish).
    35  	//
    36  	// So, start by reading the request body into a byte array using iotuil.ReadAll
    37  	// That's the easy bit.
    38  	if r.Body == nil {
    39  		dlog.Errorf(r.Context(), "no conversion request provided?")
    40  		w.WriteHeader(http.StatusBadRequest)
    41  		return
    42  	}
    43  
    44  	inputBytes, err := ioutil.ReadAll(r.Body)
    45  
    46  	// This is mirrored from wh.ServeHttp (cf sigs.k8s.io/controller-runtime/pkg/webhook/conversion.go).
    47  	if err != nil {
    48  		dlog.Errorf(r.Context(), "could not read conversion request: %s", err)
    49  		w.WriteHeader(http.StatusBadRequest)
    50  		return
    51  	}
    52  
    53  	// Go ahead and log the input...
    54  	dlog.Debugf(r.Context(), "INPUT: %s", string(inputBytes))
    55  
    56  	// ...then replace the request body with a new io.NopCloser that feeds back
    57  	// the contents of the input buffer to the actual conversion method...
    58  	r.Body = io.NopCloser(bytes.NewBuffer(inputBytes))
    59  
    60  	// ...then use an httptest.ResponseRecorder to capture the output of the real
    61  	// conversion method.
    62  	rec := httptest.NewRecorder()
    63  	wh.ServeHTTP(rec, r)
    64  
    65  	// Log the output...
    66  	dlog.Debugf(r.Context(), "OUTPUT: %s", rec.Body)
    67  
    68  	// ...and then copy the recorded output to the real response.
    69  	for k, v := range rec.Result().Header {
    70  		w.Header()[k] = v
    71  	}
    72  
    73  	w.WriteHeader(rec.Code)
    74  
    75  	// There's kind of nothing we can do if we can't write the body to w, so, uh... do
    76  	// nothing? Should we panic instead??
    77  	//nolint:errcheck
    78  	rec.Body.WriteTo(w)
    79  }
    80  
    81  func ServeHTTPS(ctx context.Context, port int, ca *CA, scheme *k8sRuntime.Scheme) error {
    82  	webhook := &conversion.Webhook{}
    83  	if err := webhook.InjectScheme(scheme); err != nil {
    84  		return err
    85  	}
    86  
    87  	mux := http.NewServeMux()
    88  
    89  	mux.Handle(pathWebhooksCrdConvert, webhook)
    90  
    91  	dlog.Infof(ctx, "Serving HTTPS on port %d", port)
    92  
    93  	// Assume that we'll use the conversion method directly, by using 'mux' for our
    94  	// Handler...
    95  	sc := &dhttp.ServerConfig{
    96  		Handler: mux,
    97  		TLSConfig: &tls.Config{
    98  			GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
    99  				return ca.GenServerCert(ctx, clientHello.ServerName)
   100  			},
   101  		},
   102  	}
   103  
   104  	// ...but if we're in debug mode, switch to using our conversionWithLogging handler
   105  	// instead.
   106  	if LogLevelIsAtLeastDebug() {
   107  		sc.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   108  			conversionWithLogging(webhook, w, r)
   109  		})
   110  	}
   111  
   112  	return sc.ListenAndServeTLS(ctx, fmt.Sprintf(":%d", port), "", "")
   113  }
   114  
   115  func ServeHTTP(ctx context.Context, port int) error {
   116  	mux := http.NewServeMux()
   117  
   118  	mux.Handle(pathProbesReady, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
   119  		_, _ = io.WriteString(w, "Ready!\n")
   120  	}))
   121  	mux.Handle(pathProbesLive, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
   122  		_, _ = io.WriteString(w, "Living!\n")
   123  	}))
   124  
   125  	sc := &dhttp.ServerConfig{
   126  		Handler: mux,
   127  	}
   128  	return sc.ListenAndServe(ctx, fmt.Sprintf(":%d", port))
   129  }
   130  

View as plain text