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  }