mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Merge pull request #77819 from smarterclayton/client
Add a metadata client to client-go that can read PartialObjectMetadata
This commit is contained in:
commit
024c7bd84f
@ -59,6 +59,7 @@ filegroup(
|
||||
"//staging/src/k8s.io/client-go/listers/storage/v1:all-srcs",
|
||||
"//staging/src/k8s.io/client-go/listers/storage/v1alpha1:all-srcs",
|
||||
"//staging/src/k8s.io/client-go/listers/storage/v1beta1:all-srcs",
|
||||
"//staging/src/k8s.io/client-go/metadata:all-srcs",
|
||||
"//staging/src/k8s.io/client-go/pkg/apis/clientauthentication:all-srcs",
|
||||
"//staging/src/k8s.io/client-go/pkg/version:all-srcs",
|
||||
"//staging/src/k8s.io/client-go/plugin/pkg/client/auth:all-srcs",
|
||||
|
51
staging/src/k8s.io/client-go/metadata/BUILD
Normal file
51
staging/src/k8s.io/client-go/metadata/BUILD
Normal file
@ -0,0 +1,51 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"interface.go",
|
||||
"metadata.go",
|
||||
],
|
||||
importmap = "k8s.io/kubernetes/vendor/k8s.io/client-go/metadata",
|
||||
importpath = "k8s.io/client-go/metadata",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1: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/runtime/serializer:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/klog:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["metadata_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1: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/rest: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"],
|
||||
)
|
47
staging/src/k8s.io/client-go/metadata/interface.go
Normal file
47
staging/src/k8s.io/client-go/metadata/interface.go
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2016 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 metadata
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
// Interface allows a caller to get the metadata (in the form of PartialObjectMetadata objects)
|
||||
// from any Kubernetes compatible resource API.
|
||||
type Interface interface {
|
||||
Resource(resource schema.GroupVersionResource) Getter
|
||||
}
|
||||
|
||||
// ResourceInterface contains the set of methods that may be invoked on objects by their metadata.
|
||||
// Update is not supported by the server, but Patch can be used for the actions Update would handle.
|
||||
type ResourceInterface interface {
|
||||
Delete(name string, options *metav1.DeleteOptions, subresources ...string) error
|
||||
DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error
|
||||
Get(name string, options metav1.GetOptions, subresources ...string) (*metav1.PartialObjectMetadata, error)
|
||||
List(opts metav1.ListOptions) (*metav1.PartialObjectMetadataList, error)
|
||||
Watch(opts metav1.ListOptions) (watch.Interface, error)
|
||||
Patch(name string, pt types.PatchType, data []byte, options metav1.PatchOptions, subresources ...string) (*metav1.PartialObjectMetadata, error)
|
||||
}
|
||||
|
||||
// Getter handles both namespaced and non-namespaced resource types consistently.
|
||||
type Getter interface {
|
||||
Namespace(string) ResourceInterface
|
||||
ResourceInterface
|
||||
}
|
312
staging/src/k8s.io/client-go/metadata/metadata.go
Normal file
312
staging/src/k8s.io/client-go/metadata/metadata.go
Normal file
@ -0,0 +1,312 @@
|
||||
/*
|
||||
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 metadata
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog"
|
||||
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"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/rest"
|
||||
)
|
||||
|
||||
var deleteScheme = runtime.NewScheme()
|
||||
var parameterScheme = runtime.NewScheme()
|
||||
var deleteOptionsCodec = serializer.NewCodecFactory(deleteScheme)
|
||||
var dynamicParameterCodec = runtime.NewParameterCodec(parameterScheme)
|
||||
|
||||
var versionV1 = schema.GroupVersion{Version: "v1"}
|
||||
|
||||
func init() {
|
||||
metav1.AddToGroupVersion(parameterScheme, versionV1)
|
||||
metav1.AddToGroupVersion(deleteScheme, versionV1)
|
||||
}
|
||||
|
||||
// Client allows callers to retrieve the object metadata for any
|
||||
// Kubernetes-compatible API endpoint. The client uses the
|
||||
// meta.k8s.io/v1 PartialObjectMetadata resource to more efficiently
|
||||
// retrieve just the necessary metadata, but on older servers
|
||||
// (Kubernetes 1.14 and before) will retrieve the object and then
|
||||
// convert the metadata.
|
||||
type Client struct {
|
||||
client *rest.RESTClient
|
||||
}
|
||||
|
||||
var _ Interface = &Client{}
|
||||
|
||||
// ConfigFor returns a copy of the provided config with the
|
||||
// appropriate metadata client defaults set.
|
||||
func ConfigFor(inConfig *rest.Config) *rest.Config {
|
||||
config := rest.CopyConfig(inConfig)
|
||||
config.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json"
|
||||
config.ContentType = "application/vnd.kubernetes.protobuf"
|
||||
config.NegotiatedSerializer = metainternalversion.Codecs.WithoutConversion()
|
||||
if config.UserAgent == "" {
|
||||
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
// NewConfigOrDie creates a new metadata client for the given config and
|
||||
// panics if there is an error in the config.
|
||||
func NewConfigOrDie(c *rest.Config) Interface {
|
||||
ret, err := NewForConfig(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// NewForConfig creates a new metadata client that can retrieve object
|
||||
// metadata details about any Kubernetes object (core, aggregated, or custom
|
||||
// resource based) in the form of PartialObjectMetadata objects, or returns
|
||||
// an error.
|
||||
func NewForConfig(inConfig *rest.Config) (Interface, error) {
|
||||
config := ConfigFor(inConfig)
|
||||
// for serializing the options
|
||||
config.GroupVersion = &schema.GroupVersion{}
|
||||
config.APIPath = "/this-value-should-never-be-sent"
|
||||
|
||||
restClient, err := rest.RESTClientFor(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{client: restClient}, nil
|
||||
}
|
||||
|
||||
type client struct {
|
||||
client *Client
|
||||
namespace string
|
||||
resource schema.GroupVersionResource
|
||||
}
|
||||
|
||||
// Resource returns an interface that can access cluster or namespace
|
||||
// scoped instances of resource.
|
||||
func (c *Client) Resource(resource schema.GroupVersionResource) Getter {
|
||||
return &client{client: c, resource: resource}
|
||||
}
|
||||
|
||||
// Namespace returns an interface that can access namespace-scoped instances of the
|
||||
// provided resource.
|
||||
func (c *client) Namespace(ns string) ResourceInterface {
|
||||
ret := *c
|
||||
ret.namespace = ns
|
||||
return &ret
|
||||
}
|
||||
|
||||
// Delete removes the provided resource from the server.
|
||||
func (c *client) Delete(name string, opts *metav1.DeleteOptions, subresources ...string) error {
|
||||
if len(name) == 0 {
|
||||
return fmt.Errorf("name is required")
|
||||
}
|
||||
if opts == nil {
|
||||
opts = &metav1.DeleteOptions{}
|
||||
}
|
||||
deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := c.client.client.
|
||||
Delete().
|
||||
AbsPath(append(c.makeURLSegments(name), subresources...)...).
|
||||
Body(deleteOptionsByte).
|
||||
Do()
|
||||
return result.Error()
|
||||
}
|
||||
|
||||
// DeleteCollection triggers deletion of all resources in the specified scope (namespace or cluster).
|
||||
func (c *client) DeleteCollection(opts *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
|
||||
if opts == nil {
|
||||
opts = &metav1.DeleteOptions{}
|
||||
}
|
||||
deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := c.client.client.
|
||||
Delete().
|
||||
AbsPath(c.makeURLSegments("")...).
|
||||
Body(deleteOptionsByte).
|
||||
SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1).
|
||||
Do()
|
||||
return result.Error()
|
||||
}
|
||||
|
||||
// Get returns the resource with name from the specified scope (namespace or cluster).
|
||||
func (c *client) Get(name string, opts metav1.GetOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
|
||||
if len(name) == 0 {
|
||||
return nil, fmt.Errorf("name is required")
|
||||
}
|
||||
result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).
|
||||
SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
|
||||
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
|
||||
Do()
|
||||
if err := result.Error(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj, err := result.Get()
|
||||
if runtime.IsNotRegisteredError(err) {
|
||||
klog.V(5).Infof("Unable to retrieve PartialObjectMetadata: %#v", err)
|
||||
rawBytes, err := result.Raw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var partial metav1.PartialObjectMetadata
|
||||
if err := json.Unmarshal(rawBytes, &partial); err != nil {
|
||||
return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err)
|
||||
}
|
||||
if !isLikelyObjectMetadata(&partial) {
|
||||
return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema: %#v", partial)
|
||||
}
|
||||
partial.TypeMeta = metav1.TypeMeta{}
|
||||
return &partial, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
partial, ok := obj.(*metav1.PartialObjectMetadata)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
|
||||
}
|
||||
return partial, nil
|
||||
}
|
||||
|
||||
// List returns all resources within the specified scope (namespace or cluster).
|
||||
func (c *client) List(opts metav1.ListOptions) (*metav1.PartialObjectMetadataList, error) {
|
||||
result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).
|
||||
SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json").
|
||||
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
|
||||
Do()
|
||||
if err := result.Error(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj, err := result.Get()
|
||||
if runtime.IsNotRegisteredError(err) {
|
||||
klog.V(5).Infof("Unable to retrieve PartialObjectMetadataList: %#v", err)
|
||||
rawBytes, err := result.Raw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var partial metav1.PartialObjectMetadataList
|
||||
if err := json.Unmarshal(rawBytes, &partial); err != nil {
|
||||
return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadataList: %v", err)
|
||||
}
|
||||
partial.TypeMeta = metav1.TypeMeta{}
|
||||
return &partial, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
partial, ok := obj.(*metav1.PartialObjectMetadataList)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
|
||||
}
|
||||
return partial, nil
|
||||
}
|
||||
|
||||
// Watch finds all changes to the resources in the specified scope (namespace or cluster).
|
||||
func (c *client) Watch(opts metav1.ListOptions) (watch.Interface, error) {
|
||||
var timeout time.Duration
|
||||
if opts.TimeoutSeconds != nil {
|
||||
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
|
||||
}
|
||||
opts.Watch = true
|
||||
return c.client.client.Get().
|
||||
AbsPath(c.makeURLSegments("")...).
|
||||
SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
|
||||
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
|
||||
Timeout(timeout).
|
||||
Watch()
|
||||
}
|
||||
|
||||
// Patch modifies the named resource in the specified scope (namespace or cluster).
|
||||
func (c *client) Patch(name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) {
|
||||
if len(name) == 0 {
|
||||
return nil, fmt.Errorf("name is required")
|
||||
}
|
||||
result := c.client.client.
|
||||
Patch(pt).
|
||||
AbsPath(append(c.makeURLSegments(name), subresources...)...).
|
||||
Body(data).
|
||||
SetHeader("Accept", "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json").
|
||||
SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
|
||||
Do()
|
||||
if err := result.Error(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj, err := result.Get()
|
||||
if runtime.IsNotRegisteredError(err) {
|
||||
rawBytes, err := result.Raw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var partial metav1.PartialObjectMetadata
|
||||
if err := json.Unmarshal(rawBytes, &partial); err != nil {
|
||||
return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err)
|
||||
}
|
||||
if !isLikelyObjectMetadata(&partial) {
|
||||
return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema")
|
||||
}
|
||||
partial.TypeMeta = metav1.TypeMeta{}
|
||||
return &partial, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
partial, ok := obj.(*metav1.PartialObjectMetadata)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj)
|
||||
}
|
||||
return partial, nil
|
||||
}
|
||||
|
||||
func (c *client) makeURLSegments(name string) []string {
|
||||
url := []string{}
|
||||
if len(c.resource.Group) == 0 {
|
||||
url = append(url, "api")
|
||||
} else {
|
||||
url = append(url, "apis", c.resource.Group)
|
||||
}
|
||||
url = append(url, c.resource.Version)
|
||||
|
||||
if len(c.namespace) > 0 {
|
||||
url = append(url, "namespaces", c.namespace)
|
||||
}
|
||||
url = append(url, c.resource.Resource)
|
||||
|
||||
if len(name) > 0 {
|
||||
url = append(url, name)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
func isLikelyObjectMetadata(meta *metav1.PartialObjectMetadata) bool {
|
||||
return len(meta.UID) > 0 || !meta.CreationTimestamp.IsZero() || len(meta.Name) > 0 || len(meta.GenerateName) > 0
|
||||
}
|
243
staging/src/k8s.io/client-go/metadata/metadata_test.go
Normal file
243
staging/src/k8s.io/client-go/metadata/metadata_test.go
Normal file
@ -0,0 +1,243 @@
|
||||
/*
|
||||
Copyright 2019 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 metadata
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
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/rest"
|
||||
)
|
||||
|
||||
func TestClient(t *testing.T) {
|
||||
gvr := schema.GroupVersionResource{Group: "group", Version: "v1", Resource: "resource"}
|
||||
|
||||
writeJSON := func(t *testing.T, w http.ResponseWriter, obj runtime.Object) {
|
||||
data, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if _, err := w.Write(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
handler func(t *testing.T, w http.ResponseWriter, req *http.Request)
|
||||
want func(t *testing.T, client *Client)
|
||||
}{
|
||||
{
|
||||
name: "GET is able to convert a JSON object to PartialObjectMetadata",
|
||||
handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) {
|
||||
if req.Header.Get("Accept") != "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json" {
|
||||
t.Fatal(req.Header.Get("Accept"))
|
||||
}
|
||||
if req.Method != "GET" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource/name" {
|
||||
t.Fatal(req.URL.String())
|
||||
}
|
||||
writeJSON(t, w, &corev1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
},
|
||||
})
|
||||
},
|
||||
want: func(t *testing.T, client *Client) {
|
||||
obj, err := client.Resource(gvr).Namespace("ns").Get("name", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expect := &metav1.PartialObjectMetadata{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(expect, obj) {
|
||||
t.Fatal(diff.ObjectReflectDiff(expect, obj))
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "LIST is able to convert a JSON object to PartialObjectMetadata",
|
||||
handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) {
|
||||
if req.Header.Get("Accept") != "application/vnd.kubernetes.protobuf;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1,application/json" {
|
||||
t.Fatal(req.Header.Get("Accept"))
|
||||
}
|
||||
if req.Method != "GET" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource" {
|
||||
t.Fatal(req.URL.String())
|
||||
}
|
||||
writeJSON(t, w, &corev1.PodList{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "PodList",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: "253",
|
||||
},
|
||||
Items: []corev1.Pod{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
want: func(t *testing.T, client *Client) {
|
||||
objs, err := client.Resource(gvr).Namespace("ns").List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if objs.GetResourceVersion() != "253" {
|
||||
t.Fatal(objs)
|
||||
}
|
||||
expect := []metav1.PartialObjectMetadata{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name",
|
||||
Namespace: "ns",
|
||||
},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(expect, objs.Items) {
|
||||
t.Fatal(diff.ObjectReflectDiff(expect, objs.Items))
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "GET fails if the object is JSON and has no kind",
|
||||
handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) {
|
||||
if req.Header.Get("Accept") != "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json" {
|
||||
t.Fatal(req.Header.Get("Accept"))
|
||||
}
|
||||
if req.Method != "GET" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource/name" {
|
||||
t.Fatal(req.URL.String())
|
||||
}
|
||||
writeJSON(t, w, &corev1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "123",
|
||||
},
|
||||
})
|
||||
},
|
||||
want: func(t *testing.T, client *Client) {
|
||||
obj, err := client.Resource(gvr).Namespace("ns").Get("name", metav1.GetOptions{})
|
||||
if err == nil || !runtime.IsMissingKind(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if obj != nil {
|
||||
t.Fatal(obj)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "GET fails if the object is JSON and has no apiVersion",
|
||||
handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) {
|
||||
if req.Header.Get("Accept") != "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json" {
|
||||
t.Fatal(req.Header.Get("Accept"))
|
||||
}
|
||||
if req.Method != "GET" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource/name" {
|
||||
t.Fatal(req.URL.String())
|
||||
}
|
||||
writeJSON(t, w, &corev1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "123",
|
||||
},
|
||||
})
|
||||
},
|
||||
want: func(t *testing.T, client *Client) {
|
||||
obj, err := client.Resource(gvr).Namespace("ns").Get("name", metav1.GetOptions{})
|
||||
if err == nil || !runtime.IsMissingVersion(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if obj != nil {
|
||||
t.Fatal(obj)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "GET fails if the object is JSON and not clearly metadata",
|
||||
handler: func(t *testing.T, w http.ResponseWriter, req *http.Request) {
|
||||
if req.Header.Get("Accept") != "application/vnd.kubernetes.protobuf;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1,application/json" {
|
||||
t.Fatal(req.Header.Get("Accept"))
|
||||
}
|
||||
if req.Method != "GET" && req.URL.String() != "/apis/group/v1/namespaces/ns/resource/name" {
|
||||
t.Fatal(req.URL.String())
|
||||
}
|
||||
writeJSON(t, w, &corev1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{},
|
||||
})
|
||||
},
|
||||
want: func(t *testing.T, client *Client) {
|
||||
obj, err := client.Resource(gvr).Namespace("ns").Get("name", metav1.GetOptions{})
|
||||
if err == nil || !strings.Contains(err.Error(), "object does not appear to match the ObjectMeta schema") {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if obj != nil {
|
||||
t.Fatal(obj)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { tt.handler(t, w, req) }))
|
||||
defer s.Close()
|
||||
|
||||
cfg := ConfigFor(&rest.Config{Host: s.URL})
|
||||
client := NewConfigOrDie(cfg).(*Client)
|
||||
tt.want(t, client)
|
||||
})
|
||||
}
|
||||
}
|
@ -55,12 +55,14 @@ go_test(
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/protobuf:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/features:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/discovery/cached/disk:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/metadata:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
|
||||
|
@ -30,6 +30,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
@ -48,10 +49,12 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/protobuf"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/dynamic"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/metadata"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/pager"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
@ -403,6 +406,338 @@ func TestNameInFieldSelector(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type callWrapper struct {
|
||||
nested http.RoundTripper
|
||||
req *http.Request
|
||||
resp *http.Response
|
||||
err error
|
||||
}
|
||||
|
||||
func (w *callWrapper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
w.req = req
|
||||
resp, err := w.nested.RoundTrip(req)
|
||||
w.resp = resp
|
||||
w.err = err
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func TestMetadataClient(t *testing.T) {
|
||||
tearDown, config, _, err := fixtures.StartDefaultServer(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tearDown()
|
||||
|
||||
s, clientset, closeFn := setup(t)
|
||||
defer closeFn()
|
||||
|
||||
apiExtensionClient, err := apiextensionsclient.NewForConfig(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dynamicClient, err := dynamic.NewForConfig(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fooCRD := &apiextensionsv1beta1.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foos.cr.bar.com",
|
||||
},
|
||||
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
|
||||
Group: "cr.bar.com",
|
||||
Version: "v1",
|
||||
Scope: apiextensionsv1beta1.NamespaceScoped,
|
||||
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
|
||||
Plural: "foos",
|
||||
Kind: "Foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
fooCRD, err = fixtures.CreateNewCustomResourceDefinition(fooCRD, apiExtensionClient, dynamicClient)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
crdGVR := schema.GroupVersionResource{Group: fooCRD.Spec.Group, Version: fooCRD.Spec.Version, Resource: "foos"}
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
want func(*testing.T)
|
||||
}{
|
||||
{
|
||||
name: "list, get, patch, and delete via metadata client",
|
||||
want: func(t *testing.T) {
|
||||
ns := "metadata-builtin"
|
||||
svc, err := clientset.CoreV1().Services(ns).Create(&v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-1", Annotations: map[string]string{"foo": "bar"}}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create service: %v", err)
|
||||
}
|
||||
|
||||
cfg := metadata.ConfigFor(&restclient.Config{Host: s.URL})
|
||||
wrapper := &callWrapper{}
|
||||
cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
|
||||
wrapper.nested = rt
|
||||
return wrapper
|
||||
})
|
||||
|
||||
client := metadata.NewConfigOrDie(cfg).Resource(v1.SchemeGroupVersion.WithResource("services"))
|
||||
items, err := client.Namespace(ns).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if items.ResourceVersion == "" {
|
||||
t.Fatalf("unexpected items: %#v", items)
|
||||
}
|
||||
if len(items.Items) != 1 {
|
||||
t.Fatalf("unexpected list: %#v", items)
|
||||
}
|
||||
if item := items.Items[0]; item.Name != "test-1" || item.UID != svc.UID || item.Annotations["foo"] != "bar" {
|
||||
t.Fatalf("unexpected object: %#v", item)
|
||||
}
|
||||
|
||||
if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf" {
|
||||
t.Fatalf("unexpected response: %#v", wrapper.resp)
|
||||
}
|
||||
wrapper.resp = nil
|
||||
|
||||
item, err := client.Namespace(ns).Get("test-1", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if item.ResourceVersion == "" || item.UID != svc.UID || item.Annotations["foo"] != "bar" {
|
||||
t.Fatalf("unexpected object: %#v", item)
|
||||
}
|
||||
if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf" {
|
||||
t.Fatalf("unexpected response: %#v", wrapper.resp)
|
||||
}
|
||||
|
||||
item, err = client.Namespace(ns).Patch("test-1", types.MergePatchType, []byte(`{"metadata":{"annotations":{"foo":"baz"}}}`), metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if item.Annotations["foo"] != "baz" {
|
||||
t.Fatalf("unexpected object: %#v", item)
|
||||
}
|
||||
|
||||
if err := client.Namespace(ns).Delete("test-1", &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &item.UID}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := client.Namespace(ns).Get("test-1", metav1.GetOptions{}); !apierrors.IsNotFound(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list, get, patch, and delete via metadata client on a CRD",
|
||||
want: func(t *testing.T) {
|
||||
ns := "metadata-crd"
|
||||
crclient := dynamicClient.Resource(crdGVR).Namespace(ns)
|
||||
cr, err := crclient.Create(&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "cr.bar.com/v1",
|
||||
"kind": "Foo",
|
||||
"spec": map[string]interface{}{"field": 1},
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "test-1",
|
||||
"annotations": map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create cr: %v", err)
|
||||
}
|
||||
|
||||
cfg := metadata.ConfigFor(config)
|
||||
wrapper := &callWrapper{}
|
||||
cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
|
||||
wrapper.nested = rt
|
||||
return wrapper
|
||||
})
|
||||
|
||||
client := metadata.NewConfigOrDie(cfg).Resource(crdGVR)
|
||||
items, err := client.Namespace(ns).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if items.ResourceVersion == "" {
|
||||
t.Fatalf("unexpected items: %#v", items)
|
||||
}
|
||||
if len(items.Items) != 1 {
|
||||
t.Fatalf("unexpected list: %#v", items)
|
||||
}
|
||||
if item := items.Items[0]; item.Name != "test-1" || item.UID != cr.GetUID() || item.Annotations["foo"] != "bar" {
|
||||
t.Fatalf("unexpected object: %#v", item)
|
||||
}
|
||||
|
||||
if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf" {
|
||||
t.Fatalf("unexpected response: %#v", wrapper.resp)
|
||||
}
|
||||
wrapper.resp = nil
|
||||
|
||||
item, err := client.Namespace(ns).Get("test-1", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if item.ResourceVersion == "" || item.UID != cr.GetUID() || item.Annotations["foo"] != "bar" {
|
||||
t.Fatalf("unexpected object: %#v", item)
|
||||
}
|
||||
if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf" {
|
||||
t.Fatalf("unexpected response: %#v", wrapper.resp)
|
||||
}
|
||||
|
||||
item, err = client.Namespace(ns).Patch("test-1", types.MergePatchType, []byte(`{"metadata":{"annotations":{"foo":"baz"}}}`), metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if item.Annotations["foo"] != "baz" {
|
||||
t.Fatalf("unexpected object: %#v", item)
|
||||
}
|
||||
|
||||
if err := client.Namespace(ns).Delete("test-1", &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &item.UID}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := client.Namespace(ns).Get("test-1", metav1.GetOptions{}); !apierrors.IsNotFound(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "watch via metadata client",
|
||||
want: func(t *testing.T) {
|
||||
ns := "metadata-watch"
|
||||
svc, err := clientset.CoreV1().Services(ns).Create(&v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "test-2", Annotations: map[string]string{"foo": "bar"}}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 1000}}}})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create service: %v", err)
|
||||
}
|
||||
if _, err := clientset.CoreV1().Services(ns).Patch("test-2", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`)); err != nil {
|
||||
t.Fatalf("unable to patch cr: %v", err)
|
||||
}
|
||||
|
||||
cfg := metadata.ConfigFor(&restclient.Config{Host: s.URL})
|
||||
wrapper := &callWrapper{}
|
||||
cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
|
||||
wrapper.nested = rt
|
||||
return wrapper
|
||||
})
|
||||
|
||||
client := metadata.NewConfigOrDie(cfg).Resource(v1.SchemeGroupVersion.WithResource("services"))
|
||||
w, err := client.Namespace(ns).Watch(metav1.ListOptions{ResourceVersion: svc.ResourceVersion, Watch: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer w.Stop()
|
||||
var r watch.Event
|
||||
select {
|
||||
case evt, ok := <-w.ResultChan():
|
||||
if !ok {
|
||||
t.Fatal("watch closed")
|
||||
}
|
||||
r = evt
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("no watch event in 5 seconds, bug")
|
||||
}
|
||||
if r.Type != watch.Modified {
|
||||
t.Fatalf("unexpected watch: %#v", r)
|
||||
}
|
||||
item, ok := r.Object.(*metav1.PartialObjectMetadata)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected object: %T", item)
|
||||
}
|
||||
if item.ResourceVersion == "" || item.Name != "test-2" || item.UID != svc.UID || item.Annotations["test"] != "1" {
|
||||
t.Fatalf("unexpected object: %#v", item)
|
||||
}
|
||||
|
||||
if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf;stream=watch" {
|
||||
t.Fatalf("unexpected response: %#v", wrapper.resp)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "watch via metadata client on a CRD",
|
||||
want: func(t *testing.T) {
|
||||
ns := "metadata-watch-crd"
|
||||
crclient := dynamicClient.Resource(crdGVR).Namespace(ns)
|
||||
cr, err := crclient.Create(&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "cr.bar.com/v1",
|
||||
"kind": "Foo",
|
||||
"spec": map[string]interface{}{"field": 1},
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "test-2",
|
||||
"annotations": map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create cr: %v", err)
|
||||
}
|
||||
|
||||
cfg := metadata.ConfigFor(config)
|
||||
client := metadata.NewConfigOrDie(cfg).Resource(crdGVR)
|
||||
|
||||
patched, err := client.Namespace(ns).Patch("test-2", types.MergePatchType, []byte(`{"metadata":{"annotations":{"test":"1"}}}`), metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if patched.GetResourceVersion() == cr.GetResourceVersion() {
|
||||
t.Fatalf("Patch did not modify object: %#v", patched)
|
||||
}
|
||||
|
||||
wrapper := &callWrapper{}
|
||||
cfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
|
||||
wrapper.nested = rt
|
||||
return wrapper
|
||||
})
|
||||
client = metadata.NewConfigOrDie(cfg).Resource(crdGVR)
|
||||
|
||||
w, err := client.Namespace(ns).Watch(metav1.ListOptions{ResourceVersion: cr.GetResourceVersion(), Watch: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer w.Stop()
|
||||
var r watch.Event
|
||||
select {
|
||||
case evt, ok := <-w.ResultChan():
|
||||
if !ok {
|
||||
t.Fatal("watch closed")
|
||||
}
|
||||
r = evt
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("no watch event in 5 seconds, bug")
|
||||
}
|
||||
if r.Type != watch.Modified {
|
||||
t.Fatalf("unexpected watch: %#v", r)
|
||||
}
|
||||
item, ok := r.Object.(*metav1.PartialObjectMetadata)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected object: %T", item)
|
||||
}
|
||||
if item.ResourceVersion == "" || item.Name != "test-2" || item.UID != cr.GetUID() || item.Annotations["test"] != "1" {
|
||||
t.Fatalf("unexpected object: %#v", item)
|
||||
}
|
||||
|
||||
if wrapper.resp == nil || wrapper.resp.Header.Get("Content-Type") != "application/vnd.kubernetes.protobuf;stream=watch" {
|
||||
t.Fatalf("unexpected response: %#v", wrapper.resp)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i := range testcases {
|
||||
tc := testcases[i]
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tc.want(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPICRDProtobuf(t *testing.T) {
|
||||
testNamespace := "test-api-crd-protobuf"
|
||||
tearDown, config, _, err := fixtures.StartDefaultServer(t)
|
||||
|
1
vendor/modules.txt
vendored
1
vendor/modules.txt
vendored
@ -1477,6 +1477,7 @@ k8s.io/client-go/listers/settings/v1alpha1
|
||||
k8s.io/client-go/listers/storage/v1
|
||||
k8s.io/client-go/listers/storage/v1alpha1
|
||||
k8s.io/client-go/listers/storage/v1beta1
|
||||
k8s.io/client-go/metadata
|
||||
k8s.io/client-go/pkg/apis/clientauthentication
|
||||
k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1
|
||||
k8s.io/client-go/pkg/apis/clientauthentication/v1beta1
|
||||
|
Loading…
Reference in New Issue
Block a user