...

Source file src/k8s.io/client-go/applyconfigurations/meta/v1/unstructured.go

Documentation: k8s.io/client-go/applyconfigurations/meta/v1

     1  /*
     2  Copyright 2021 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 v1
    18  
    19  import (
    20  	"fmt"
    21  	"sync"
    22  	"time"
    23  
    24  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    25  	"k8s.io/apimachinery/pkg/runtime/schema"
    26  	"k8s.io/apimachinery/pkg/util/managedfields"
    27  	"k8s.io/client-go/discovery"
    28  	"k8s.io/kube-openapi/pkg/util/proto"
    29  	"sigs.k8s.io/structured-merge-diff/v4/typed"
    30  )
    31  
    32  // openAPISchemaTTL is how frequently we need to check
    33  // whether the open API schema has changed or not.
    34  const openAPISchemaTTL = time.Minute
    35  
    36  // UnstructuredExtractor enables extracting the applied configuration state from object for fieldManager into an
    37  // unstructured object type.
    38  type UnstructuredExtractor interface {
    39  	Extract(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error)
    40  	ExtractStatus(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error)
    41  }
    42  
    43  // gvkParserCache caches the GVKParser in order to prevent from having to repeatedly
    44  // parse the models from the open API schema when the schema itself changes infrequently.
    45  type gvkParserCache struct {
    46  	// discoveryClient is the client for retrieving the openAPI document and checking
    47  	// whether the document has changed recently
    48  	discoveryClient discovery.DiscoveryInterface
    49  	// mu protects the gvkParser
    50  	mu sync.Mutex
    51  	// gvkParser retrieves the objectType for a given gvk
    52  	gvkParser *managedfields.GvkParser
    53  	// lastChecked is the last time we checked if the openAPI doc has changed.
    54  	lastChecked time.Time
    55  }
    56  
    57  // regenerateGVKParser builds the parser from the raw OpenAPI schema.
    58  func regenerateGVKParser(dc discovery.DiscoveryInterface) (*managedfields.GvkParser, error) {
    59  	doc, err := dc.OpenAPISchema()
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	models, err := proto.NewOpenAPIData(doc)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	return managedfields.NewGVKParser(models, false)
    70  }
    71  
    72  // objectTypeForGVK retrieves the typed.ParseableType for a given gvk from the cache
    73  func (c *gvkParserCache) objectTypeForGVK(gvk schema.GroupVersionKind) (*typed.ParseableType, error) {
    74  	c.mu.Lock()
    75  	defer c.mu.Unlock()
    76  	// if the ttl on the openAPISchema has expired,
    77  	// regenerate the gvk parser
    78  	if time.Since(c.lastChecked) > openAPISchemaTTL {
    79  		c.lastChecked = time.Now()
    80  		parser, err := regenerateGVKParser(c.discoveryClient)
    81  		if err != nil {
    82  			return nil, err
    83  		}
    84  		c.gvkParser = parser
    85  	}
    86  	return c.gvkParser.Type(gvk), nil
    87  }
    88  
    89  type extractor struct {
    90  	cache *gvkParserCache
    91  }
    92  
    93  // NewUnstructuredExtractor creates the extractor with which you can extract the applied configuration
    94  // for a given manager from an unstructured object.
    95  func NewUnstructuredExtractor(dc discovery.DiscoveryInterface) (UnstructuredExtractor, error) {
    96  	parser, err := regenerateGVKParser(dc)
    97  	if err != nil {
    98  		return nil, fmt.Errorf("failed generating initial GVK Parser: %v", err)
    99  	}
   100  	return &extractor{
   101  		cache: &gvkParserCache{
   102  			gvkParser:       parser,
   103  			discoveryClient: dc,
   104  		},
   105  	}, nil
   106  }
   107  
   108  // Extract extracts the applied configuration owned by fieldManager from an unstructured object.
   109  // Note that the apply configuration itself is also an unstructured object.
   110  func (e *extractor) Extract(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error) {
   111  	return e.extractUnstructured(object, fieldManager, "")
   112  }
   113  
   114  // ExtractStatus is the same as ExtractUnstructured except
   115  // that it extracts the status subresource applied configuration.
   116  // Experimental!
   117  func (e *extractor) ExtractStatus(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error) {
   118  	return e.extractUnstructured(object, fieldManager, "status")
   119  }
   120  
   121  func (e *extractor) extractUnstructured(object *unstructured.Unstructured, fieldManager string, subresource string) (*unstructured.Unstructured, error) {
   122  	gvk := object.GetObjectKind().GroupVersionKind()
   123  	objectType, err := e.cache.objectTypeForGVK(gvk)
   124  	if err != nil {
   125  		return nil, fmt.Errorf("failed to fetch the objectType: %v", err)
   126  	}
   127  	result := &unstructured.Unstructured{}
   128  	err = managedfields.ExtractInto(object, *objectType, fieldManager, result, subresource) //nolint:forbidigo
   129  	if err != nil {
   130  		return nil, fmt.Errorf("failed calling ExtractInto for unstructured: %v", err)
   131  	}
   132  	result.SetName(object.GetName())
   133  	result.SetNamespace(object.GetNamespace())
   134  	result.SetKind(object.GetKind())
   135  	result.SetAPIVersion(object.GetAPIVersion())
   136  	return result, nil
   137  }
   138  

View as plain text