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  }