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