unstructured.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. /*
  2. Copyright 2021 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package v1
  14. import (
  15. "fmt"
  16. "sync"
  17. "time"
  18. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  19. "k8s.io/apimachinery/pkg/runtime/schema"
  20. "k8s.io/apimachinery/pkg/util/managedfields"
  21. "k8s.io/client-go/discovery"
  22. "k8s.io/kube-openapi/pkg/util/proto"
  23. "sigs.k8s.io/structured-merge-diff/v4/typed"
  24. )
  25. // openAPISchemaTTL is how frequently we need to check
  26. // whether the open API schema has changed or not.
  27. const openAPISchemaTTL = time.Minute
  28. // UnstructuredExtractor enables extracting the applied configuration state from object for fieldManager into an
  29. // unstructured object type.
  30. type UnstructuredExtractor interface {
  31. Extract(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error)
  32. ExtractStatus(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error)
  33. }
  34. // gvkParserCache caches the GVKParser in order to prevent from having to repeatedly
  35. // parse the models from the open API schema when the schema itself changes infrequently.
  36. type gvkParserCache struct {
  37. // discoveryClient is the client for retrieving the openAPI document and checking
  38. // whether the document has changed recently
  39. discoveryClient discovery.DiscoveryInterface
  40. // mu protects the gvkParser
  41. mu sync.Mutex
  42. // gvkParser retrieves the objectType for a given gvk
  43. gvkParser *managedfields.GvkParser
  44. // lastChecked is the last time we checked if the openAPI doc has changed.
  45. lastChecked time.Time
  46. }
  47. // regenerateGVKParser builds the parser from the raw OpenAPI schema.
  48. func regenerateGVKParser(dc discovery.DiscoveryInterface) (*managedfields.GvkParser, error) {
  49. doc, err := dc.OpenAPISchema()
  50. if err != nil {
  51. return nil, err
  52. }
  53. models, err := proto.NewOpenAPIData(doc)
  54. if err != nil {
  55. return nil, err
  56. }
  57. return managedfields.NewGVKParser(models, false)
  58. }
  59. // objectTypeForGVK retrieves the typed.ParseableType for a given gvk from the cache
  60. func (c *gvkParserCache) objectTypeForGVK(gvk schema.GroupVersionKind) (*typed.ParseableType, error) {
  61. c.mu.Lock()
  62. defer c.mu.Unlock()
  63. // if the ttl on the openAPISchema has expired,
  64. // regenerate the gvk parser
  65. if time.Since(c.lastChecked) > openAPISchemaTTL {
  66. c.lastChecked = time.Now()
  67. parser, err := regenerateGVKParser(c.discoveryClient)
  68. if err != nil {
  69. return nil, err
  70. }
  71. c.gvkParser = parser
  72. }
  73. return c.gvkParser.Type(gvk), nil
  74. }
  75. type extractor struct {
  76. cache *gvkParserCache
  77. }
  78. // NewUnstructuredExtractor creates the extractor with which you can extract the applied configuration
  79. // for a given manager from an unstructured object.
  80. func NewUnstructuredExtractor(dc discovery.DiscoveryInterface) (UnstructuredExtractor, error) {
  81. parser, err := regenerateGVKParser(dc)
  82. if err != nil {
  83. return nil, fmt.Errorf("failed generating initial GVK Parser: %v", err)
  84. }
  85. return &extractor{
  86. cache: &gvkParserCache{
  87. gvkParser: parser,
  88. discoveryClient: dc,
  89. },
  90. }, nil
  91. }
  92. // Extract extracts the applied configuration owned by fieldManager from an unstructured object.
  93. // Note that the apply configuration itself is also an unstructured object.
  94. func (e *extractor) Extract(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error) {
  95. return e.extractUnstructured(object, fieldManager, "")
  96. }
  97. // ExtractStatus is the same as ExtractUnstructured except
  98. // that it extracts the status subresource applied configuration.
  99. // Experimental!
  100. func (e *extractor) ExtractStatus(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error) {
  101. return e.extractUnstructured(object, fieldManager, "status")
  102. }
  103. func (e *extractor) extractUnstructured(object *unstructured.Unstructured, fieldManager string, subresource string) (*unstructured.Unstructured, error) {
  104. gvk := object.GetObjectKind().GroupVersionKind()
  105. objectType, err := e.cache.objectTypeForGVK(gvk)
  106. if err != nil {
  107. return nil, fmt.Errorf("failed to fetch the objectType: %v", err)
  108. }
  109. result := &unstructured.Unstructured{}
  110. err = managedfields.ExtractInto(object, *objectType, fieldManager, result, subresource)
  111. if err != nil {
  112. return nil, fmt.Errorf("failed calling ExtractInto for unstructured: %v", err)
  113. }
  114. result.SetName(object.GetName())
  115. result.SetNamespace(object.GetNamespace())
  116. result.SetKind(object.GetKind())
  117. result.SetAPIVersion(object.GetAPIVersion())
  118. return result, nil
  119. }