forked from operator-framework/operator-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
framework.go
156 lines (148 loc) · 5.12 KB
/
framework.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright 2018 The Operator-SDK Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
goctx "context"
"fmt"
"net"
"os"
"sync"
"time"
extscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/discovery/cached"
"k8s.io/client-go/kubernetes"
cgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"k8s.io/client-go/tools/clientcmd"
dynclient "sigs.k8s.io/controller-runtime/pkg/client"
)
var (
// Global framework struct
Global *Framework
// mutex for AddToFrameworkScheme
mutex = sync.Mutex{}
// whether to run tests in a single namespace
singleNamespace *bool
// decoder used by createFromYaml
dynamicDecoder runtime.Decoder
)
type Framework struct {
Client *frameworkClient
KubeConfig *rest.Config
KubeClient kubernetes.Interface
Scheme *runtime.Scheme
NamespacedManPath *string
Namespace string
}
func setup(kubeconfigPath, namespacedManPath *string) error {
var err error
var kubeconfig *rest.Config
if *kubeconfigPath == "incluster" {
// Work around https://github.com/kubernetes/kubernetes/issues/40973
if len(os.Getenv("KUBERNETES_SERVICE_HOST")) == 0 {
addrs, err := net.LookupHost("kubernetes.default.svc")
if err != nil {
return fmt.Errorf("failed to get service host: %v", err)
}
os.Setenv("KUBERNETES_SERVICE_HOST", addrs[0])
}
if len(os.Getenv("KUBERNETES_SERVICE_PORT")) == 0 {
os.Setenv("KUBERNETES_SERVICE_PORT", "443")
}
kubeconfig, err = rest.InClusterConfig()
*singleNamespace = true
} else {
kubeconfig, err = clientcmd.BuildConfigFromFlags("", *kubeconfigPath)
}
if err != nil {
return fmt.Errorf("failed to build the kubeconfig: %v", err)
}
kubeclient, err := kubernetes.NewForConfig(kubeconfig)
if err != nil {
return fmt.Errorf("failed to build the kubeclient: %v", err)
}
scheme := runtime.NewScheme()
cgoscheme.AddToScheme(scheme)
extscheme.AddToScheme(scheme)
dynClient, err := dynclient.New(kubeconfig, dynclient.Options{Scheme: scheme})
if err != nil {
return fmt.Errorf("failed to build the dynamic client: %v", err)
}
dynamicDecoder = serializer.NewCodecFactory(scheme).UniversalDeserializer()
namespace := ""
if *singleNamespace {
namespace = os.Getenv(TestNamespaceEnv)
if len(namespace) == 0 {
return fmt.Errorf("namespace set in %s cannot be empty", TestNamespaceEnv)
}
}
Global = &Framework{
Client: &frameworkClient{Client: dynClient},
KubeConfig: kubeconfig,
KubeClient: kubeclient,
Scheme: scheme,
NamespacedManPath: namespacedManPath,
Namespace: namespace,
}
return nil
}
type addToSchemeFunc func(*runtime.Scheme) error
// AddToFrameworkScheme allows users to add the scheme for their custom resources
// to the framework's scheme for use with the dynamic client. The user provides
// the addToScheme function (located in the register.go file of their operator
// project) and the List struct for their custom resource. For example, for a
// memcached operator, the list stuct may look like:
// &MemcachedList{
// TypeMeta: metav1.TypeMeta{
// Kind: "Memcached",
// APIVersion: "cache.example.com/v1alpha1",
// },
// }
// The List object is needed because the CRD has not always been fully registered
// by the time this function is called. If the CRD takes more than 5 seconds to
// become ready, this function throws an error
func AddToFrameworkScheme(addToScheme addToSchemeFunc, obj runtime.Object) error {
mutex.Lock()
defer mutex.Unlock()
err := addToScheme(Global.Scheme)
if err != nil {
return err
}
cachedDiscoveryClient := cached.NewMemCacheClient(Global.KubeClient.Discovery())
restMapper := restmapper.NewDeferredDiscoveryRESTMapper(cachedDiscoveryClient)
restMapper.Reset()
dynClient, err := dynclient.New(Global.KubeConfig, dynclient.Options{Scheme: Global.Scheme, Mapper: restMapper})
err = wait.PollImmediate(time.Second, time.Second*10, func() (done bool, err error) {
if *singleNamespace {
err = dynClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: Global.Namespace}, obj)
} else {
err = dynClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: "default"}, obj)
}
if err != nil {
restMapper.Reset()
return false, nil
}
Global.Client = &frameworkClient{Client: dynClient}
return true, nil
})
if err != nil {
return fmt.Errorf("failed to build the dynamic client: %v", err)
}
dynamicDecoder = serializer.NewCodecFactory(Global.Scheme).UniversalDeserializer()
return nil
}