github.imxd.top/operator-framework/operator-sdk@v0.8.2/pkg/ansible/proxy/proxy.go (about)

     1  // Copyright 2018 The Operator-SDK Authors
     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 proxy
    16  
    17  // This file contains this project's custom code, as opposed to kubectl.go
    18  // which contains code retrieved from the kubernetes project.
    19  
    20  import (
    21  	"bytes"
    22  	"encoding/base64"
    23  	"encoding/json"
    24  	"errors"
    25  	"fmt"
    26  	"io/ioutil"
    27  	"net/http"
    28  
    29  	"github.com/operator-framework/operator-sdk/pkg/ansible/proxy/controllermap"
    30  	"github.com/operator-framework/operator-sdk/pkg/ansible/proxy/kubeconfig"
    31  	osdkHandler "github.com/operator-framework/operator-sdk/pkg/handler"
    32  	"k8s.io/apimachinery/pkg/api/meta"
    33  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    34  	"k8s.io/apimachinery/pkg/runtime/schema"
    35  	"k8s.io/client-go/rest"
    36  	"sigs.k8s.io/controller-runtime/pkg/cache"
    37  	"sigs.k8s.io/controller-runtime/pkg/handler"
    38  	"sigs.k8s.io/controller-runtime/pkg/source"
    39  )
    40  
    41  // RequestLogHandler - log the requests that come through the proxy.
    42  func RequestLogHandler(h http.Handler) http.Handler {
    43  	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    44  		// read body
    45  		body, err := ioutil.ReadAll(req.Body)
    46  		if err != nil {
    47  			log.Error(err, "Could not read request body")
    48  		}
    49  		// fix body
    50  		req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
    51  		log.Info("Request Info", "method", req.Method, "uri", req.RequestURI, "body", string(body))
    52  		// Removing the authorization so that the proxy can set the correct authorization.
    53  		req.Header.Del("Authorization")
    54  		h.ServeHTTP(w, req)
    55  	})
    56  }
    57  
    58  // HandlerChain will be used for users to pass defined handlers to the proxy.
    59  // The hander chain will be run after InjectingOwnerReference if it is added
    60  // and before the proxy handler.
    61  type HandlerChain func(http.Handler) http.Handler
    62  
    63  // Options will be used by the user to specify the desired details
    64  // for the proxy.
    65  type Options struct {
    66  	Address           string
    67  	Port              int
    68  	Handler           HandlerChain
    69  	OwnerInjection    bool
    70  	LogRequests       bool
    71  	KubeConfig        *rest.Config
    72  	Cache             cache.Cache
    73  	RESTMapper        meta.RESTMapper
    74  	ControllerMap     *controllermap.ControllerMap
    75  	WatchedNamespaces []string
    76  	DisableCache      bool
    77  }
    78  
    79  // Run will start a proxy server in a go routine that returns on the error
    80  // channel if something is not correct on startup. Run will not return until
    81  // the network socket is listening.
    82  func Run(done chan error, o Options) error {
    83  	server, err := newServer("/", o.KubeConfig)
    84  	if err != nil {
    85  		return err
    86  	}
    87  	if o.Handler != nil {
    88  		server.Handler = o.Handler(server.Handler)
    89  	}
    90  	if o.ControllerMap == nil {
    91  		return fmt.Errorf("failed to get controller map from options")
    92  	}
    93  	if o.WatchedNamespaces == nil {
    94  		return fmt.Errorf("failed to get list of watched namespaces from options")
    95  	}
    96  
    97  	watchedNamespaceMap := make(map[string]interface{})
    98  	// Convert string list to map
    99  	for _, ns := range o.WatchedNamespaces {
   100  		watchedNamespaceMap[ns] = nil
   101  	}
   102  
   103  	if o.Cache == nil && !o.DisableCache {
   104  		// Need to initialize cache since we don't have one
   105  		log.Info("Initializing and starting informer cache...")
   106  		informerCache, err := cache.New(o.KubeConfig, cache.Options{})
   107  		if err != nil {
   108  			return err
   109  		}
   110  		stop := make(chan struct{})
   111  		go func() {
   112  			if err := informerCache.Start(stop); err != nil {
   113  				log.Error(err, "Failed to start informer cache")
   114  			}
   115  			defer close(stop)
   116  		}()
   117  		log.Info("Waiting for cache to sync...")
   118  		synced := informerCache.WaitForCacheSync(stop)
   119  		if !synced {
   120  			return fmt.Errorf("failed to sync cache")
   121  		}
   122  		log.Info("Cache sync was successful")
   123  		o.Cache = informerCache
   124  	}
   125  
   126  	// Remove the authorization header so the proxy can correctly inject the header.
   127  	server.Handler = removeAuthorizationHeader(server.Handler)
   128  
   129  	if o.OwnerInjection {
   130  		server.Handler = &injectOwnerReferenceHandler{
   131  			next:              server.Handler,
   132  			cMap:              o.ControllerMap,
   133  			restMapper:        o.RESTMapper,
   134  			watchedNamespaces: watchedNamespaceMap,
   135  		}
   136  	} else {
   137  		log.Info("Warning: injection of owner references and dependent watches is turned off")
   138  	}
   139  	if o.LogRequests {
   140  		server.Handler = RequestLogHandler(server.Handler)
   141  	}
   142  	if !o.DisableCache {
   143  		server.Handler = &cacheResponseHandler{
   144  			next:              server.Handler,
   145  			informerCache:     o.Cache,
   146  			restMapper:        o.RESTMapper,
   147  			watchedNamespaces: watchedNamespaceMap,
   148  			cMap:              o.ControllerMap,
   149  			injectOwnerRef:    o.OwnerInjection,
   150  		}
   151  	}
   152  
   153  	l, err := server.Listen(o.Address, o.Port)
   154  	if err != nil {
   155  		return err
   156  	}
   157  	go func() {
   158  		log.Info("Starting to serve", "Address", l.Addr().String())
   159  		done <- server.ServeOnListener(l)
   160  	}()
   161  	return nil
   162  }
   163  
   164  // Helper function used by cache response and owner injection
   165  func addWatchToController(owner kubeconfig.NamespacedOwnerReference, cMap *controllermap.ControllerMap, resource *unstructured.Unstructured, restMapper meta.RESTMapper, useOwnerRef bool) error {
   166  	dataMapping, err := restMapper.RESTMapping(resource.GroupVersionKind().GroupKind(), resource.GroupVersionKind().Version)
   167  	if err != nil {
   168  		m := fmt.Sprintf("Could not get rest mapping for: %v", resource.GroupVersionKind())
   169  		log.Error(err, m)
   170  		return err
   171  
   172  	}
   173  	ownerGV, err := schema.ParseGroupVersion(owner.APIVersion)
   174  	if err != nil {
   175  		m := fmt.Sprintf("could not get broup version for: %v", owner)
   176  		log.Error(err, m)
   177  		return err
   178  	}
   179  	ownerMapping, err := restMapper.RESTMapping(schema.GroupKind{Kind: owner.Kind, Group: ownerGV.Group}, ownerGV.Version)
   180  	if err != nil {
   181  		m := fmt.Sprintf("could not get rest mapping for: %v", owner)
   182  		log.Error(err, m)
   183  		return err
   184  	}
   185  
   186  	dataNamespaceScoped := dataMapping.Scope.Name() != meta.RESTScopeNameRoot
   187  	contents, ok := cMap.Get(ownerMapping.GroupVersionKind)
   188  	if !ok {
   189  		return errors.New("failed to find controller in map")
   190  	}
   191  	owMap := contents.OwnerWatchMap
   192  	awMap := contents.AnnotationWatchMap
   193  	u := &unstructured.Unstructured{}
   194  	u.SetGroupVersionKind(ownerMapping.GroupVersionKind)
   195  	// Add a watch to controller
   196  	if contents.WatchDependentResources {
   197  		// Store watch in map
   198  		// Use EnqueueRequestForOwner unless user has configured watching cluster scoped resources and we have to
   199  		switch {
   200  		case useOwnerRef:
   201  			_, exists := owMap.Get(resource.GroupVersionKind())
   202  			// If already watching resource no need to add a new watch
   203  			if exists {
   204  				return nil
   205  			}
   206  
   207  			owMap.Store(resource.GroupVersionKind())
   208  			log.Info("Watching child resource", "kind", resource.GroupVersionKind(), "enqueue_kind", u.GroupVersionKind())
   209  			// Store watch in map
   210  			err := contents.Controller.Watch(&source.Kind{Type: resource}, &handler.EnqueueRequestForOwner{OwnerType: u})
   211  			if err != nil {
   212  				return err
   213  			}
   214  		case (!useOwnerRef && dataNamespaceScoped) || contents.WatchClusterScopedResources:
   215  			_, exists := awMap.Get(resource.GroupVersionKind())
   216  			// If already watching resource no need to add a new watch
   217  			if exists {
   218  				return nil
   219  			}
   220  			awMap.Store(resource.GroupVersionKind())
   221  			typeString := fmt.Sprintf("%v.%v", owner.Kind, ownerGV.Group)
   222  			log.Info("Watching child resource", "kind", resource.GroupVersionKind(), "enqueue_annotation_type", typeString)
   223  			err = contents.Controller.Watch(&source.Kind{Type: resource}, &osdkHandler.EnqueueRequestForAnnotation{Type: typeString})
   224  			if err != nil {
   225  				return err
   226  			}
   227  		}
   228  	}
   229  	return nil
   230  }
   231  
   232  func removeAuthorizationHeader(h http.Handler) http.Handler {
   233  	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   234  		req.Header.Del("Authorization")
   235  		h.ServeHTTP(w, req)
   236  	})
   237  }
   238  
   239  // Helper function used by recovering dependent watches and owner ref injection.
   240  func getRequestOwnerRef(req *http.Request) (kubeconfig.NamespacedOwnerReference, error) {
   241  	owner := kubeconfig.NamespacedOwnerReference{}
   242  	user, _, ok := req.BasicAuth()
   243  	if !ok {
   244  		return owner, errors.New("basic auth header not found")
   245  	}
   246  	authString, err := base64.StdEncoding.DecodeString(user)
   247  	if err != nil {
   248  		m := "Could not base64 decode username"
   249  		log.Error(err, m)
   250  		return owner, err
   251  	}
   252  	// Set owner to NamespacedOwnerReference, which has metav1.OwnerReference
   253  	// as a subset along with the Namespace of the owner. Please see the
   254  	// kubeconfig.NamespacedOwnerReference type for more information. The
   255  	// namespace is required when creating the reconcile requests.
   256  	json.Unmarshal(authString, &owner)
   257  	if err := json.Unmarshal(authString, &owner); err != nil {
   258  		m := "Could not unmarshal auth string"
   259  		log.Error(err, m)
   260  		return owner, err
   261  	}
   262  	return owner, err
   263  }