diff --git a/applyconfigurations/meta/v1/unstructured.go b/applyconfigurations/meta/v1/unstructured.go index 9764d83c28..67f95026dd 100644 --- a/applyconfigurations/meta/v1/unstructured.go +++ b/applyconfigurations/meta/v1/unstructured.go @@ -1,8 +1,6 @@ package v1 import ( - "crypto/sha512" - "encoding/json" "fmt" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -32,40 +30,25 @@ type objectTypeCache interface { type nonCachingObjectTypeCache struct { // TODO: lock this? discoveryClient discovery.DiscoveryInterface - docHash [sha512.Size]uint8 gvkParser *fieldmanager.GvkParser - typeForGVK map[schema.GroupVersionKind]*typed.ParseableType } // objectTypeForGVK retrieves the typed.ParseableType for a given gvk from the cache func (c *nonCachingObjectTypeCache) objectTypeForGVK(gvk schema.GroupVersionKind) (*typed.ParseableType, error) { - doc, err := c.discoveryClient.OpenAPISchema() - if err != nil { - return nil, err - } - - docBytes, err := json.Marshal(doc) - if err != nil { - return nil, err - } - docHash := sha512.Sum512(docBytes) - // cache hit - if c.docHash == docHash { + if !c.discoveryClient.HasOpenAPISchemaChanged() && c.gvkParser != nil { + // cache hit fmt.Println("cache hit") - fmt.Printf("docHash = %+v\n", docHash) - objType, ok := c.typeForGVK[gvk] - if ok { - fmt.Println("gvk recognized") - fmt.Printf("gvk = %+v\n", gvk) - return objType, nil - } - objType = c.gvkParser.Type(gvk) - c.typeForGVK[gvk] = objType - return objType, nil + fmt.Printf("gvk = %+v\n", gvk) + return c.gvkParser.Type(gvk), nil } else { - fmt.Println("cache miss") // cache miss + fmt.Println("cache miss") + fmt.Printf("gvk = %+v\n", gvk) + doc, err := c.discoveryClient.OpenAPISchema() + if err != nil { + return nil, err + } models, err := proto.NewOpenAPIData(doc) if err != nil { return nil, err @@ -77,11 +60,7 @@ func (c *nonCachingObjectTypeCache) objectTypeForGVK(gvk schema.GroupVersionKind } objType := gvkParser.Type(gvk) - c.docHash = docHash c.gvkParser = gvkParser - c.typeForGVK = map[schema.GroupVersionKind]*typed.ParseableType{ - gvk: objType, - } return objType, nil } diff --git a/discovery/cached/disk/cached_discovery.go b/discovery/cached/disk/cached_discovery.go index 6a35dcc604..61acab9709 100644 --- a/discovery/cached/disk/cached_discovery.go +++ b/discovery/cached/disk/cached_discovery.go @@ -240,6 +240,10 @@ func (d *CachedDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) { return d.delegate.OpenAPISchema() } +func (d *CachedDiscoveryClient) HasOpenAPISchemaChanged() bool { + return d.delegate.HasOpenAPISchemaChanged() +} + // Fresh is supposed to tell the caller whether or not to retry if the cache // fails to find something (false = retry, true = no need to retry). func (d *CachedDiscoveryClient) Fresh() bool { diff --git a/discovery/discovery_client.go b/discovery/discovery_client.go index 87de32970e..87f79dc46e 100644 --- a/discovery/discovery_client.go +++ b/discovery/discovery_client.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + "net/http" "net/url" "sort" "strings" @@ -125,6 +126,8 @@ type ServerVersionInterface interface { type OpenAPISchemaInterface interface { // OpenAPISchema retrieves and parses the swagger API schema the server supports. OpenAPISchema() (*openapi_v2.Document, error) + + HasOpenAPISchemaChanged() bool } // DiscoveryClient implements the functions that discover server-supported API groups, @@ -133,6 +136,7 @@ type DiscoveryClient struct { restClient restclient.Interface LegacyPrefix string + etag string } // Convert metav1.APIVersions to metav1.APIGroup. APIVersions is used by legacy v1, so @@ -419,9 +423,21 @@ func (d *DiscoveryClient) ServerVersion() (*version.Info, error) { return &info, nil } +func (d *DiscoveryClient) HasOpenAPISchemaChanged() bool { + result := d.restClient.Verb("HEAD").AbsPath("/openapi/v2").SetHeader("If-None-Match", d.etag).SetHeader("Accept", mimePb).Do(context.TODO()) + var status int + result.StatusCode(&status) + if status == http.StatusNotModified { + return false + } + return true +} + // OpenAPISchema fetches the open api schema using a rest client and parses the proto. func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) { - data, err := d.restClient.Get().AbsPath("/openapi/v2").SetHeader("Accept", mimePb).Do(context.TODO()).Raw() + result := d.restClient.Get().AbsPath("/openapi/v2").SetHeader("Accept", mimePb).Do(context.TODO()) + d.etag = result.Etag() + data, err := result.Raw() if err != nil { if errors.IsForbidden(err) || errors.IsNotFound(err) || errors.IsNotAcceptable(err) { // single endpoint not found/registered in old server, try to fetch old endpoint diff --git a/rest/request.go b/rest/request.go index e5a8100be2..caaf737ec5 100644 --- a/rest/request.go +++ b/rest/request.go @@ -1062,6 +1062,12 @@ func (r *Request) DoRaw(ctx context.Context) ([]byte, error) { // transformResponse converts an API response into a structured API object func (r *Request) transformResponse(resp *http.Response, req *http.Request) Result { + var etag string + etagHeader, ok := resp.Header["Etag"] + if ok && len(etagHeader) == 1 { + etag = etagHeader[0] + } + var body []byte if resp.Body != nil { data, err := ioutil.ReadAll(resp.Body) @@ -1146,6 +1152,7 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request) Resu statusCode: resp.StatusCode, decoder: decoder, warnings: handleWarnings(resp.Header, r.warningHandler), + etag: etag, } } @@ -1272,6 +1279,7 @@ type Result struct { contentType string err error statusCode int + etag string decoder runtime.Decoder } @@ -1308,6 +1316,10 @@ func (r Result) Get() (runtime.Object, error) { return out, nil } +func (r Result) Etag() string { + return r.etag +} + // StatusCode returns the HTTP status code of the request. (Only valid if no // error was returned.) func (r Result) StatusCode(statusCode *int) Result {