github.com/abayer/test-infra@v0.0.5/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 23 "k8s.io/test-infra/boskos/common" 24 25 apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" 26 apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 27 apierrors "k8s.io/apimachinery/pkg/api/errors" 28 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/apimachinery/pkg/runtime/schema" 31 "k8s.io/apimachinery/pkg/runtime/serializer" 32 "k8s.io/client-go/rest" 33 "k8s.io/client-go/tools/clientcmd" 34 ) 35 36 const ( 37 group = "boskos.k8s.io" 38 version = "v1" 39 ) 40 41 var ( 42 kubeConfig = flag.String("kubeconfig", "", "absolute path to the kubeConfig file") 43 namespace = flag.String("namespace", v1.NamespaceDefault, "namespace to install on") 44 ) 45 46 // Type defines a Custom Resource Definition (CRD) Type. 47 type Type struct { 48 Kind, ListKind string 49 Singular, Plural string 50 Object Object 51 Collection Collection 52 } 53 54 // Object extends the runtime.Object interface. CRD are just a representation of the actual boskos object 55 // which should implements the common.Item interface. 56 type Object interface { 57 runtime.Object 58 GetName() string 59 FromItem(item common.Item) 60 ToItem() common.Item 61 } 62 63 // Collection is a list of Object interface. 64 type Collection interface { 65 runtime.Object 66 SetItems([]Object) 67 GetItems() []Object 68 } 69 70 // createRESTConfig for cluster API server, pass empty config file for in-cluster 71 func createRESTConfig(kubeconfig string, t Type) (config *rest.Config, types *runtime.Scheme, err error) { 72 if kubeconfig == "" { 73 config, err = rest.InClusterConfig() 74 } else { 75 config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) 76 } 77 78 if err != nil { 79 return 80 } 81 82 version := schema.GroupVersion{ 83 Group: group, 84 Version: version, 85 } 86 87 config.GroupVersion = &version 88 config.APIPath = "/apis" 89 config.ContentType = runtime.ContentTypeJSON 90 91 types = runtime.NewScheme() 92 schemeBuilder := runtime.NewSchemeBuilder( 93 func(scheme *runtime.Scheme) error { 94 scheme.AddKnownTypes(version, t.Object, t.Collection) 95 v1.AddToGroupVersion(scheme, version) 96 return nil 97 }) 98 err = schemeBuilder.AddToScheme(types) 99 config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: serializer.NewCodecFactory(types)} 100 101 return 102 } 103 104 // registerResource sends a request to create CRDs and waits for them to initialize 105 func registerResource(config *rest.Config, t Type) error { 106 c, err := apiextensionsclient.NewForConfig(config) 107 if err != nil { 108 return err 109 } 110 111 crd := &apiextensionsv1beta1.CustomResourceDefinition{ 112 ObjectMeta: v1.ObjectMeta{ 113 Name: fmt.Sprintf("%s.%s", t.Plural, group), 114 }, 115 Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{ 116 Group: group, 117 Version: version, 118 Scope: apiextensionsv1beta1.NamespaceScoped, 119 Names: apiextensionsv1beta1.CustomResourceDefinitionNames{ 120 Singular: t.Singular, 121 Plural: t.Plural, 122 Kind: t.Kind, 123 ListKind: t.ListKind, 124 }, 125 }, 126 } 127 if _, err := c.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd); err != nil && !apierrors.IsAlreadyExists(err) { 128 return err 129 } 130 return nil 131 } 132 133 // NewClient creates a CRD client for a given resource type. 134 func NewClient(cl *rest.RESTClient, scheme *runtime.Scheme, namespace string, t Type) Client { 135 return Client{cl: cl, ns: namespace, t: t, 136 codec: runtime.NewParameterCodec(scheme)} 137 } 138 139 // newDummyClient creates a in memory client representation for testing, such that we do not need to use a kubernetes API Server. 140 func newDummyClient(t Type) *dummyClient { 141 c := &dummyClient{ 142 t: t, 143 objects: make(map[string]Object), 144 } 145 return c 146 } 147 148 // newClientFromFlags creates a CRD rest client from provided flags. 149 func newClientFromFlags(t Type) (*Client, error) { 150 config, scheme, err := createRESTConfig(*kubeConfig, t) 151 if err != nil { 152 return nil, err 153 } 154 155 if err = registerResource(config, t); err != nil { 156 return nil, err 157 } 158 // creates the client 159 var restClient *rest.RESTClient 160 restClient, err = rest.RESTClientFor(config) 161 if err != nil { 162 return nil, err 163 } 164 rc := NewClient(restClient, scheme, *namespace, t) 165 return &rc, nil 166 } 167 168 // ClientInterface is used for testing. 169 type ClientInterface interface { 170 // NewObject instantiates a new object of the type supported by the client 171 NewObject() Object 172 // NewCollection instantiates a new collection of the type supported by the client 173 NewCollection() Collection 174 // Create a new object 175 Create(obj Object) (Object, error) 176 // Update an existing object, fails if object does not exist 177 Update(obj Object) (Object, error) 178 // Delete an existing object, fails if objects does not exist 179 Delete(name string, options *v1.DeleteOptions) error 180 // Get an existing object 181 Get(name string) (Object, error) 182 // LIst existing objects 183 List(opts v1.ListOptions) (Collection, error) 184 } 185 186 // dummyClient is used for testing purposes 187 type dummyClient struct { 188 objects map[string]Object 189 t Type 190 } 191 192 // NewObject implements ClientInterface 193 func (c *dummyClient) NewObject() Object { 194 return c.t.Object.DeepCopyObject().(Object) 195 } 196 197 // NewCollection implements ClientInterface 198 func (c *dummyClient) NewCollection() Collection { 199 return c.t.Collection.DeepCopyObject().(Collection) 200 } 201 202 // Create implements ClientInterface 203 func (c *dummyClient) Create(obj Object) (Object, error) { 204 c.objects[obj.GetName()] = obj 205 return obj, nil 206 } 207 208 // Update implements ClientInterface 209 func (c *dummyClient) Update(obj Object) (Object, error) { 210 _, ok := c.objects[obj.GetName()] 211 if !ok { 212 return nil, fmt.Errorf("cannot find object %s", obj.GetName()) 213 } 214 c.objects[obj.GetName()] = obj 215 return obj, nil 216 } 217 218 // Delete implements ClientInterface 219 func (c *dummyClient) Delete(name string, options *v1.DeleteOptions) error { 220 _, ok := c.objects[name] 221 if ok { 222 delete(c.objects, name) 223 return nil 224 } 225 return fmt.Errorf("%s does not exist", name) 226 } 227 228 // Get implements ClientInterface 229 func (c *dummyClient) Get(name string) (Object, error) { 230 obj, ok := c.objects[name] 231 if ok { 232 return obj, nil 233 } 234 return nil, fmt.Errorf("could not find %s", name) 235 } 236 237 // List implements ClientInterface 238 func (c *dummyClient) List(opts v1.ListOptions) (Collection, error) { 239 var items []Object 240 for _, i := range c.objects { 241 items = append(items, i) 242 } 243 r := c.NewCollection() 244 r.SetItems(items) 245 return r, nil 246 } 247 248 // Client implements a true CRD rest client 249 type Client struct { 250 cl *rest.RESTClient 251 ns string 252 t Type 253 codec runtime.ParameterCodec 254 } 255 256 // NewObject implements ClientInterface 257 func (c *Client) NewObject() Object { 258 return c.t.Object.DeepCopyObject().(Object) 259 } 260 261 // NewCollection implements ClientInterface 262 func (c *Client) NewCollection() Collection { 263 return c.t.Collection.DeepCopyObject().(Collection) 264 } 265 266 // Create implements ClientInterface 267 func (c *Client) Create(obj Object) (Object, error) { 268 result := c.NewObject() 269 err := c.cl.Post(). 270 Namespace(c.ns). 271 Resource(c.t.Plural). 272 Name(obj.GetName()). 273 Body(obj). 274 Do(). 275 Into(result) 276 return result, err 277 } 278 279 // Update implements ClientInterface 280 func (c *Client) Update(obj Object) (Object, error) { 281 result := c.NewObject() 282 err := c.cl.Put(). 283 Namespace(c.ns). 284 Resource(c.t.Plural). 285 Body(obj). 286 Name(obj.GetName()). 287 Do(). 288 Into(result) 289 return result, err 290 } 291 292 // Delete implements ClientInterface 293 func (c *Client) Delete(name string, options *v1.DeleteOptions) error { 294 return c.cl.Delete(). 295 Namespace(c.ns). 296 Resource(c.t.Plural). 297 Name(name). 298 Body(options). 299 Do(). 300 Error() 301 } 302 303 // Get implements ClientInterface 304 func (c *Client) Get(name string) (Object, error) { 305 result := c.NewObject() 306 err := c.cl.Get(). 307 Namespace(c.ns). 308 Resource(c.t.Plural). 309 Name(name). 310 Do(). 311 Into(result) 312 return result, err 313 } 314 315 // List implements ClientInterface 316 func (c *Client) List(opts v1.ListOptions) (Collection, error) { 317 result := c.NewCollection() 318 err := c.cl.Get(). 319 Namespace(c.ns). 320 Resource(c.t.Plural). 321 VersionedParams(&opts, c.codec). 322 Do(). 323 Into(result) 324 return result, err 325 }