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