github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/boskos/crds/client.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package crds 18 19 import ( 20 "flag" 21 "fmt" 22 "os" 23 24 "k8s.io/test-infra/boskos/common" 25 26 apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" 27 apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 28 apierrors "k8s.io/apimachinery/pkg/api/errors" 29 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/runtime/schema" 32 "k8s.io/apimachinery/pkg/runtime/serializer" 33 "k8s.io/client-go/rest" 34 "k8s.io/client-go/tools/clientcmd" 35 ) 36 37 const ( 38 group = "boskos.k8s.io" 39 version = "v1" 40 ) 41 42 // KubernetesClientOptions are flag options used to create a kube client. 43 type KubernetesClientOptions struct { 44 inMemory bool 45 kubeConfig string 46 namespace string 47 } 48 49 // AddFlags adds kube client flags to existing FlagSet. 50 func (o *KubernetesClientOptions) AddFlags(fs *flag.FlagSet) { 51 fs.StringVar(&o.namespace, "namespace", v1.NamespaceDefault, "namespace to install on") 52 fs.StringVar(&o.kubeConfig, "kubeconfig", "", "absolute path to the kubeConfig file") 53 fs.BoolVar(&o.inMemory, "in_memory", false, "Use in memory client instead of CRD") 54 } 55 56 // Validate validates Kubernetes client options. 57 func (o *KubernetesClientOptions) Validate() error { 58 if o.kubeConfig != "" { 59 if _, err := os.Stat(o.kubeConfig); err != nil { 60 return err 61 } 62 } 63 return nil 64 } 65 66 // Client returns a ClientInterface based on the flags provided. 67 func (o *KubernetesClientOptions) Client(t Type) (ClientInterface, error) { 68 if o.inMemory { 69 return newDummyClient(t), nil 70 } 71 return o.newCRDClient(t) 72 } 73 74 // newClientFromFlags creates a CRD rest client from provided flags. 75 func (o *KubernetesClientOptions) newCRDClient(t Type) (*Client, error) { 76 config, scheme, err := createRESTConfig(o.kubeConfig, t) 77 if err != nil { 78 return nil, err 79 } 80 81 if err = registerResource(config, t); err != nil { 82 return nil, err 83 } 84 // creates the client 85 var restClient *rest.RESTClient 86 restClient, err = rest.RESTClientFor(config) 87 if err != nil { 88 return nil, err 89 } 90 rc := Client{cl: restClient, ns: o.namespace, t: t, 91 codec: runtime.NewParameterCodec(scheme)} 92 return &rc, nil 93 } 94 95 // Type defines a Custom Resource Definition (CRD) Type. 96 type Type struct { 97 Kind, ListKind string 98 Singular, Plural string 99 Object Object 100 Collection Collection 101 } 102 103 // Object extends the runtime.Object interface. CRD are just a representation of the actual boskos object 104 // which should implements the common.Item interface. 105 type Object interface { 106 runtime.Object 107 GetName() string 108 FromItem(item common.Item) 109 ToItem() common.Item 110 } 111 112 // Collection is a list of Object interface. 113 type Collection interface { 114 runtime.Object 115 SetItems([]Object) 116 GetItems() []Object 117 } 118 119 // createRESTConfig for cluster API server, pass empty config file for in-cluster 120 func createRESTConfig(kubeconfig string, t Type) (config *rest.Config, types *runtime.Scheme, err error) { 121 if kubeconfig == "" { 122 config, err = rest.InClusterConfig() 123 } else { 124 config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) 125 } 126 127 if err != nil { 128 return 129 } 130 131 version := schema.GroupVersion{ 132 Group: group, 133 Version: version, 134 } 135 136 config.GroupVersion = &version 137 config.APIPath = "/apis" 138 config.ContentType = runtime.ContentTypeJSON 139 140 types = runtime.NewScheme() 141 schemeBuilder := runtime.NewSchemeBuilder( 142 func(scheme *runtime.Scheme) error { 143 scheme.AddKnownTypes(version, t.Object, t.Collection) 144 v1.AddToGroupVersion(scheme, version) 145 return nil 146 }) 147 err = schemeBuilder.AddToScheme(types) 148 config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: serializer.NewCodecFactory(types)} 149 150 return 151 } 152 153 // registerResource sends a request to create CRDs and waits for them to initialize 154 func registerResource(config *rest.Config, t Type) error { 155 c, err := apiextensionsclient.NewForConfig(config) 156 if err != nil { 157 return err 158 } 159 160 crd := &apiextensionsv1beta1.CustomResourceDefinition{ 161 ObjectMeta: v1.ObjectMeta{ 162 Name: fmt.Sprintf("%s.%s", t.Plural, group), 163 }, 164 Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{ 165 Group: group, 166 Scope: apiextensionsv1beta1.NamespaceScoped, 167 Names: apiextensionsv1beta1.CustomResourceDefinitionNames{ 168 Singular: t.Singular, 169 Plural: t.Plural, 170 Kind: t.Kind, 171 ListKind: t.ListKind, 172 }, 173 }, 174 } 175 if _, err := c.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd); err != nil && !apierrors.IsAlreadyExists(err) { 176 return err 177 } 178 return nil 179 } 180 181 // newDummyClient creates a in memory client representation for testing, such that we do not need to use a kubernetes API Server. 182 func newDummyClient(t Type) *dummyClient { 183 c := &dummyClient{ 184 t: t, 185 objects: make(map[string]Object), 186 } 187 return c 188 } 189 190 // ClientInterface is used for testing. 191 type ClientInterface interface { 192 // NewObject instantiates a new object of the type supported by the client 193 NewObject() Object 194 // NewCollection instantiates a new collection of the type supported by the client 195 NewCollection() Collection 196 // Create a new object 197 Create(obj Object) (Object, error) 198 // Update an existing object, fails if object does not exist 199 Update(obj Object) (Object, error) 200 // Delete an existing object, fails if objects does not exist 201 Delete(name string, options *v1.DeleteOptions) error 202 // Get an existing object 203 Get(name string) (Object, error) 204 // LIst existing objects 205 List(opts v1.ListOptions) (Collection, error) 206 } 207 208 // dummyClient is used for testing purposes 209 type dummyClient struct { 210 objects map[string]Object 211 t Type 212 } 213 214 // NewObject implements ClientInterface 215 func (c *dummyClient) NewObject() Object { 216 return c.t.Object.DeepCopyObject().(Object) 217 } 218 219 // NewCollection implements ClientInterface 220 func (c *dummyClient) NewCollection() Collection { 221 return c.t.Collection.DeepCopyObject().(Collection) 222 } 223 224 // Create implements ClientInterface 225 func (c *dummyClient) Create(obj Object) (Object, error) { 226 c.objects[obj.GetName()] = obj 227 return obj, nil 228 } 229 230 // Update implements ClientInterface 231 func (c *dummyClient) Update(obj Object) (Object, error) { 232 _, ok := c.objects[obj.GetName()] 233 if !ok { 234 return nil, fmt.Errorf("cannot find object %s", obj.GetName()) 235 } 236 c.objects[obj.GetName()] = obj 237 return obj, nil 238 } 239 240 // Delete implements ClientInterface 241 func (c *dummyClient) Delete(name string, options *v1.DeleteOptions) error { 242 _, ok := c.objects[name] 243 if ok { 244 delete(c.objects, name) 245 return nil 246 } 247 return fmt.Errorf("%s does not exist", name) 248 } 249 250 // Get implements ClientInterface 251 func (c *dummyClient) Get(name string) (Object, error) { 252 obj, ok := c.objects[name] 253 if ok { 254 return obj, nil 255 } 256 return nil, fmt.Errorf("could not find %s", name) 257 } 258 259 // List implements ClientInterface 260 func (c *dummyClient) List(opts v1.ListOptions) (Collection, error) { 261 var items []Object 262 for _, i := range c.objects { 263 items = append(items, i) 264 } 265 r := c.NewCollection() 266 r.SetItems(items) 267 return r, nil 268 } 269 270 // Client implements a true CRD rest client 271 type Client struct { 272 cl *rest.RESTClient 273 ns string 274 t Type 275 codec runtime.ParameterCodec 276 } 277 278 // NewObject implements ClientInterface 279 func (c *Client) NewObject() Object { 280 return c.t.Object.DeepCopyObject().(Object) 281 } 282 283 // NewCollection implements ClientInterface 284 func (c *Client) NewCollection() Collection { 285 return c.t.Collection.DeepCopyObject().(Collection) 286 } 287 288 // Create implements ClientInterface 289 func (c *Client) Create(obj Object) (Object, error) { 290 result := c.NewObject() 291 err := c.cl.Post(). 292 Namespace(c.ns). 293 Resource(c.t.Plural). 294 Name(obj.GetName()). 295 Body(obj). 296 Do(). 297 Into(result) 298 return result, err 299 } 300 301 // Update implements ClientInterface 302 func (c *Client) Update(obj Object) (Object, error) { 303 result := c.NewObject() 304 err := c.cl.Put(). 305 Namespace(c.ns). 306 Resource(c.t.Plural). 307 Body(obj). 308 Name(obj.GetName()). 309 Do(). 310 Into(result) 311 return result, err 312 } 313 314 // Delete implements ClientInterface 315 func (c *Client) Delete(name string, options *v1.DeleteOptions) error { 316 return c.cl.Delete(). 317 Namespace(c.ns). 318 Resource(c.t.Plural). 319 Name(name). 320 Body(options). 321 Do(). 322 Error() 323 } 324 325 // Get implements ClientInterface 326 func (c *Client) Get(name string) (Object, error) { 327 result := c.NewObject() 328 err := c.cl.Get(). 329 Namespace(c.ns). 330 Resource(c.t.Plural). 331 Name(name). 332 Do(). 333 Into(result) 334 return result, err 335 } 336 337 // List implements ClientInterface 338 func (c *Client) List(opts v1.ListOptions) (Collection, error) { 339 result := c.NewCollection() 340 err := c.cl.Get(). 341 Namespace(c.ns). 342 Resource(c.t.Plural). 343 VersionedParams(&opts, c.codec). 344 Do(). 345 Into(result) 346 return result, err 347 }