mirror of
https://github.com/kubernetes/client-go.git
synced 2025-06-30 00:41:53 +00:00
Add fake client, informer factory, and lister to metadata client
These will be used by the garbage collector controller and others that use higher level primitives. Kubernetes-commit: bc89c37f32aa6cfd0f9ca975d9221d0a89320623
This commit is contained in:
parent
b83dc9a7d9
commit
cade5c0473
390
metadata/fake/simple.go
Normal file
390
metadata/fake/simple.go
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes 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 fake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
|
"k8s.io/client-go/metadata"
|
||||||
|
"k8s.io/client-go/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MetadataClient assists in creating fake objects for use when testing, since metadata.Getter
|
||||||
|
// does not expose create
|
||||||
|
type MetadataClient interface {
|
||||||
|
metadata.Getter
|
||||||
|
CreateFake(obj *metav1.PartialObjectMetadata, opts metav1.CreateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error)
|
||||||
|
UpdateFake(obj *metav1.PartialObjectMetadata, opts metav1.UpdateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSimpleMetadataClient creates a new client that will use the provided scheme and respond with the
|
||||||
|
// provided objects when requests are made. It will track actions made to the client which can be checked
|
||||||
|
// with GetActions().
|
||||||
|
func NewSimpleMetadataClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeMetadataClient {
|
||||||
|
gvkFakeList := schema.GroupVersionKind{Group: "fake-metadata-client-group", Version: "v1", Kind: "List"}
|
||||||
|
if !scheme.Recognizes(gvkFakeList) {
|
||||||
|
// In order to use List with this client, you have to have the v1.List registered in your scheme, since this is a test
|
||||||
|
// type we modify the input scheme
|
||||||
|
scheme.AddKnownTypeWithName(gvkFakeList, &metav1.List{})
|
||||||
|
}
|
||||||
|
|
||||||
|
codecs := serializer.NewCodecFactory(scheme)
|
||||||
|
o := testing.NewObjectTracker(scheme, codecs.UniversalDeserializer())
|
||||||
|
for _, obj := range objects {
|
||||||
|
if err := o.Add(obj); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cs := &FakeMetadataClient{scheme: scheme}
|
||||||
|
cs.AddReactor("*", "*", testing.ObjectReaction(o))
|
||||||
|
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
|
||||||
|
gvr := action.GetResource()
|
||||||
|
ns := action.GetNamespace()
|
||||||
|
watch, err := o.Watch(gvr, ns)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
return true, watch, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return cs
|
||||||
|
}
|
||||||
|
|
||||||
|
// FakeMetadataClient implements clientset.Interface. Meant to be embedded into a
|
||||||
|
// struct to get a default implementation. This makes faking out just the method
|
||||||
|
// you want to test easier.
|
||||||
|
type FakeMetadataClient struct {
|
||||||
|
testing.Fake
|
||||||
|
scheme *runtime.Scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
type metadataResourceClient struct {
|
||||||
|
client *FakeMetadataClient
|
||||||
|
namespace string
|
||||||
|
resource schema.GroupVersionResource
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ metadata.Interface = &FakeMetadataClient{}
|
||||||
|
|
||||||
|
// Resource returns an interface for accessing the provided resource.
|
||||||
|
func (c *FakeMetadataClient) Resource(resource schema.GroupVersionResource) metadata.Getter {
|
||||||
|
return &metadataResourceClient{client: c, resource: resource}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Namespace returns an interface for accessing the current resource in the specified
|
||||||
|
// namespace.
|
||||||
|
func (c *metadataResourceClient) Namespace(ns string) metadata.ResourceInterface {
|
||||||
|
ret := *c
|
||||||
|
ret.namespace = ns
|
||||||
|
return &ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFake records the object creation and processes it via the reactor.
|
||||||
|
func (c *metadataResourceClient) CreateFake(obj *metav1.PartialObjectMetadata, opts metav1.CreateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
|
||||||
|
var uncastRet runtime.Object
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case len(c.namespace) == 0 && len(subresources) == 0:
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewRootCreateAction(c.resource, obj), obj)
|
||||||
|
|
||||||
|
case len(c.namespace) == 0 && len(subresources) > 0:
|
||||||
|
accessor, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
name := accessor.GetName()
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewRootCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), obj), obj)
|
||||||
|
|
||||||
|
case len(c.namespace) > 0 && len(subresources) == 0:
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewCreateAction(c.resource, c.namespace, obj), obj)
|
||||||
|
|
||||||
|
case len(c.namespace) > 0 && len(subresources) > 0:
|
||||||
|
accessor, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
name := accessor.GetName()
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), c.namespace, obj), obj)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if uncastRet == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret, ok := uncastRet.(*metav1.PartialObjectMetadata)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected return value type %T", uncastRet)
|
||||||
|
}
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFake records the object update and processes it via the reactor.
|
||||||
|
func (c *metadataResourceClient) UpdateFake(obj *metav1.PartialObjectMetadata, opts metav1.UpdateOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
|
||||||
|
var uncastRet runtime.Object
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case len(c.namespace) == 0 && len(subresources) == 0:
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewRootUpdateAction(c.resource, obj), obj)
|
||||||
|
|
||||||
|
case len(c.namespace) == 0 && len(subresources) > 0:
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewRootUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), obj), obj)
|
||||||
|
|
||||||
|
case len(c.namespace) > 0 && len(subresources) == 0:
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewUpdateAction(c.resource, c.namespace, obj), obj)
|
||||||
|
|
||||||
|
case len(c.namespace) > 0 && len(subresources) > 0:
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, obj), obj)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if uncastRet == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret, ok := uncastRet.(*metav1.PartialObjectMetadata)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected return value type %T", uncastRet)
|
||||||
|
}
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStatus records the object status update and processes it via the reactor.
|
||||||
|
func (c *metadataResourceClient) UpdateStatus(obj *metav1.PartialObjectMetadata, opts metav1.UpdateOptions) (*metav1.PartialObjectMetadata, error) {
|
||||||
|
var uncastRet runtime.Object
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case len(c.namespace) == 0:
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewRootUpdateSubresourceAction(c.resource, "status", obj), obj)
|
||||||
|
|
||||||
|
case len(c.namespace) > 0:
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewUpdateSubresourceAction(c.resource, "status", c.namespace, obj), obj)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if uncastRet == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret, ok := uncastRet.(*metav1.PartialObjectMetadata)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected return value type %T", uncastRet)
|
||||||
|
}
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete records the object deletion and processes it via the reactor.
|
||||||
|
func (c *metadataResourceClient) Delete(name string, opts *metav1.DeleteOptions, subresources ...string) error {
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case len(c.namespace) == 0 && len(subresources) == 0:
|
||||||
|
_, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewRootDeleteAction(c.resource, name), &metav1.Status{Status: "metadata delete fail"})
|
||||||
|
|
||||||
|
case len(c.namespace) == 0 && len(subresources) > 0:
|
||||||
|
_, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewRootDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "metadata delete fail"})
|
||||||
|
|
||||||
|
case len(c.namespace) > 0 && len(subresources) == 0:
|
||||||
|
_, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewDeleteAction(c.resource, c.namespace, name), &metav1.Status{Status: "metadata delete fail"})
|
||||||
|
|
||||||
|
case len(c.namespace) > 0 && len(subresources) > 0:
|
||||||
|
_, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, name), &metav1.Status{Status: "metadata delete fail"})
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCollection records the object collection deletion and processes it via the reactor.
|
||||||
|
func (c *metadataResourceClient) DeleteCollection(opts *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case len(c.namespace) == 0:
|
||||||
|
action := testing.NewRootDeleteCollectionAction(c.resource, listOptions)
|
||||||
|
_, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "metadata deletecollection fail"})
|
||||||
|
|
||||||
|
case len(c.namespace) > 0:
|
||||||
|
action := testing.NewDeleteCollectionAction(c.resource, c.namespace, listOptions)
|
||||||
|
_, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "metadata deletecollection fail"})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get records the object retrieval and processes it via the reactor.
|
||||||
|
func (c *metadataResourceClient) Get(name string, opts metav1.GetOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
|
||||||
|
var uncastRet runtime.Object
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case len(c.namespace) == 0 && len(subresources) == 0:
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewRootGetAction(c.resource, name), &metav1.Status{Status: "metadata get fail"})
|
||||||
|
|
||||||
|
case len(c.namespace) == 0 && len(subresources) > 0:
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewRootGetSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "metadata get fail"})
|
||||||
|
|
||||||
|
case len(c.namespace) > 0 && len(subresources) == 0:
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewGetAction(c.resource, c.namespace, name), &metav1.Status{Status: "metadata get fail"})
|
||||||
|
|
||||||
|
case len(c.namespace) > 0 && len(subresources) > 0:
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewGetSubresourceAction(c.resource, c.namespace, strings.Join(subresources, "/"), name), &metav1.Status{Status: "metadata get fail"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if uncastRet == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret, ok := uncastRet.(*metav1.PartialObjectMetadata)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected return value type %T", uncastRet)
|
||||||
|
}
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// List records the object deletion and processes it via the reactor.
|
||||||
|
func (c *metadataResourceClient) List(opts metav1.ListOptions) (*metav1.PartialObjectMetadataList, error) {
|
||||||
|
var obj runtime.Object
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case len(c.namespace) == 0:
|
||||||
|
obj, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewRootListAction(c.resource, schema.GroupVersionKind{Group: "fake-metadata-client-group", Version: "v1", Kind: "" /*List is appended by the tracker automatically*/}, opts), &metav1.Status{Status: "metadata list fail"})
|
||||||
|
|
||||||
|
case len(c.namespace) > 0:
|
||||||
|
obj, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewListAction(c.resource, schema.GroupVersionKind{Group: "fake-metadata-client-group", Version: "v1", Kind: "" /*List is appended by the tracker automatically*/}, c.namespace, opts), &metav1.Status{Status: "metadata list fail"})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
label, _, _ := testing.ExtractFromListOptions(opts)
|
||||||
|
if label == nil {
|
||||||
|
label = labels.Everything()
|
||||||
|
}
|
||||||
|
|
||||||
|
inputList, ok := obj.(*metav1.List)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("incoming object is incorrect type %T", obj)
|
||||||
|
}
|
||||||
|
fmt.Printf("DEBUG: %#v\n", inputList)
|
||||||
|
|
||||||
|
list := &metav1.PartialObjectMetadataList{
|
||||||
|
ListMeta: inputList.ListMeta,
|
||||||
|
}
|
||||||
|
for i := range inputList.Items {
|
||||||
|
item, ok := inputList.Items[i].Object.(*metav1.PartialObjectMetadata)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("item %d in list %T is %T", i, inputList, inputList.Items[i].Object)
|
||||||
|
}
|
||||||
|
metadata, err := meta.Accessor(item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if label.Matches(labels.Set(metadata.GetLabels())) {
|
||||||
|
list.Items = append(list.Items, *item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *metadataResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {
|
||||||
|
switch {
|
||||||
|
case len(c.namespace) == 0:
|
||||||
|
return c.client.Fake.
|
||||||
|
InvokesWatch(testing.NewRootWatchAction(c.resource, opts))
|
||||||
|
|
||||||
|
case len(c.namespace) > 0:
|
||||||
|
return c.client.Fake.
|
||||||
|
InvokesWatch(testing.NewWatchAction(c.resource, c.namespace, opts))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("math broke")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch records the object patch and processes it via the reactor.
|
||||||
|
func (c *metadataResourceClient) Patch(name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
|
||||||
|
var uncastRet runtime.Object
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case len(c.namespace) == 0 && len(subresources) == 0:
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewRootPatchAction(c.resource, name, pt, data), &metav1.Status{Status: "metadata patch fail"})
|
||||||
|
|
||||||
|
case len(c.namespace) == 0 && len(subresources) > 0:
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, pt, data, subresources...), &metav1.Status{Status: "metadata patch fail"})
|
||||||
|
|
||||||
|
case len(c.namespace) > 0 && len(subresources) == 0:
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewPatchAction(c.resource, c.namespace, name, pt, data), &metav1.Status{Status: "metadata patch fail"})
|
||||||
|
|
||||||
|
case len(c.namespace) > 0 && len(subresources) > 0:
|
||||||
|
uncastRet, err = c.client.Fake.
|
||||||
|
Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, pt, data, subresources...), &metav1.Status{Status: "metadata patch fail"})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if uncastRet == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret, ok := uncastRet.(*metav1.PartialObjectMetadata)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected return value type %T", uncastRet)
|
||||||
|
}
|
||||||
|
return ret, err
|
||||||
|
}
|
201
metadata/fake/simple_test.go
Normal file
201
metadata/fake/simple_test.go
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes 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 fake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/equality"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/diff"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testGroup = "testgroup"
|
||||||
|
testVersion = "testversion"
|
||||||
|
testResource = "testkinds"
|
||||||
|
testNamespace = "testns"
|
||||||
|
testName = "testname"
|
||||||
|
testKind = "TestKind"
|
||||||
|
testAPIVersion = "testgroup/testversion"
|
||||||
|
)
|
||||||
|
|
||||||
|
var scheme *runtime.Scheme
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
scheme = runtime.NewScheme()
|
||||||
|
metav1.AddMetaToScheme(scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPartialObjectMetadata(apiVersion, kind, namespace, name string) *metav1.PartialObjectMetadata {
|
||||||
|
return &metav1.PartialObjectMetadata{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: apiVersion,
|
||||||
|
Kind: kind,
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: namespace,
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPartialObjectMetadataWithAnnotations(annotations map[string]string) *metav1.PartialObjectMetadata {
|
||||||
|
u := newPartialObjectMetadata(testAPIVersion, testKind, testNamespace, testName)
|
||||||
|
u.Annotations = annotations
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestList(t *testing.T) {
|
||||||
|
client := NewSimpleMetadataClient(scheme,
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||||
|
newPartialObjectMetadata("group2/version", "TheKind", "ns-foo", "name2-foo"),
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-bar"),
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-baz"),
|
||||||
|
newPartialObjectMetadata("group2/version", "TheKind", "ns-foo", "name2-baz"),
|
||||||
|
)
|
||||||
|
listFirst, err := client.Resource(schema.GroupVersionResource{Group: "group", Version: "version", Resource: "thekinds"}).List(metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []metav1.PartialObjectMetadata{
|
||||||
|
*newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||||
|
*newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-bar"),
|
||||||
|
*newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-baz"),
|
||||||
|
}
|
||||||
|
if !equality.Semantic.DeepEqual(listFirst.Items, expected) {
|
||||||
|
t.Fatal(diff.ObjectGoPrintDiff(expected, listFirst.Items))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type patchTestCase struct {
|
||||||
|
name string
|
||||||
|
object runtime.Object
|
||||||
|
patchType types.PatchType
|
||||||
|
patchBytes []byte
|
||||||
|
wantErrMsg string
|
||||||
|
expectedPatchedObject runtime.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *patchTestCase) runner(t *testing.T) {
|
||||||
|
client := NewSimpleMetadataClient(scheme, tc.object)
|
||||||
|
resourceInterface := client.Resource(schema.GroupVersionResource{Group: testGroup, Version: testVersion, Resource: testResource}).Namespace(testNamespace)
|
||||||
|
|
||||||
|
got, recErr := resourceInterface.Patch(testName, tc.patchType, tc.patchBytes, metav1.PatchOptions{})
|
||||||
|
|
||||||
|
if err := tc.verifyErr(recErr); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tc.verifyResult(got); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyErr verifies that the given error returned from Patch is the error
|
||||||
|
// expected by the test case.
|
||||||
|
func (tc *patchTestCase) verifyErr(err error) error {
|
||||||
|
if tc.wantErrMsg != "" && err == nil {
|
||||||
|
return fmt.Errorf("want error, got nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.wantErrMsg == "" && err != nil {
|
||||||
|
return fmt.Errorf("want no error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if want, got := tc.wantErrMsg, err.Error(); want != got {
|
||||||
|
return fmt.Errorf("incorrect error: want: %q got: %q", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *patchTestCase) verifyResult(result *metav1.PartialObjectMetadata) error {
|
||||||
|
if tc.expectedPatchedObject == nil && result == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !equality.Semantic.DeepEqual(result, tc.expectedPatchedObject) {
|
||||||
|
return fmt.Errorf("unexpected diff in received object: %s", diff.ObjectGoPrintDiff(tc.expectedPatchedObject, result))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPatch(t *testing.T) {
|
||||||
|
testCases := []patchTestCase{
|
||||||
|
{
|
||||||
|
name: "jsonpatch fails with merge type",
|
||||||
|
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
|
||||||
|
patchType: types.StrategicMergePatchType,
|
||||||
|
patchBytes: []byte(`[]`),
|
||||||
|
wantErrMsg: "invalid JSON document",
|
||||||
|
}, {
|
||||||
|
name: "jsonpatch works with empty patch",
|
||||||
|
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
|
||||||
|
patchType: types.JSONPatchType,
|
||||||
|
// No-op
|
||||||
|
patchBytes: []byte(`[]`),
|
||||||
|
expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
|
||||||
|
}, {
|
||||||
|
name: "jsonpatch works with simple change patch",
|
||||||
|
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
|
||||||
|
patchType: types.JSONPatchType,
|
||||||
|
// change spec.foo from bar to foobar
|
||||||
|
patchBytes: []byte(`[{"op": "replace", "path": "/metadata/annotations/foo", "value": "foobar"}]`),
|
||||||
|
expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "foobar"}),
|
||||||
|
}, {
|
||||||
|
name: "jsonpatch works with simple addition",
|
||||||
|
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
|
||||||
|
patchType: types.JSONPatchType,
|
||||||
|
// add spec.newvalue = dummy
|
||||||
|
patchBytes: []byte(`[{"op": "add", "path": "/metadata/annotations/newvalue", "value": "dummy"}]`),
|
||||||
|
expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar", "newvalue": "dummy"}),
|
||||||
|
}, {
|
||||||
|
name: "jsonpatch works with simple deletion",
|
||||||
|
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar", "toremove": "shouldnotbehere"}),
|
||||||
|
patchType: types.JSONPatchType,
|
||||||
|
// remove spec.newvalue = dummy
|
||||||
|
patchBytes: []byte(`[{"op": "remove", "path": "/metadata/annotations/toremove"}]`),
|
||||||
|
expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
|
||||||
|
}, {
|
||||||
|
name: "strategic merge patch fails with JSONPatch",
|
||||||
|
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
|
||||||
|
patchType: types.StrategicMergePatchType,
|
||||||
|
// add spec.newvalue = dummy
|
||||||
|
patchBytes: []byte(`[{"op": "add", "path": "/metadata/annotations/newvalue", "value": "dummy"}]`),
|
||||||
|
wantErrMsg: "invalid JSON document",
|
||||||
|
}, {
|
||||||
|
name: "merge patch works with simple replacement",
|
||||||
|
object: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "bar"}),
|
||||||
|
patchType: types.MergePatchType,
|
||||||
|
patchBytes: []byte(`{ "metadata": {"annotations": { "foo": "baz" } } }`),
|
||||||
|
expectedPatchedObject: newPartialObjectMetadataWithAnnotations(map[string]string{"foo": "baz"}),
|
||||||
|
},
|
||||||
|
// TODO: Add tests for strategic merge using v1.Pod for example to ensure the test cases
|
||||||
|
// demonstrate expected use cases.
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, tc.runner)
|
||||||
|
}
|
||||||
|
}
|
156
metadata/metadatainformer/informer.go
Normal file
156
metadata/metadatainformer/informer.go
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes 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 metadatainformer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
|
"k8s.io/client-go/metadata"
|
||||||
|
"k8s.io/client-go/metadata/metadatalister"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewSharedInformerFactory constructs a new instance of metadataSharedInformerFactory for all namespaces.
|
||||||
|
func NewSharedInformerFactory(client metadata.Interface, defaultResync time.Duration) SharedInformerFactory {
|
||||||
|
return NewFilteredSharedInformerFactory(client, defaultResync, metav1.NamespaceAll, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFilteredSharedInformerFactory constructs a new instance of metadataSharedInformerFactory.
|
||||||
|
// Listers obtained via this factory will be subject to the same filters as specified here.
|
||||||
|
func NewFilteredSharedInformerFactory(client metadata.Interface, defaultResync time.Duration, namespace string, tweakListOptions TweakListOptionsFunc) SharedInformerFactory {
|
||||||
|
return &metadataSharedInformerFactory{
|
||||||
|
client: client,
|
||||||
|
defaultResync: defaultResync,
|
||||||
|
namespace: namespace,
|
||||||
|
informers: map[schema.GroupVersionResource]informers.GenericInformer{},
|
||||||
|
startedInformers: make(map[schema.GroupVersionResource]bool),
|
||||||
|
tweakListOptions: tweakListOptions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type metadataSharedInformerFactory struct {
|
||||||
|
client metadata.Interface
|
||||||
|
defaultResync time.Duration
|
||||||
|
namespace string
|
||||||
|
|
||||||
|
lock sync.Mutex
|
||||||
|
informers map[schema.GroupVersionResource]informers.GenericInformer
|
||||||
|
// startedInformers is used for tracking which informers have been started.
|
||||||
|
// This allows Start() to be called multiple times safely.
|
||||||
|
startedInformers map[schema.GroupVersionResource]bool
|
||||||
|
tweakListOptions TweakListOptionsFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ SharedInformerFactory = &metadataSharedInformerFactory{}
|
||||||
|
|
||||||
|
func (f *metadataSharedInformerFactory) ForResource(gvr schema.GroupVersionResource) informers.GenericInformer {
|
||||||
|
f.lock.Lock()
|
||||||
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
|
key := gvr
|
||||||
|
informer, exists := f.informers[key]
|
||||||
|
if exists {
|
||||||
|
return informer
|
||||||
|
}
|
||||||
|
|
||||||
|
informer = NewFilteredMetadataInformer(f.client, gvr, f.namespace, f.defaultResync, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
|
||||||
|
f.informers[key] = informer
|
||||||
|
|
||||||
|
return informer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start initializes all requested informers.
|
||||||
|
func (f *metadataSharedInformerFactory) Start(stopCh <-chan struct{}) {
|
||||||
|
f.lock.Lock()
|
||||||
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
|
for informerType, informer := range f.informers {
|
||||||
|
if !f.startedInformers[informerType] {
|
||||||
|
go informer.Informer().Run(stopCh)
|
||||||
|
f.startedInformers[informerType] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForCacheSync waits for all started informers' cache were synced.
|
||||||
|
func (f *metadataSharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[schema.GroupVersionResource]bool {
|
||||||
|
informers := func() map[schema.GroupVersionResource]cache.SharedIndexInformer {
|
||||||
|
f.lock.Lock()
|
||||||
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
|
informers := map[schema.GroupVersionResource]cache.SharedIndexInformer{}
|
||||||
|
for informerType, informer := range f.informers {
|
||||||
|
if f.startedInformers[informerType] {
|
||||||
|
informers[informerType] = informer.Informer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return informers
|
||||||
|
}()
|
||||||
|
|
||||||
|
res := map[schema.GroupVersionResource]bool{}
|
||||||
|
for informType, informer := range informers {
|
||||||
|
res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFilteredMetadataInformer constructs a new informer for a metadata type.
|
||||||
|
func NewFilteredMetadataInformer(client metadata.Interface, gvr schema.GroupVersionResource, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions TweakListOptionsFunc) informers.GenericInformer {
|
||||||
|
return &metadataInformer{
|
||||||
|
gvr: gvr,
|
||||||
|
informer: cache.NewSharedIndexInformer(
|
||||||
|
&cache.ListWatch{
|
||||||
|
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||||
|
if tweakListOptions != nil {
|
||||||
|
tweakListOptions(&options)
|
||||||
|
}
|
||||||
|
return client.Resource(gvr).Namespace(namespace).List(options)
|
||||||
|
},
|
||||||
|
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||||
|
if tweakListOptions != nil {
|
||||||
|
tweakListOptions(&options)
|
||||||
|
}
|
||||||
|
return client.Resource(gvr).Namespace(namespace).Watch(options)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&metav1.PartialObjectMetadata{},
|
||||||
|
resyncPeriod,
|
||||||
|
indexers,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type metadataInformer struct {
|
||||||
|
informer cache.SharedIndexInformer
|
||||||
|
gvr schema.GroupVersionResource
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ informers.GenericInformer = &metadataInformer{}
|
||||||
|
|
||||||
|
func (d *metadataInformer) Informer() cache.SharedIndexInformer {
|
||||||
|
return d.informer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *metadataInformer) Lister() cache.GenericLister {
|
||||||
|
return metadatalister.NewRuntimeObjectShim(metadatalister.New(d.informer.GetIndexer(), d.gvr))
|
||||||
|
}
|
170
metadata/metadatainformer/informer_test.go
Normal file
170
metadata/metadatainformer/informer_test.go
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes 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 metadatainformer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/klog"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/equality"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/util/diff"
|
||||||
|
"k8s.io/client-go/metadata/fake"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
klog.InitFlags(flag.CommandLine)
|
||||||
|
flag.CommandLine.Lookup("v").Value.Set("5")
|
||||||
|
flag.CommandLine.Lookup("alsologtostderr").Value.Set("true")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMetadataSharedInformerFactory(t *testing.T) {
|
||||||
|
scenarios := []struct {
|
||||||
|
name string
|
||||||
|
existingObj *metav1.PartialObjectMetadata
|
||||||
|
gvr schema.GroupVersionResource
|
||||||
|
ns string
|
||||||
|
trigger func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeMetadataClient, testObject *metav1.PartialObjectMetadata) *metav1.PartialObjectMetadata
|
||||||
|
handler func(rcvCh chan<- *metav1.PartialObjectMetadata) *cache.ResourceEventHandlerFuncs
|
||||||
|
}{
|
||||||
|
// scenario 1
|
||||||
|
{
|
||||||
|
name: "scenario 1: test if adding an object triggers AddFunc",
|
||||||
|
ns: "ns-foo",
|
||||||
|
gvr: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
|
||||||
|
trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeMetadataClient, _ *metav1.PartialObjectMetadata) *metav1.PartialObjectMetadata {
|
||||||
|
testObject := newPartialObjectMetadata("extensions/v1beta1", "Deployment", "ns-foo", "name-foo")
|
||||||
|
createdObj, err := fakeClient.Resource(gvr).Namespace(ns).(fake.MetadataClient).CreateFake(testObject, metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
return createdObj
|
||||||
|
},
|
||||||
|
handler: func(rcvCh chan<- *metav1.PartialObjectMetadata) *cache.ResourceEventHandlerFuncs {
|
||||||
|
return &cache.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: func(obj interface{}) {
|
||||||
|
rcvCh <- obj.(*metav1.PartialObjectMetadata)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// scenario 2
|
||||||
|
{
|
||||||
|
name: "scenario 2: tests if updating an object triggers UpdateFunc",
|
||||||
|
ns: "ns-foo",
|
||||||
|
gvr: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
|
||||||
|
existingObj: newPartialObjectMetadata("extensions/v1beta1", "Deployment", "ns-foo", "name-foo"),
|
||||||
|
trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeMetadataClient, testObject *metav1.PartialObjectMetadata) *metav1.PartialObjectMetadata {
|
||||||
|
if testObject.Annotations == nil {
|
||||||
|
testObject.Annotations = make(map[string]string)
|
||||||
|
}
|
||||||
|
testObject.Annotations["test"] = "updatedName"
|
||||||
|
updatedObj, err := fakeClient.Resource(gvr).Namespace(ns).(fake.MetadataClient).UpdateFake(testObject, metav1.UpdateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
return updatedObj
|
||||||
|
},
|
||||||
|
handler: func(rcvCh chan<- *metav1.PartialObjectMetadata) *cache.ResourceEventHandlerFuncs {
|
||||||
|
return &cache.ResourceEventHandlerFuncs{
|
||||||
|
UpdateFunc: func(old, updated interface{}) {
|
||||||
|
rcvCh <- updated.(*metav1.PartialObjectMetadata)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// scenario 3
|
||||||
|
{
|
||||||
|
name: "scenario 3: test if deleting an object triggers DeleteFunc",
|
||||||
|
ns: "ns-foo",
|
||||||
|
gvr: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
|
||||||
|
existingObj: newPartialObjectMetadata("extensions/v1beta1", "Deployment", "ns-foo", "name-foo"),
|
||||||
|
trigger: func(gvr schema.GroupVersionResource, ns string, fakeClient *fake.FakeMetadataClient, testObject *metav1.PartialObjectMetadata) *metav1.PartialObjectMetadata {
|
||||||
|
err := fakeClient.Resource(gvr).Namespace(ns).Delete(testObject.GetName(), &metav1.DeleteOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
return testObject
|
||||||
|
},
|
||||||
|
handler: func(rcvCh chan<- *metav1.PartialObjectMetadata) *cache.ResourceEventHandlerFuncs {
|
||||||
|
return &cache.ResourceEventHandlerFuncs{
|
||||||
|
DeleteFunc: func(obj interface{}) {
|
||||||
|
rcvCh <- obj.(*metav1.PartialObjectMetadata)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ts := range scenarios {
|
||||||
|
t.Run(ts.name, func(t *testing.T) {
|
||||||
|
// test data
|
||||||
|
timeout := time.Duration(3 * time.Second)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
scheme := runtime.NewScheme()
|
||||||
|
metav1.AddMetaToScheme(scheme)
|
||||||
|
informerReciveObjectCh := make(chan *metav1.PartialObjectMetadata, 1)
|
||||||
|
objs := []runtime.Object{}
|
||||||
|
if ts.existingObj != nil {
|
||||||
|
objs = append(objs, ts.existingObj)
|
||||||
|
}
|
||||||
|
fakeClient := fake.NewSimpleMetadataClient(scheme, objs...)
|
||||||
|
target := NewSharedInformerFactory(fakeClient, 0)
|
||||||
|
|
||||||
|
// act
|
||||||
|
informerListerForGvr := target.ForResource(ts.gvr)
|
||||||
|
informerListerForGvr.Informer().AddEventHandler(ts.handler(informerReciveObjectCh))
|
||||||
|
target.Start(ctx.Done())
|
||||||
|
if synced := target.WaitForCacheSync(ctx.Done()); !synced[ts.gvr] {
|
||||||
|
t.Fatalf("informer for %s hasn't synced", ts.gvr)
|
||||||
|
}
|
||||||
|
|
||||||
|
testObject := ts.trigger(ts.gvr, ts.ns, fakeClient, ts.existingObj)
|
||||||
|
select {
|
||||||
|
case objFromInformer := <-informerReciveObjectCh:
|
||||||
|
if !equality.Semantic.DeepEqual(testObject, objFromInformer) {
|
||||||
|
t.Fatalf("%v", diff.ObjectDiff(testObject, objFromInformer))
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
t.Errorf("tested informer haven't received an object, waited %v", timeout)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPartialObjectMetadata(apiVersion, kind, namespace, name string) *metav1.PartialObjectMetadata {
|
||||||
|
return &metav1.PartialObjectMetadata{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: apiVersion,
|
||||||
|
Kind: kind,
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: namespace,
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
34
metadata/metadatainformer/interface.go
Normal file
34
metadata/metadatainformer/interface.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes 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 metadatainformer
|
||||||
|
|
||||||
|
import (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SharedInformerFactory provides access to a shared informer and lister for dynamic client
|
||||||
|
type SharedInformerFactory interface {
|
||||||
|
Start(stopCh <-chan struct{})
|
||||||
|
ForResource(gvr schema.GroupVersionResource) informers.GenericInformer
|
||||||
|
WaitForCacheSync(stopCh <-chan struct{}) map[schema.GroupVersionResource]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// TweakListOptionsFunc defines the signature of a helper function
|
||||||
|
// that wants to provide more listing options to API
|
||||||
|
type TweakListOptionsFunc func(*metav1.ListOptions)
|
40
metadata/metadatalister/interface.go
Normal file
40
metadata/metadatalister/interface.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes 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 metadatalister
|
||||||
|
|
||||||
|
import (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Lister helps list resources.
|
||||||
|
type Lister interface {
|
||||||
|
// List lists all resources in the indexer.
|
||||||
|
List(selector labels.Selector) (ret []*metav1.PartialObjectMetadata, err error)
|
||||||
|
// Get retrieves a resource from the indexer with the given name
|
||||||
|
Get(name string) (*metav1.PartialObjectMetadata, error)
|
||||||
|
// Namespace returns an object that can list and get resources in a given namespace.
|
||||||
|
Namespace(namespace string) NamespaceLister
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceLister helps list and get resources.
|
||||||
|
type NamespaceLister interface {
|
||||||
|
// List lists all resources in the indexer for a given namespace.
|
||||||
|
List(selector labels.Selector) (ret []*metav1.PartialObjectMetadata, err error)
|
||||||
|
// Get retrieves a resource from the indexer for a given namespace and name.
|
||||||
|
Get(name string) (*metav1.PartialObjectMetadata, error)
|
||||||
|
}
|
91
metadata/metadatalister/lister.go
Normal file
91
metadata/metadatalister/lister.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes 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 metadatalister
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Lister = &metadataLister{}
|
||||||
|
var _ NamespaceLister = &metadataNamespaceLister{}
|
||||||
|
|
||||||
|
// metadataLister implements the Lister interface.
|
||||||
|
type metadataLister struct {
|
||||||
|
indexer cache.Indexer
|
||||||
|
gvr schema.GroupVersionResource
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Lister.
|
||||||
|
func New(indexer cache.Indexer, gvr schema.GroupVersionResource) Lister {
|
||||||
|
return &metadataLister{indexer: indexer, gvr: gvr}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List lists all resources in the indexer.
|
||||||
|
func (l *metadataLister) List(selector labels.Selector) (ret []*metav1.PartialObjectMetadata, err error) {
|
||||||
|
err = cache.ListAll(l.indexer, selector, func(m interface{}) {
|
||||||
|
ret = append(ret, m.(*metav1.PartialObjectMetadata))
|
||||||
|
})
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves a resource from the indexer with the given name
|
||||||
|
func (l *metadataLister) Get(name string) (*metav1.PartialObjectMetadata, error) {
|
||||||
|
obj, exists, err := l.indexer.GetByKey(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return nil, errors.NewNotFound(l.gvr.GroupResource(), name)
|
||||||
|
}
|
||||||
|
return obj.(*metav1.PartialObjectMetadata), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Namespace returns an object that can list and get resources from a given namespace.
|
||||||
|
func (l *metadataLister) Namespace(namespace string) NamespaceLister {
|
||||||
|
return &metadataNamespaceLister{indexer: l.indexer, namespace: namespace, gvr: l.gvr}
|
||||||
|
}
|
||||||
|
|
||||||
|
// metadataNamespaceLister implements the NamespaceLister interface.
|
||||||
|
type metadataNamespaceLister struct {
|
||||||
|
indexer cache.Indexer
|
||||||
|
namespace string
|
||||||
|
gvr schema.GroupVersionResource
|
||||||
|
}
|
||||||
|
|
||||||
|
// List lists all resources in the indexer for a given namespace.
|
||||||
|
func (l *metadataNamespaceLister) List(selector labels.Selector) (ret []*metav1.PartialObjectMetadata, err error) {
|
||||||
|
err = cache.ListAllByNamespace(l.indexer, l.namespace, selector, func(m interface{}) {
|
||||||
|
ret = append(ret, m.(*metav1.PartialObjectMetadata))
|
||||||
|
})
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves a resource from the indexer for a given namespace and name.
|
||||||
|
func (l *metadataNamespaceLister) Get(name string) (*metav1.PartialObjectMetadata, error) {
|
||||||
|
obj, exists, err := l.indexer.GetByKey(l.namespace + "/" + name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return nil, errors.NewNotFound(l.gvr.GroupResource(), name)
|
||||||
|
}
|
||||||
|
return obj.(*metav1.PartialObjectMetadata), nil
|
||||||
|
}
|
256
metadata/metadatalister/lister_test.go
Normal file
256
metadata/metadatalister/lister_test.go
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes 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 metadatalister
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/util/diff"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNamespaceGetMethod(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
existingObjects []runtime.Object
|
||||||
|
namespaceToSync string
|
||||||
|
gvrToSync schema.GroupVersionResource
|
||||||
|
objectToGet string
|
||||||
|
expectedObject *metav1.PartialObjectMetadata
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "scenario 1: gets name-foo1 resource from the indexer from ns-foo namespace",
|
||||||
|
existingObjects: []runtime.Object{
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo1"),
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-bar", "name-bar"),
|
||||||
|
},
|
||||||
|
namespaceToSync: "ns-foo",
|
||||||
|
gvrToSync: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"},
|
||||||
|
objectToGet: "name-foo1",
|
||||||
|
expectedObject: newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scenario 2: gets name-foo-non-existing resource from the indexer from ns-foo namespace",
|
||||||
|
existingObjects: []runtime.Object{
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo1"),
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-bar", "name-bar"),
|
||||||
|
},
|
||||||
|
namespaceToSync: "ns-foo",
|
||||||
|
gvrToSync: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"},
|
||||||
|
objectToGet: "name-foo-non-existing",
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
// test data
|
||||||
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
|
||||||
|
for _, obj := range test.existingObjects {
|
||||||
|
err := indexer.Add(obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// act
|
||||||
|
target := New(indexer, test.gvrToSync).Namespace(test.namespaceToSync)
|
||||||
|
actualObject, err := target.Get(test.objectToGet)
|
||||||
|
|
||||||
|
// validate
|
||||||
|
if test.expectError {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected to get an error but non was returned")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(test.expectedObject, actualObject) {
|
||||||
|
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", test.expectedObject, actualObject, diff.ObjectDiff(test.expectedObject, actualObject))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNamespaceListMethod(t *testing.T) {
|
||||||
|
// test data
|
||||||
|
objs := []runtime.Object{
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo1"),
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-bar", "name-bar"),
|
||||||
|
}
|
||||||
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
|
||||||
|
for _, obj := range objs {
|
||||||
|
err := indexer.Add(obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expectedOutput := []*metav1.PartialObjectMetadata{
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo1"),
|
||||||
|
}
|
||||||
|
namespaceToList := "ns-foo"
|
||||||
|
|
||||||
|
// act
|
||||||
|
target := New(indexer, schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"}).Namespace(namespaceToList)
|
||||||
|
actualOutput, err := target.List(labels.Everything())
|
||||||
|
|
||||||
|
// validate
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertListOrDie(expectedOutput, actualOutput, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListerGetMethod(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
existingObjects []runtime.Object
|
||||||
|
namespaceToSync string
|
||||||
|
gvrToSync schema.GroupVersionResource
|
||||||
|
objectToGet string
|
||||||
|
expectedObject *metav1.PartialObjectMetadata
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "scenario 1: gets name-foo1 resource from the indexer",
|
||||||
|
existingObjects: []runtime.Object{
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "", "name-foo1"),
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-bar", "name-bar"),
|
||||||
|
},
|
||||||
|
namespaceToSync: "",
|
||||||
|
gvrToSync: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"},
|
||||||
|
objectToGet: "name-foo1",
|
||||||
|
expectedObject: newPartialObjectMetadata("group/version", "TheKind", "", "name-foo1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scenario 2: doesn't get name-foo resource from the indexer from ns-foo namespace",
|
||||||
|
existingObjects: []runtime.Object{
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo1"),
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-bar", "name-bar"),
|
||||||
|
},
|
||||||
|
namespaceToSync: "ns-foo",
|
||||||
|
gvrToSync: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"},
|
||||||
|
objectToGet: "name-foo",
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
// test data
|
||||||
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
|
||||||
|
for _, obj := range test.existingObjects {
|
||||||
|
err := indexer.Add(obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// act
|
||||||
|
target := New(indexer, test.gvrToSync)
|
||||||
|
actualObject, err := target.Get(test.objectToGet)
|
||||||
|
|
||||||
|
// validate
|
||||||
|
if test.expectError {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected to get an error but non was returned")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(test.expectedObject, actualObject) {
|
||||||
|
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", test.expectedObject, actualObject, diff.ObjectDiff(test.expectedObject, actualObject))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListerListMethod(t *testing.T) {
|
||||||
|
// test data
|
||||||
|
objs := []runtime.Object{
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-bar"),
|
||||||
|
}
|
||||||
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
|
||||||
|
for _, obj := range objs {
|
||||||
|
err := indexer.Add(obj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expectedOutput := []*metav1.PartialObjectMetadata{
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||||
|
newPartialObjectMetadata("group/version", "TheKind", "ns-foo", "name-bar"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// act
|
||||||
|
target := New(indexer, schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"})
|
||||||
|
actualOutput, err := target.List(labels.Everything())
|
||||||
|
|
||||||
|
// validate
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assertListOrDie(expectedOutput, actualOutput, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertListOrDie(expected, actual []*metav1.PartialObjectMetadata, t *testing.T) {
|
||||||
|
if len(actual) != len(expected) {
|
||||||
|
t.Fatalf("unexpected number of items returned, expected = %d, actual = %d", len(expected), len(actual))
|
||||||
|
}
|
||||||
|
for _, expectedObject := range expected {
|
||||||
|
found := false
|
||||||
|
for _, actualObject := range actual {
|
||||||
|
if actualObject.GetName() == expectedObject.GetName() {
|
||||||
|
if !reflect.DeepEqual(expectedObject, actualObject) {
|
||||||
|
t.Fatalf("unexpected object has been returned expected = %v actual = %v, diff = %v", expectedObject, actualObject, diff.ObjectDiff(expectedObject, actualObject))
|
||||||
|
}
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("the resource with the name = %s was not found in the returned output", expectedObject.GetName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPartialObjectMetadata(apiVersion, kind, namespace, name string) *metav1.PartialObjectMetadata {
|
||||||
|
return &metav1.PartialObjectMetadata{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: apiVersion,
|
||||||
|
Kind: kind,
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: namespace,
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
87
metadata/metadatalister/shim.go
Normal file
87
metadata/metadatalister/shim.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes 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 metadatalister
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ cache.GenericLister = &metadataListerShim{}
|
||||||
|
var _ cache.GenericNamespaceLister = &metadataNamespaceListerShim{}
|
||||||
|
|
||||||
|
// metadataListerShim implements the cache.GenericLister interface.
|
||||||
|
type metadataListerShim struct {
|
||||||
|
lister Lister
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRuntimeObjectShim returns a new shim for Lister.
|
||||||
|
// It wraps Lister so that it implements cache.GenericLister interface
|
||||||
|
func NewRuntimeObjectShim(lister Lister) cache.GenericLister {
|
||||||
|
return &metadataListerShim{lister: lister}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List will return all objects across namespaces
|
||||||
|
func (s *metadataListerShim) List(selector labels.Selector) (ret []runtime.Object, err error) {
|
||||||
|
objs, err := s.lister.List(selector)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = make([]runtime.Object, len(objs))
|
||||||
|
for index, obj := range objs {
|
||||||
|
ret[index] = obj
|
||||||
|
}
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get will attempt to retrieve assuming that name==key
|
||||||
|
func (s *metadataListerShim) Get(name string) (runtime.Object, error) {
|
||||||
|
return s.lister.Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *metadataListerShim) ByNamespace(namespace string) cache.GenericNamespaceLister {
|
||||||
|
return &metadataNamespaceListerShim{
|
||||||
|
namespaceLister: s.lister.Namespace(namespace),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// metadataNamespaceListerShim implements the NamespaceLister interface.
|
||||||
|
// It wraps NamespaceLister so that it implements cache.GenericNamespaceLister interface
|
||||||
|
type metadataNamespaceListerShim struct {
|
||||||
|
namespaceLister NamespaceLister
|
||||||
|
}
|
||||||
|
|
||||||
|
// List will return all objects in this namespace
|
||||||
|
func (ns *metadataNamespaceListerShim) List(selector labels.Selector) (ret []runtime.Object, err error) {
|
||||||
|
objs, err := ns.namespaceLister.List(selector)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = make([]runtime.Object, len(objs))
|
||||||
|
for index, obj := range objs {
|
||||||
|
ret[index] = obj
|
||||||
|
}
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get will attempt to retrieve by namespace and name
|
||||||
|
func (ns *metadataNamespaceListerShim) Get(name string) (runtime.Object, error) {
|
||||||
|
return ns.namespaceLister.Get(name)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user