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