Merge pull request #68748 from p0lyn0mial/dynamic_lister_informer
adds dynamic lister
This commit is contained in:
		| @@ -59,6 +59,7 @@ filegroup( | ||||
|     name = "all-srcs", | ||||
|     srcs = [ | ||||
|         ":package-srcs", | ||||
|         "//staging/src/k8s.io/client-go/dynamic/dynamiclister:all-srcs", | ||||
|         "//staging/src/k8s.io/client-go/dynamic/fake:all-srcs", | ||||
|     ], | ||||
|     tags = ["automanaged"], | ||||
|   | ||||
							
								
								
									
										47
									
								
								staging/src/k8s.io/client-go/dynamic/dynamiclister/BUILD
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								staging/src/k8s.io/client-go/dynamic/dynamiclister/BUILD
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") | ||||
|  | ||||
| go_library( | ||||
|     name = "go_default_library", | ||||
|     srcs = [ | ||||
|         "interface.go", | ||||
|         "lister.go", | ||||
|     ], | ||||
|     importmap = "k8s.io/kubernetes/vendor/k8s.io/client-go/dynamic/dynamiclister", | ||||
|     importpath = "k8s.io/client-go/dynamic/dynamiclister", | ||||
|     visibility = ["//visibility:public"], | ||||
|     deps = [ | ||||
|         "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", | ||||
|         "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", | ||||
|         "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", | ||||
|         "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", | ||||
|         "//staging/src/k8s.io/client-go/tools/cache:go_default_library", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
| go_test( | ||||
|     name = "go_default_test", | ||||
|     srcs = ["lister_test.go"], | ||||
|     embed = [":go_default_library"], | ||||
|     deps = [ | ||||
|         "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", | ||||
|         "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", | ||||
|         "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", | ||||
|         "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", | ||||
|         "//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library", | ||||
|         "//staging/src/k8s.io/client-go/tools/cache:go_default_library", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
| filegroup( | ||||
|     name = "package-srcs", | ||||
|     srcs = glob(["**"]), | ||||
|     tags = ["automanaged"], | ||||
|     visibility = ["//visibility:private"], | ||||
| ) | ||||
|  | ||||
| filegroup( | ||||
|     name = "all-srcs", | ||||
|     srcs = [":package-srcs"], | ||||
|     tags = ["automanaged"], | ||||
|     visibility = ["//visibility:public"], | ||||
| ) | ||||
| @@ -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 dynamiclister | ||||
|  | ||||
| import ( | ||||
| 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||||
| 	"k8s.io/apimachinery/pkg/labels" | ||||
| ) | ||||
|  | ||||
| // Lister helps list resources. | ||||
| type Lister interface { | ||||
| 	// List lists all resources in the indexer. | ||||
| 	List(selector labels.Selector) (ret []*unstructured.Unstructured, err error) | ||||
| 	// Get retrieves a resource from the indexer with the given name | ||||
| 	Get(name string) (*unstructured.Unstructured, 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 []*unstructured.Unstructured, err error) | ||||
| 	// Get retrieves a resource from the indexer for a given namespace and name. | ||||
| 	Get(name string) (*unstructured.Unstructured, error) | ||||
| } | ||||
							
								
								
									
										91
									
								
								staging/src/k8s.io/client-go/dynamic/dynamiclister/lister.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								staging/src/k8s.io/client-go/dynamic/dynamiclister/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 dynamiclister | ||||
|  | ||||
| import ( | ||||
| 	"k8s.io/apimachinery/pkg/api/errors" | ||||
| 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||||
| 	"k8s.io/apimachinery/pkg/labels" | ||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| ) | ||||
|  | ||||
| var _ Lister = &dynamicLister{} | ||||
| var _ NamespaceLister = &dynamicNamespaceLister{} | ||||
|  | ||||
| // dynamicLister implements the Lister interface. | ||||
| type dynamicLister struct { | ||||
| 	indexer cache.Indexer | ||||
| 	gvr     schema.GroupVersionResource | ||||
| } | ||||
|  | ||||
| // New returns a new Lister. | ||||
| func New(indexer cache.Indexer, gvr schema.GroupVersionResource) Lister { | ||||
| 	return &dynamicLister{indexer: indexer, gvr: gvr} | ||||
| } | ||||
|  | ||||
| // List lists all resources in the indexer. | ||||
| func (l *dynamicLister) List(selector labels.Selector) (ret []*unstructured.Unstructured, err error) { | ||||
| 	err = cache.ListAll(l.indexer, selector, func(m interface{}) { | ||||
| 		ret = append(ret, m.(*unstructured.Unstructured)) | ||||
| 	}) | ||||
| 	return ret, err | ||||
| } | ||||
|  | ||||
| // Get retrieves a resource from the indexer with the given name | ||||
| func (l *dynamicLister) Get(name string) (*unstructured.Unstructured, 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.(*unstructured.Unstructured), nil | ||||
| } | ||||
|  | ||||
| // Namespace returns an object that can list and get resources from a given namespace. | ||||
| func (l *dynamicLister) Namespace(namespace string) NamespaceLister { | ||||
| 	return &dynamicNamespaceLister{indexer: l.indexer, namespace: namespace, gvr: l.gvr} | ||||
| } | ||||
|  | ||||
| // dynamicNamespaceLister implements the NamespaceLister interface. | ||||
| type dynamicNamespaceLister struct { | ||||
| 	indexer   cache.Indexer | ||||
| 	namespace string | ||||
| 	gvr       schema.GroupVersionResource | ||||
| } | ||||
|  | ||||
| // List lists all resources in the indexer for a given namespace. | ||||
| func (l *dynamicNamespaceLister) List(selector labels.Selector) (ret []*unstructured.Unstructured, err error) { | ||||
| 	err = cache.ListAllByNamespace(l.indexer, l.namespace, selector, func(m interface{}) { | ||||
| 		ret = append(ret, m.(*unstructured.Unstructured)) | ||||
| 	}) | ||||
| 	return ret, err | ||||
| } | ||||
|  | ||||
| // Get retrieves a resource from the indexer for a given namespace and name. | ||||
| func (l *dynamicNamespaceLister) Get(name string) (*unstructured.Unstructured, 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.(*unstructured.Unstructured), nil | ||||
| } | ||||
| @@ -0,0 +1,257 @@ | ||||
| /* | ||||
| 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 dynamiclister_test | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||||
| 	"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/dynamic/dynamiclister" | ||||
| 	"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  *unstructured.Unstructured | ||||
| 		expectError     bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "scenario 1: gets name-foo1 resource from the indexer from ns-foo namespace", | ||||
| 			existingObjects: []runtime.Object{ | ||||
| 				newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"), | ||||
| 				newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"), | ||||
| 				newUnstructured("group/version", "TheKind", "ns-bar", "name-bar"), | ||||
| 			}, | ||||
| 			namespaceToSync: "ns-foo", | ||||
| 			gvrToSync:       schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"}, | ||||
| 			objectToGet:     "name-foo1", | ||||
| 			expectedObject:  newUnstructured("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{ | ||||
| 				newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"), | ||||
| 				newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"), | ||||
| 				newUnstructured("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 := dynamiclister.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{ | ||||
| 		newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"), | ||||
| 		newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"), | ||||
| 		newUnstructured("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 := []*unstructured.Unstructured{ | ||||
| 		newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"), | ||||
| 		newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"), | ||||
| 	} | ||||
| 	namespaceToList := "ns-foo" | ||||
|  | ||||
| 	// act | ||||
| 	target := dynamiclister.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  *unstructured.Unstructured | ||||
| 		expectError     bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "scenario 1: gets name-foo1 resource from the indexer", | ||||
| 			existingObjects: []runtime.Object{ | ||||
| 				newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"), | ||||
| 				newUnstructured("group/version", "TheKind", "", "name-foo1"), | ||||
| 				newUnstructured("group/version", "TheKind", "ns-bar", "name-bar"), | ||||
| 			}, | ||||
| 			namespaceToSync: "", | ||||
| 			gvrToSync:       schema.GroupVersionResource{Group: "group", Version: "version", Resource: "TheKinds"}, | ||||
| 			objectToGet:     "name-foo1", | ||||
| 			expectedObject:  newUnstructured("group/version", "TheKind", "", "name-foo1"), | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "scenario 2: doesn't get name-foo resource from the indexer from ns-foo namespace", | ||||
| 			existingObjects: []runtime.Object{ | ||||
| 				newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"), | ||||
| 				newUnstructured("group/version", "TheKind", "ns-foo", "name-foo1"), | ||||
| 				newUnstructured("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 := dynamiclister.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{ | ||||
| 		newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"), | ||||
| 		newUnstructured("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 := []*unstructured.Unstructured{ | ||||
| 		newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"), | ||||
| 		newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"), | ||||
| 	} | ||||
|  | ||||
| 	// act | ||||
| 	target := dynamiclister.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 newUnstructured(apiVersion, kind, namespace, name string) *unstructured.Unstructured { | ||||
| 	return &unstructured.Unstructured{ | ||||
| 		Object: map[string]interface{}{ | ||||
| 			"apiVersion": apiVersion, | ||||
| 			"kind":       kind, | ||||
| 			"metadata": map[string]interface{}{ | ||||
| 				"namespace": namespace, | ||||
| 				"name":      name, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func assertListOrDie(expected, actual []*unstructured.Unstructured, 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()) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 k8s-ci-robot
					k8s-ci-robot