github.com/mkimuram/operator-sdk@v0.7.1-0.20190410172100-52ad33a4bda0/pkg/test/framework.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 test 16 17 import ( 18 goctx "context" 19 "fmt" 20 "net" 21 "os" 22 "sync" 23 "time" 24 25 // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 26 _ "k8s.io/client-go/plugin/pkg/client/auth" 27 28 k8sInternal "github.com/operator-framework/operator-sdk/internal/util/k8sutil" 29 30 extscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/runtime/serializer" 33 "k8s.io/apimachinery/pkg/util/wait" 34 "k8s.io/client-go/discovery/cached" 35 "k8s.io/client-go/kubernetes" 36 cgoscheme "k8s.io/client-go/kubernetes/scheme" 37 "k8s.io/client-go/rest" 38 "k8s.io/client-go/restmapper" 39 dynclient "sigs.k8s.io/controller-runtime/pkg/client" 40 ) 41 42 var ( 43 // Global framework struct 44 Global *Framework 45 // mutex for AddToFrameworkScheme 46 mutex = sync.Mutex{} 47 // whether to run tests in a single namespace 48 singleNamespace *bool 49 // decoder used by createFromYaml 50 dynamicDecoder runtime.Decoder 51 // restMapper for the dynamic client 52 restMapper *restmapper.DeferredDiscoveryRESTMapper 53 ) 54 55 type Framework struct { 56 Client *frameworkClient 57 KubeConfig *rest.Config 58 KubeClient kubernetes.Interface 59 Scheme *runtime.Scheme 60 NamespacedManPath *string 61 Namespace string 62 LocalOperator bool 63 } 64 65 func setup(kubeconfigPath, namespacedManPath *string, localOperator bool) error { 66 namespace := "" 67 if *singleNamespace { 68 namespace = os.Getenv(TestNamespaceEnv) 69 } 70 var err error 71 var kubeconfig *rest.Config 72 if *kubeconfigPath == "incluster" { 73 // Work around https://github.com/kubernetes/kubernetes/issues/40973 74 if len(os.Getenv("KUBERNETES_SERVICE_HOST")) == 0 { 75 addrs, err := net.LookupHost("kubernetes.default.svc") 76 if err != nil { 77 return fmt.Errorf("failed to get service host: %v", err) 78 } 79 if err := os.Setenv("KUBERNETES_SERVICE_HOST", addrs[0]); err != nil { 80 return fmt.Errorf("failed to set kubernetes host env var: (%v)", err) 81 } 82 } 83 if len(os.Getenv("KUBERNETES_SERVICE_PORT")) == 0 { 84 if err := os.Setenv("KUBERNETES_SERVICE_PORT", "443"); err != nil { 85 return fmt.Errorf("failed to set kubernetes port env var: (%v)", err) 86 } 87 } 88 kubeconfig, err = rest.InClusterConfig() 89 *singleNamespace = true 90 namespace = os.Getenv(TestNamespaceEnv) 91 if len(namespace) == 0 { 92 return fmt.Errorf("test namespace env not set") 93 } 94 } else { 95 var kcNamespace string 96 kubeconfig, kcNamespace, err = k8sInternal.GetKubeconfigAndNamespace(*kubeconfigPath) 97 if *singleNamespace && namespace == "" { 98 namespace = kcNamespace 99 } 100 } 101 if err != nil { 102 return fmt.Errorf("failed to build the kubeconfig: %v", err) 103 } 104 kubeclient, err := kubernetes.NewForConfig(kubeconfig) 105 if err != nil { 106 return fmt.Errorf("failed to build the kubeclient: %v", err) 107 } 108 scheme := runtime.NewScheme() 109 if err := cgoscheme.AddToScheme(scheme); err != nil { 110 return fmt.Errorf("failed to add cgo scheme to runtime scheme: (%v)", err) 111 } 112 if err := extscheme.AddToScheme(scheme); err != nil { 113 return fmt.Errorf("failed to add api extensions scheme to runtime scheme: (%v)", err) 114 } 115 cachedDiscoveryClient := cached.NewMemCacheClient(kubeclient.Discovery()) 116 restMapper = restmapper.NewDeferredDiscoveryRESTMapper(cachedDiscoveryClient) 117 restMapper.Reset() 118 dynClient, err := dynclient.New(kubeconfig, dynclient.Options{Scheme: scheme, Mapper: restMapper}) 119 if err != nil { 120 return fmt.Errorf("failed to build the dynamic client: %v", err) 121 } 122 dynamicDecoder = serializer.NewCodecFactory(scheme).UniversalDeserializer() 123 Global = &Framework{ 124 Client: &frameworkClient{Client: dynClient}, 125 KubeConfig: kubeconfig, 126 KubeClient: kubeclient, 127 Scheme: scheme, 128 NamespacedManPath: namespacedManPath, 129 Namespace: namespace, 130 LocalOperator: localOperator, 131 } 132 return nil 133 } 134 135 type addToSchemeFunc func(*runtime.Scheme) error 136 137 // AddToFrameworkScheme allows users to add the scheme for their custom resources 138 // to the framework's scheme for use with the dynamic client. The user provides 139 // the addToScheme function (located in the register.go file of their operator 140 // project) and the List struct for their custom resource. For example, for a 141 // memcached operator, the list stuct may look like: 142 // &MemcachedList{ 143 // TypeMeta: metav1.TypeMeta{ 144 // Kind: "Memcached", 145 // APIVersion: "cache.example.com/v1alpha1", 146 // }, 147 // } 148 // The List object is needed because the CRD has not always been fully registered 149 // by the time this function is called. If the CRD takes more than 5 seconds to 150 // become ready, this function throws an error 151 func AddToFrameworkScheme(addToScheme addToSchemeFunc, obj runtime.Object) error { 152 mutex.Lock() 153 defer mutex.Unlock() 154 err := addToScheme(Global.Scheme) 155 if err != nil { 156 return err 157 } 158 restMapper.Reset() 159 dynClient, err := dynclient.New(Global.KubeConfig, dynclient.Options{Scheme: Global.Scheme, Mapper: restMapper}) 160 if err != nil { 161 return fmt.Errorf("failed to initialize new dynamic client: (%v)", err) 162 } 163 err = wait.PollImmediate(time.Second, time.Second*10, func() (done bool, err error) { 164 if *singleNamespace { 165 err = dynClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: Global.Namespace}, obj) 166 } else { 167 err = dynClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: "default"}, obj) 168 } 169 if err != nil { 170 restMapper.Reset() 171 return false, nil 172 } 173 Global.Client = &frameworkClient{Client: dynClient} 174 return true, nil 175 }) 176 if err != nil { 177 return fmt.Errorf("failed to build the dynamic client: %v", err) 178 } 179 dynamicDecoder = serializer.NewCodecFactory(Global.Scheme).UniversalDeserializer() 180 return nil 181 }