diff --git a/real_cluster_script.go b/real_cluster_script.go index 139f9146f2e..83dd3036a42 100644 --- a/real_cluster_script.go +++ b/real_cluster_script.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "fmt" - "os" + "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -16,6 +16,7 @@ import ( corev1ac "k8s.io/client-go/applyconfigurations/core/v1" metav1ac "k8s.io/client-go/applyconfigurations/meta/v1" "k8s.io/client-go/discovery" + "k8s.io/client-go/discovery/cached/disk" "k8s.io/client-go/dynamic" clientset "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" @@ -24,17 +25,12 @@ import ( "sigs.k8s.io/structured-merge-diff/v4/typed" ) -// This is a small hacky script that tests the extract config against a real cluster -// to demonstrate a bug I'm seeing with the GVKParser where it claims there -// are duplicate GVK entries -// TODO: don't commit this, delete it before merging. -// Any and all functionality from this script should be captured in the appropriate integration test. func main() { + fmt.Println("vim-go") defaultNS := "default" mydep := "mydep" mgr := "mymanager" - // TODO: make this an arg - kubeconfig := os.Getenv("KUBECONFIG") + kubeconfig := "/usr/local/google/home/kevindelgado/.kube/config" config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { @@ -173,7 +169,10 @@ func main() { fmt.Printf("appliedGuestbook = %+v\n", appliedGuestbook) fmt.Println("---") - discoveryClient := discovery.NewDiscoveryClientForConfigOrDie(config) + discoveryClient, err := disk.NewCachedDiscoveryClientForConfig(config, ".", ".", time.Hour) + if err != nil { + panic(err) + } //// OLD WAY //extractedGuestbook, err := extractUnstructured(appliedGuestbook, guestbookGVR, discoveryClient, mgr) //if err != nil { @@ -187,6 +186,8 @@ func main() { if err != nil { panic(err) } + // do it again see if we get a 304 + extractedGuestbook, err = extractor.ExtractUnstructured(appliedGuestbook, mgr) fmt.Println("---") fmt.Printf("extractedGuestbook = %+v\n", extractedGuestbook) diff --git a/staging/src/k8s.io/client-go/applyconfigurations/meta/v1/unstructured.go b/staging/src/k8s.io/client-go/applyconfigurations/meta/v1/unstructured.go index 043276d142f..9764d83c288 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/meta/v1/unstructured.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/meta/v1/unstructured.go @@ -1,6 +1,10 @@ package v1 import ( + "crypto/sha512" + "encoding/json" + "fmt" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/managedfields" @@ -26,7 +30,11 @@ type objectTypeCache interface { // (i.e. it downloads the OpenAPISchema every time) // Useful during the proof-of-concept stage until we agree on a caching solution. type nonCachingObjectTypeCache struct { - discoveryClient *discovery.DiscoveryClient + // 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 @@ -36,17 +44,47 @@ func (c *nonCachingObjectTypeCache) objectTypeForGVK(gvk schema.GroupVersionKind return nil, err } - models, err := proto.NewOpenAPIData(doc) + docBytes, err := json.Marshal(doc) if err != nil { return nil, err } + docHash := sha512.Sum512(docBytes) - gvkParser, err := fieldmanager.NewGVKParser(models, false) - if err != nil { - return nil, err + // cache hit + if c.docHash == docHash { + 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 + } else { + fmt.Println("cache miss") + // cache miss + models, err := proto.NewOpenAPIData(doc) + if err != nil { + return nil, err + } + + gvkParser, err := fieldmanager.NewGVKParser(models, false) + if err != nil { + return nil, err + } + + objType := gvkParser.Type(gvk) + c.docHash = docHash + c.gvkParser = gvkParser + c.typeForGVK = map[schema.GroupVersionKind]*typed.ParseableType{ + gvk: objType, + } + + return objType, nil } - - return gvkParser.Type(gvk), nil } type extractor struct { @@ -55,9 +93,11 @@ type extractor struct { // NewUnstructuredExtractor creates the extractor with which you can extract the applied configuration // for a given manager from an unstructured object. -func NewUnstructuredExtractor(dc *discovery.DiscoveryClient) UnstructuredExtractor { +func NewUnstructuredExtractor(dc discovery.DiscoveryInterface) UnstructuredExtractor { return &extractor{ - cache: &nonCachingObjectTypeCache{dc}, + cache: &nonCachingObjectTypeCache{ + discoveryClient: dc, + }, } } diff --git a/test/integration/client/dynamic_client_test.go b/test/integration/client/dynamic_client_test.go index 50e5969e291..193f0c345fd 100644 --- a/test/integration/client/dynamic_client_test.go +++ b/test/integration/client/dynamic_client_test.go @@ -244,6 +244,7 @@ func TestUnstructuredExtract(t *testing.T) { t.Fatalf("failed to marshal pod into bytes: %v", err) } + // apply the unstructured object to the cluster actual, err := dynamicClient.Resource(resource).Namespace("default").Patch( context.TODO(), name,