k8s.io/client-go@v0.31.1/metadata/metadata.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package metadata 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "net/http" 24 "time" 25 26 "k8s.io/klog/v2" 27 28 metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/runtime/schema" 32 "k8s.io/apimachinery/pkg/runtime/serializer" 33 "k8s.io/apimachinery/pkg/types" 34 "k8s.io/apimachinery/pkg/watch" 35 "k8s.io/client-go/rest" 36 ) 37 38 var deleteScheme = runtime.NewScheme() 39 var parameterScheme = runtime.NewScheme() 40 var deleteOptionsCodec = serializer.NewCodecFactory(deleteScheme) 41 var dynamicParameterCodec = runtime.NewParameterCodec(parameterScheme) 42 43 var versionV1 = schema.GroupVersion{Version: "v1"} 44 45 func init() { 46 metav1.AddToGroupVersion(parameterScheme, versionV1) 47 metav1.AddToGroupVersion(deleteScheme, versionV1) 48 } 49 50 // Client allows callers to retrieve the object metadata for any 51 // Kubernetes-compatible API endpoint. The client uses the 52 // meta.k8s.io/v1 PartialObjectMetadata resource to more efficiently 53 // retrieve just the necessary metadata, but on older servers 54 // (Kubernetes 1.14 and before) will retrieve the object and then 55 // convert the metadata. 56 type Client struct { 57 client *rest.RESTClient 58 } 59 60 var _ Interface = &Client{} 61 62 // ConfigFor returns a copy of the provided config with the 63 // appropriate metadata client defaults set. 64 func ConfigFor(inConfig *rest.Config) *rest.Config { 65 config := rest.CopyConfig(inConfig) 66 config.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json" 67 config.ContentType = "application/vnd.kubernetes.protobuf" 68 config.NegotiatedSerializer = metainternalversionscheme.Codecs.WithoutConversion() 69 if config.UserAgent == "" { 70 config.UserAgent = rest.DefaultKubernetesUserAgent() 71 } 72 return config 73 } 74 75 // NewForConfigOrDie creates a new metadata client for the given config and 76 // panics if there is an error in the config. 77 func NewForConfigOrDie(c *rest.Config) Interface { 78 ret, err := NewForConfig(c) 79 if err != nil { 80 panic(err) 81 } 82 return ret 83 } 84 85 // NewForConfig creates a new metadata client that can retrieve object 86 // metadata details about any Kubernetes object (core, aggregated, or custom 87 // resource based) in the form of PartialObjectMetadata objects, or returns 88 // an error. 89 // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 90 // where httpClient was generated with rest.HTTPClientFor(c). 91 func NewForConfig(inConfig *rest.Config) (Interface, error) { 92 config := ConfigFor(inConfig) 93 94 httpClient, err := rest.HTTPClientFor(config) 95 if err != nil { 96 return nil, err 97 } 98 return NewForConfigAndClient(config, httpClient) 99 } 100 101 // NewForConfigAndClient creates a new metadata client for the given config and http client. 102 // Note the http client provided takes precedence over the configured transport values. 103 func NewForConfigAndClient(inConfig *rest.Config, h *http.Client) (Interface, error) { 104 config := ConfigFor(inConfig) 105 // for serializing the options 106 config.GroupVersion = &schema.GroupVersion{} 107 config.APIPath = "/this-value-should-never-be-sent" 108 109 restClient, err := rest.RESTClientForConfigAndClient(config, h) 110 if err != nil { 111 return nil, err 112 } 113 114 return &Client{client: restClient}, nil 115 } 116 117 type client struct { 118 client *Client 119 namespace string 120 resource schema.GroupVersionResource 121 } 122 123 // Resource returns an interface that can access cluster or namespace 124 // scoped instances of resource. 125 func (c *Client) Resource(resource schema.GroupVersionResource) Getter { 126 return &client{client: c, resource: resource} 127 } 128 129 // Namespace returns an interface that can access namespace-scoped instances of the 130 // provided resource. 131 func (c *client) Namespace(ns string) ResourceInterface { 132 ret := *c 133 ret.namespace = ns 134 return &ret 135 } 136 137 // Delete removes the provided resource from the server. 138 func (c *client) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error { 139 if len(name) == 0 { 140 return fmt.Errorf("name is required") 141 } 142 // if DeleteOptions are delivered to Negotiator for serialization, 143 // HTTP-Request header will bring "Content-Type: application/vnd.kubernetes.protobuf" 144 // apiextensions-apiserver uses unstructuredNegotiatedSerializer to decode the input, 145 // server-side will reply with 406 errors. 146 // The special treatment here is to be compatible with CRD Handler 147 // see: https://github.com/kubernetes/kubernetes/blob/1a845ccd076bbf1b03420fe694c85a5cd3bd6bed/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go#L843 148 deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts) 149 if err != nil { 150 return err 151 } 152 153 result := c.client.client. 154 Delete(). 155 AbsPath(append(c.makeURLSegments(name), subresources...)...). 156 SetHeader("Content-Type", runtime.ContentTypeJSON). 157 Body(deleteOptionsByte). 158 Do(ctx) 159 return result.Error() 160 } 161 162 // DeleteCollection triggers deletion of all resources in the specified scope (namespace or cluster). 163 func (c *client) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error { 164 // See comment on Delete 165 deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), &opts) 166 if err != nil { 167 return err 168 } 169 170 result := c.client.client. 171 Delete(). 172 AbsPath(c.makeURLSegments("")...). 173 SetHeader("Content-Type", runtime.ContentTypeJSON). 174 Body(deleteOptionsByte). 175 SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1). 176 Do(ctx) 177 return result.Error() 178 } 179 180 // Get returns the resource with name from the specified scope (namespace or cluster). 181 func (c *client) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) { 182 if len(name) == 0 { 183 return nil, fmt.Errorf("name is required") 184 } 185 result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...). 186 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"). 187 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1). 188 Do(ctx) 189 if err := result.Error(); err != nil { 190 return nil, err 191 } 192 obj, err := result.Get() 193 if runtime.IsNotRegisteredError(err) { 194 klog.FromContext(ctx).V(5).Info("Could not retrieve PartialObjectMetadata", "err", err) 195 rawBytes, err := result.Raw() 196 if err != nil { 197 return nil, err 198 } 199 var partial metav1.PartialObjectMetadata 200 if err := json.Unmarshal(rawBytes, &partial); err != nil { 201 return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err) 202 } 203 if !isLikelyObjectMetadata(&partial) { 204 return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema: %#v", partial) 205 } 206 partial.TypeMeta = metav1.TypeMeta{} 207 return &partial, nil 208 } 209 if err != nil { 210 return nil, err 211 } 212 partial, ok := obj.(*metav1.PartialObjectMetadata) 213 if !ok { 214 return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj) 215 } 216 return partial, nil 217 } 218 219 // List returns all resources within the specified scope (namespace or cluster). 220 func (c *client) List(ctx context.Context, opts metav1.ListOptions) (*metav1.PartialObjectMetadataList, error) { 221 result := c.client.client.Get().AbsPath(c.makeURLSegments("")...). 222 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"). 223 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1). 224 Do(ctx) 225 if err := result.Error(); err != nil { 226 return nil, err 227 } 228 obj, err := result.Get() 229 if runtime.IsNotRegisteredError(err) { 230 klog.FromContext(ctx).V(5).Info("Could not retrieve PartialObjectMetadataList", "err", err) 231 rawBytes, err := result.Raw() 232 if err != nil { 233 return nil, err 234 } 235 var partial metav1.PartialObjectMetadataList 236 if err := json.Unmarshal(rawBytes, &partial); err != nil { 237 return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadataList: %v", err) 238 } 239 partial.TypeMeta = metav1.TypeMeta{} 240 return &partial, nil 241 } 242 if err != nil { 243 return nil, err 244 } 245 partial, ok := obj.(*metav1.PartialObjectMetadataList) 246 if !ok { 247 return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj) 248 } 249 return partial, nil 250 } 251 252 // Watch finds all changes to the resources in the specified scope (namespace or cluster). 253 func (c *client) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { 254 var timeout time.Duration 255 if opts.TimeoutSeconds != nil { 256 timeout = time.Duration(*opts.TimeoutSeconds) * time.Second 257 } 258 opts.Watch = true 259 return c.client.client.Get(). 260 AbsPath(c.makeURLSegments("")...). 261 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"). 262 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1). 263 Timeout(timeout). 264 Watch(ctx) 265 } 266 267 // Patch modifies the named resource in the specified scope (namespace or cluster). 268 func (c *client) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*metav1.PartialObjectMetadata, error) { 269 if len(name) == 0 { 270 return nil, fmt.Errorf("name is required") 271 } 272 result := c.client.client. 273 Patch(pt). 274 AbsPath(append(c.makeURLSegments(name), subresources...)...). 275 Body(data). 276 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"). 277 SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1). 278 Do(ctx) 279 if err := result.Error(); err != nil { 280 return nil, err 281 } 282 obj, err := result.Get() 283 if runtime.IsNotRegisteredError(err) { 284 rawBytes, err := result.Raw() 285 if err != nil { 286 return nil, err 287 } 288 var partial metav1.PartialObjectMetadata 289 if err := json.Unmarshal(rawBytes, &partial); err != nil { 290 return nil, fmt.Errorf("unable to decode returned object as PartialObjectMetadata: %v", err) 291 } 292 if !isLikelyObjectMetadata(&partial) { 293 return nil, fmt.Errorf("object does not appear to match the ObjectMeta schema") 294 } 295 partial.TypeMeta = metav1.TypeMeta{} 296 return &partial, nil 297 } 298 if err != nil { 299 return nil, err 300 } 301 partial, ok := obj.(*metav1.PartialObjectMetadata) 302 if !ok { 303 return nil, fmt.Errorf("unexpected object, expected PartialObjectMetadata but got %T", obj) 304 } 305 return partial, nil 306 } 307 308 func (c *client) makeURLSegments(name string) []string { 309 url := []string{} 310 if len(c.resource.Group) == 0 { 311 url = append(url, "api") 312 } else { 313 url = append(url, "apis", c.resource.Group) 314 } 315 url = append(url, c.resource.Version) 316 317 if len(c.namespace) > 0 { 318 url = append(url, "namespaces", c.namespace) 319 } 320 url = append(url, c.resource.Resource) 321 322 if len(name) > 0 { 323 url = append(url, name) 324 } 325 326 return url 327 } 328 329 func isLikelyObjectMetadata(meta *metav1.PartialObjectMetadata) bool { 330 return len(meta.UID) > 0 || !meta.CreationTimestamp.IsZero() || len(meta.Name) > 0 || len(meta.GenerateName) > 0 331 }