github.com/lablabs/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 }