2021-06-25 05:02:01 +00:00
/ *
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 (
"context"
"encoding/json"
"fmt"
2021-12-08 13:50:47 +00:00
"net/http"
2021-06-25 05:02:01 +00:00
"time"
"k8s.io/klog/v2"
metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
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"
2025-01-16 04:11:46 +00:00
"k8s.io/client-go/util/consistencydetector"
"k8s.io/client-go/util/watchlist"
2021-06-25 05:02:01 +00:00
)
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 = metainternalversionscheme . Codecs . WithoutConversion ( )
if config . UserAgent == "" {
config . UserAgent = rest . DefaultKubernetesUserAgent ( )
}
return config
}
// NewForConfigOrDie creates a new metadata client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie ( 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.
2021-12-08 13:50:47 +00:00
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
2021-06-25 05:02:01 +00:00
func NewForConfig ( inConfig * rest . Config ) ( Interface , error ) {
config := ConfigFor ( inConfig )
2021-12-08 13:50:47 +00:00
httpClient , err := rest . HTTPClientFor ( config )
if err != nil {
return nil , err
}
return NewForConfigAndClient ( config , httpClient )
}
// NewForConfigAndClient creates a new metadata client for the given config and http client.
// Note the http client provided takes precedence over the configured transport values.
func NewForConfigAndClient ( inConfig * rest . Config , h * http . Client ) ( Interface , error ) {
config := ConfigFor ( inConfig )
2021-06-25 05:02:01 +00:00
// for serializing the options
config . GroupVersion = & schema . GroupVersion { }
config . APIPath = "/this-value-should-never-be-sent"
2021-12-08 13:50:47 +00:00
restClient , err := rest . RESTClientForConfigAndClient ( config , h )
2021-06-25 05:02:01 +00:00
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 ( ctx context . Context , name string , opts metav1 . DeleteOptions , subresources ... string ) error {
if len ( name ) == 0 {
return fmt . Errorf ( "name is required" )
}
2021-12-08 13:50:47 +00:00
// if DeleteOptions are delivered to Negotiator for serialization,
// HTTP-Request header will bring "Content-Type: application/vnd.kubernetes.protobuf"
// apiextensions-apiserver uses unstructuredNegotiatedSerializer to decode the input,
// server-side will reply with 406 errors.
// The special treatment here is to be compatible with CRD Handler
// see: https://github.com/kubernetes/kubernetes/blob/1a845ccd076bbf1b03420fe694c85a5cd3bd6bed/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go#L843
2021-06-25 05:02:01 +00:00
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 ... ) ... ) .
2021-12-08 13:50:47 +00:00
SetHeader ( "Content-Type" , runtime . ContentTypeJSON ) .
2021-06-25 05:02:01 +00:00
Body ( deleteOptionsByte ) .
Do ( ctx )
return result . Error ( )
}
// DeleteCollection triggers deletion of all resources in the specified scope (namespace or cluster).
func ( c * client ) DeleteCollection ( ctx context . Context , opts metav1 . DeleteOptions , listOptions metav1 . ListOptions ) error {
2021-12-08 13:50:47 +00:00
// See comment on Delete
2021-06-25 05:02:01 +00:00
deleteOptionsByte , err := runtime . Encode ( deleteOptionsCodec . LegacyCodec ( schema . GroupVersion { Version : "v1" } ) , & opts )
if err != nil {
return err
}
result := c . client . client .
Delete ( ) .
AbsPath ( c . makeURLSegments ( "" ) ... ) .
2021-12-08 13:50:47 +00:00
SetHeader ( "Content-Type" , runtime . ContentTypeJSON ) .
2021-06-25 05:02:01 +00:00
Body ( deleteOptionsByte ) .
SpecificallyVersionedParams ( & listOptions , dynamicParameterCodec , versionV1 ) .
Do ( ctx )
return result . Error ( )
}
// Get returns the resource with name from the specified scope (namespace or cluster).
func ( c * client ) Get ( ctx context . Context , 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 ( ctx )
if err := result . Error ( ) ; err != nil {
return nil , err
}
obj , err := result . Get ( )
if runtime . IsNotRegisteredError ( err ) {
2024-05-15 06:54:18 +00:00
klog . FromContext ( ctx ) . V ( 5 ) . Info ( "Could not retrieve PartialObjectMetadata" , "err" , err )
2021-06-25 05:02:01 +00:00
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 ( ctx context . Context , opts metav1 . ListOptions ) ( * metav1 . PartialObjectMetadataList , error ) {
2025-01-16 04:11:46 +00:00
if watchListOptions , hasWatchListOptionsPrepared , watchListOptionsErr := watchlist . PrepareWatchListOptionsFromListOptions ( opts ) ; watchListOptionsErr != nil {
klog . FromContext ( ctx ) . Error ( watchListOptionsErr , "Failed preparing watchlist options, falling back to the standard LIST semantics" , "resource" , c . resource )
} else if hasWatchListOptionsPrepared {
result , err := c . watchList ( ctx , watchListOptions )
if err == nil {
consistencydetector . CheckWatchListFromCacheDataConsistencyIfRequested ( ctx , fmt . Sprintf ( "watchlist request for %v" , c . resource ) , c . list , opts , result )
return result , nil
}
klog . FromContext ( ctx ) . Error ( err , "The watchlist request ended with an error, falling back to the standard LIST semantics" , "resource" , c . resource )
}
result , err := c . list ( ctx , opts )
if err == nil {
consistencydetector . CheckListFromCacheDataConsistencyIfRequested ( ctx , fmt . Sprintf ( "list request for %v" , c . resource ) , c . list , opts , result )
}
return result , err
}
func ( c * client ) list ( ctx context . Context , opts metav1 . ListOptions ) ( * metav1 . PartialObjectMetadataList , error ) {
2021-06-25 05:02:01 +00:00
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 ( ctx )
if err := result . Error ( ) ; err != nil {
return nil , err
}
obj , err := result . Get ( )
if runtime . IsNotRegisteredError ( err ) {
2024-05-15 06:54:18 +00:00
klog . FromContext ( ctx ) . V ( 5 ) . Info ( "Could not retrieve PartialObjectMetadataList" , "err" , err )
2021-06-25 05:02:01 +00:00
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
}
2025-01-16 04:11:46 +00:00
// watchList establishes a watch stream with the server and returns PartialObjectMetadataList.
func ( c * client ) watchList ( ctx context . Context , opts metav1 . ListOptions ) ( * metav1 . PartialObjectMetadataList , error ) {
var timeout time . Duration
if opts . TimeoutSeconds != nil {
timeout = time . Duration ( * opts . TimeoutSeconds ) * time . Second
}
result := & metav1 . PartialObjectMetadataList { }
err := 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 ) .
WatchList ( ctx ) .
Into ( result )
return result , err
}
2021-06-25 05:02:01 +00:00
// Watch finds all changes to the resources in the specified scope (namespace or cluster).
func ( c * client ) Watch ( ctx context . Context , 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 ( ctx )
}
// Patch modifies the named resource in the specified scope (namespace or cluster).
func ( c * client ) Patch ( ctx context . Context , 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 ( ctx )
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
}