k8s.io/client-go@v0.31.1/applyconfigurations/meta/v1/unstructured.go (about) 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 }