From 6bd4b565e8ccf2bc83e107cfe606289f907ba636 Mon Sep 17 00:00:00 2001 From: Jingfang Liu Date: Thu, 10 Feb 2022 14:12:45 -0800 Subject: [PATCH] Capture the apply status for individual resources after each apply. If the inventory object supports status field, it is updated after setting the inventory list at then of one apply process. BREAKING CHANGE: Update the inventory client and inventory interfaces to pass the apply/reconcile status. --- pkg/apply/prune/prune_test.go | 2 +- pkg/apply/task/inv_set_task.go | 5 +- pkg/inventory/fake-inventory-client.go | 11 ++-- pkg/inventory/inventory-client.go | 44 +++++++++++--- pkg/inventory/inventory-client_test.go | 84 +++++++++++++++++++++++--- pkg/inventory/inventory.go | 3 +- pkg/inventory/inventory_test.go | 7 --- pkg/inventory/inventorycm.go | 46 +++++++++++--- pkg/inventory/inventorycm_test.go | 77 +++++++++++++++++++++++ test/e2e/customprovider/provider.go | 3 +- 10 files changed, 243 insertions(+), 39 deletions(-) create mode 100644 pkg/inventory/inventorycm_test.go diff --git a/pkg/apply/prune/prune_test.go b/pkg/apply/prune/prune_test.go index 4d8bde2f..f1f7a1ad 100644 --- a/pkg/apply/prune/prune_test.go +++ b/pkg/apply/prune/prune_test.go @@ -126,7 +126,7 @@ func createInventoryInfo(children ...*unstructured.Unstructured) inventory.Info inventoryObjCopy := inventoryObj.DeepCopy() wrappedInv := inventory.WrapInventoryObj(inventoryObjCopy) objs := object.UnstructuredSetToObjMetadataSet(children) - if err := wrappedInv.Store(objs); err != nil { + if err := wrappedInv.Store(objs, nil); err != nil { return nil } obj, err := wrappedInv.GetObject() diff --git a/pkg/apply/task/inv_set_task.go b/pkg/apply/task/inv_set_task.go index 51f057a0..4bd92da5 100644 --- a/pkg/apply/task/inv_set_task.go +++ b/pkg/apply/task/inv_set_task.go @@ -114,8 +114,11 @@ func (i *InvSetTask) Start(taskContext *taskrunner.TaskContext) { klog.V(4).Infof("keep in inventory %d invalid objects", len(invalidObjects)) invObjs = invObjs.Union(invalidObjects) + klog.V(4).Infof("get the apply status for %d objects", len(invObjs)) + objStatus := taskContext.InventoryManager().Inventory().Status.Objects + klog.V(4).Infof("set inventory %d total objects", len(invObjs)) - err := i.InvClient.Replace(i.InvInfo, invObjs, i.DryRun) + err := i.InvClient.Replace(i.InvInfo, invObjs, objStatus, i.DryRun) klog.V(2).Infof("inventory set task completing (name: %q)", i.Name()) taskContext.TaskChannel() <- taskrunner.TaskResult{Err: err} diff --git a/pkg/inventory/fake-inventory-client.go b/pkg/inventory/fake-inventory-client.go index a8f12595..41de174a 100644 --- a/pkg/inventory/fake-inventory-client.go +++ b/pkg/inventory/fake-inventory-client.go @@ -6,14 +6,16 @@ package inventory import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" cmdutil "k8s.io/kubectl/pkg/cmd/util" + "sigs.k8s.io/cli-utils/pkg/apis/actuation" "sigs.k8s.io/cli-utils/pkg/common" "sigs.k8s.io/cli-utils/pkg/object" ) // FakeClient is a testing implementation of the Client interface. type FakeClient struct { - Objs object.ObjMetadataSet - Err error + Objs object.ObjMetadataSet + Status []actuation.ObjectStatus + Err error } var ( @@ -57,12 +59,13 @@ func (fic *FakeClient) Merge(_ Info, objs object.ObjMetadataSet, _ common.DryRun // Replace the stored cluster inventory objs with the passed obj, or an // error if one is set up. - -func (fic *FakeClient) Replace(_ Info, objs object.ObjMetadataSet, _ common.DryRunStrategy) error { +func (fic *FakeClient) Replace(_ Info, objs object.ObjMetadataSet, status []actuation.ObjectStatus, + _ common.DryRunStrategy) error { if fic.Err != nil { return fic.Err } fic.Objs = objs + fic.Status = status return nil } diff --git a/pkg/inventory/inventory-client.go b/pkg/inventory/inventory-client.go index 8199167f..fce7ab4d 100644 --- a/pkg/inventory/inventory-client.go +++ b/pkg/inventory/inventory-client.go @@ -16,6 +16,7 @@ import ( "k8s.io/klog/v2" cmdutil "k8s.io/kubectl/pkg/cmd/util" "k8s.io/kubectl/pkg/util" + "sigs.k8s.io/cli-utils/pkg/apis/actuation" "sigs.k8s.io/cli-utils/pkg/common" "sigs.k8s.io/cli-utils/pkg/object" ) @@ -34,7 +35,7 @@ type Client interface { Merge(inv Info, objs object.ObjMetadataSet, dryRun common.DryRunStrategy) (object.ObjMetadataSet, error) // Replace replaces the set of objects stored in the inventory // object with the passed set of objects, or an error if one occurs. - Replace(inv Info, objs object.ObjMetadataSet, dryRun common.DryRunStrategy) error + Replace(inv Info, objs object.ObjMetadataSet, status []actuation.ObjectStatus, dryRun common.DryRunStrategy) error // DeleteInventoryObj deletes the passed inventory object from the APIServer. DeleteInventoryObj(inv Info, dryRun common.DryRunStrategy) error // ApplyInventoryNamespace applies the Namespace that the inventory object should be in. @@ -100,8 +101,9 @@ func (cic *ClusterClient) Merge(localInv Info, objs object.ObjMetadataSet, dryRu } if clusterInv == nil { // Wrap inventory object and store the inventory in it. + status := getObjStatus(nil, objs) inv := cic.InventoryFactoryFunc(invObj) - if err := inv.Store(objs); err != nil { + if err := inv.Store(objs, status); err != nil { return nil, err } invInfo, err := inv.GetObject() @@ -126,10 +128,11 @@ func (cic *ClusterClient) Merge(localInv Info, objs object.ObjMetadataSet, dryRu } pruneIds = clusterObjs.Diff(objs) unionObjs := clusterObjs.Union(objs) + status := getObjStatus(pruneIds, unionObjs) klog.V(4).Infof("num objects to prune: %d", len(pruneIds)) klog.V(4).Infof("num merged objects to store in inventory: %d", len(unionObjs)) wrappedInv := cic.InventoryFactoryFunc(clusterInv) - if err = wrappedInv.Store(unionObjs); err != nil { + if err = wrappedInv.Store(unionObjs, status); err != nil { return pruneIds, err } clusterInv, err = wrappedInv.GetObject() @@ -158,7 +161,8 @@ func (cic *ClusterClient) Merge(localInv Info, objs object.ObjMetadataSet, dryRu // Replace stores the passed objects in the cluster inventory object, or // an error if one occurred. -func (cic *ClusterClient) Replace(localInv Info, objs object.ObjMetadataSet, dryRun common.DryRunStrategy) error { +func (cic *ClusterClient) Replace(localInv Info, objs object.ObjMetadataSet, status []actuation.ObjectStatus, + dryRun common.DryRunStrategy) error { // Skip entire function for dry-run. if dryRun.ClientOrServerDryRun() { klog.V(4).Infoln("dry-run replace inventory object: not applied") @@ -172,7 +176,7 @@ func (cic *ClusterClient) Replace(localInv Info, objs object.ObjMetadataSet, dry if err != nil { return fmt.Errorf("failed to read inventory from cluster: %w", err) } - clusterInv, err = cic.replaceInventory(clusterInv, objs) + clusterInv, err = cic.replaceInventory(clusterInv, objs, status) if err != nil { return err } @@ -193,9 +197,10 @@ func (cic *ClusterClient) Replace(localInv Info, objs object.ObjMetadataSet, dry } // replaceInventory stores the passed objects into the passed inventory object. -func (cic *ClusterClient) replaceInventory(inv *unstructured.Unstructured, objs object.ObjMetadataSet) (*unstructured.Unstructured, error) { +func (cic *ClusterClient) replaceInventory(inv *unstructured.Unstructured, objs object.ObjMetadataSet, + status []actuation.ObjectStatus) (*unstructured.Unstructured, error) { wrappedInv := cic.InventoryFactoryFunc(inv) - if err := wrappedInv.Store(objs); err != nil { + if err := wrappedInv.Store(objs, status); err != nil { return nil, err } clusterInv, err := wrappedInv.GetObject() @@ -493,3 +498,28 @@ func (cic *ClusterClient) hasSubResource(groupVersion, resource, subresource str } return false, nil } + +// getObjStatus returns the list of object status +// at the beginning of an apply process. +func getObjStatus(pruneIds, unionIds []object.ObjMetadata) []actuation.ObjectStatus { + status := []actuation.ObjectStatus{} + for _, obj := range unionIds { + status = append(status, + actuation.ObjectStatus{ + ObjectReference: ObjectReferenceFromObjMetadata(obj), + Strategy: actuation.ActuationStrategyApply, + Actuation: actuation.ActuationPending, + Reconcile: actuation.ReconcilePending, + }) + } + for _, obj := range pruneIds { + status = append(status, + actuation.ObjectStatus{ + ObjectReference: ObjectReferenceFromObjMetadata(obj), + Strategy: actuation.ActuationStrategyDelete, + Actuation: actuation.ActuationPending, + Reconcile: actuation.ReconcilePending, + }) + } + return status +} diff --git a/pkg/inventory/inventory-client_test.go b/pkg/inventory/inventory-client_test.go index df1b80c9..4933d691 100644 --- a/pkg/inventory/inventory-client_test.go +++ b/pkg/inventory/inventory-client_test.go @@ -4,8 +4,10 @@ package inventory import ( + "fmt" "testing" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -13,14 +15,31 @@ import ( "k8s.io/cli-runtime/pkg/resource" clienttesting "k8s.io/client-go/testing" cmdtesting "k8s.io/kubectl/pkg/cmd/testing" + "sigs.k8s.io/cli-utils/pkg/apis/actuation" "sigs.k8s.io/cli-utils/pkg/common" "sigs.k8s.io/cli-utils/pkg/object" ) +func podStatus(info *resource.Info) actuation.ObjectStatus { + return actuation.ObjectStatus{ + ObjectReference: ObjectReferenceFromObjMetadata(ignoreErrInfoToObjMeta(info)), + Strategy: actuation.ActuationStrategyApply, + Actuation: actuation.ActuationSucceeded, + Reconcile: actuation.ReconcileSucceeded, + } +} + +func podData(name string) map[string]string { + return map[string]string{ + fmt.Sprintf("test-inventory-namespace_%s__Pod", name): "{\"actuation\":\"Succeeded\",\"reconcile\":\"Succeeded\",\"strategy\":\"Apply\"}", + } +} + func TestGetClusterInventoryInfo(t *testing.T) { tests := map[string]struct { inv Info localObjs object.ObjMetadataSet + objStatus []actuation.ObjectStatus isError bool }{ "Nil local inventory object is an error": { @@ -38,7 +57,8 @@ func TestGetClusterInventoryInfo(t *testing.T) { localObjs: object.ObjMetadataSet{ ignoreErrInfoToObjMeta(pod2Info), }, - isError: false, + objStatus: []actuation.ObjectStatus{podStatus(pod2Info)}, + isError: false, }, "Local inventory with multiple objects": { inv: localInv, @@ -46,6 +66,11 @@ func TestGetClusterInventoryInfo(t *testing.T) { ignoreErrInfoToObjMeta(pod1Info), ignoreErrInfoToObjMeta(pod2Info), ignoreErrInfoToObjMeta(pod3Info)}, + objStatus: []actuation.ObjectStatus{ + podStatus(pod1Info), + podStatus(pod2Info), + podStatus(pod3Info), + }, isError: false, }, } @@ -61,7 +86,7 @@ func TestGetClusterInventoryInfo(t *testing.T) { var inv *unstructured.Unstructured if tc.inv != nil { - inv = storeObjsInInventory(tc.inv, tc.localObjs) + inv = storeObjsInInventory(tc.inv, tc.localObjs, tc.objStatus) } clusterInv, err := invClient.GetClusterInventoryInfo(WrapInventoryInfoObj(inv)) if tc.isError { @@ -188,6 +213,7 @@ func TestCreateInventory(t *testing.T) { inv Info localObjs object.ObjMetadataSet error string + objStatus []actuation.ObjectStatus }{ "Nil local inventory object is an error": { inv: nil, @@ -203,6 +229,7 @@ func TestCreateInventory(t *testing.T) { localObjs: object.ObjMetadataSet{ ignoreErrInfoToObjMeta(pod2Info), }, + objStatus: []actuation.ObjectStatus{podStatus(pod2Info)}, }, "Local inventory with multiple objects": { inv: localInv, @@ -210,6 +237,11 @@ func TestCreateInventory(t *testing.T) { ignoreErrInfoToObjMeta(pod1Info), ignoreErrInfoToObjMeta(pod2Info), ignoreErrInfoToObjMeta(pod3Info)}, + objStatus: []actuation.ObjectStatus{ + podStatus(pod1Info), + podStatus(pod2Info), + podStatus(pod3Info), + }, }, } @@ -230,7 +262,7 @@ func TestCreateInventory(t *testing.T) { require.NoError(t, err) inv := invClient.invToUnstructuredFunc(tc.inv) if inv != nil { - inv = storeObjsInInventory(tc.inv, tc.localObjs) + inv = storeObjsInInventory(tc.inv, tc.localObjs, tc.objStatus) } _, err = invClient.createInventoryObj(inv, common.DryRunNone) if tc.error != "" { @@ -242,7 +274,11 @@ func TestCreateInventory(t *testing.T) { expectedInventory := tc.localObjs.ToStringMap() // handle empty inventories special to avoid problems with empty vs nil maps if len(expectedInventory) != 0 || len(storedInventory) != 0 { - assert.Equal(t, expectedInventory, storedInventory) + for key := range expectedInventory { + if _, found := storedInventory[key]; !found { + t.Errorf("%s not found in the stored inventory", key) + } + } } }) } @@ -252,10 +288,13 @@ func TestReplace(t *testing.T) { tests := map[string]struct { localObjs object.ObjMetadataSet clusterObjs object.ObjMetadataSet + objStatus []actuation.ObjectStatus + data map[string]string }{ "Cluster and local inventories empty": { localObjs: object.ObjMetadataSet{}, clusterObjs: object.ObjMetadataSet{}, + data: map[string]string{}, }, "Cluster and local inventories same": { localObjs: object.ObjMetadataSet{ @@ -264,6 +303,8 @@ func TestReplace(t *testing.T) { clusterObjs: object.ObjMetadataSet{ ignoreErrInfoToObjMeta(pod1Info), }, + objStatus: []actuation.ObjectStatus{podStatus(pod1Info)}, + data: podData("pod-1"), }, "Cluster two obj, local one": { localObjs: object.ObjMetadataSet{ @@ -273,6 +314,8 @@ func TestReplace(t *testing.T) { ignoreErrInfoToObjMeta(pod1Info), ignoreErrInfoToObjMeta(pod3Info), }, + objStatus: []actuation.ObjectStatus{podStatus(pod1Info), podStatus(pod3Info)}, + data: podData("pod-1"), }, "Cluster multiple objs, local multiple different objs": { localObjs: object.ObjMetadataSet{ @@ -282,6 +325,8 @@ func TestReplace(t *testing.T) { ignoreErrInfoToObjMeta(pod1Info), ignoreErrInfoToObjMeta(pod2Info), ignoreErrInfoToObjMeta(pod3Info)}, + objStatus: []actuation.ObjectStatus{podStatus(pod2Info), podStatus(pod1Info), podStatus(pod3Info)}, + data: podData("pod-2"), }, } @@ -291,11 +336,11 @@ func TestReplace(t *testing.T) { // Client and server dry-run do not throw errors. invClient, err := NewClient(tf, WrapInventoryObj, InvInfoToConfigMap) require.NoError(t, err) - err = invClient.Replace(copyInventory(), object.ObjMetadataSet{}, common.DryRunClient) + err = invClient.Replace(copyInventory(), object.ObjMetadataSet{}, nil, common.DryRunClient) if err != nil { t.Fatalf("unexpected error received: %s", err) } - err = invClient.Replace(copyInventory(), object.ObjMetadataSet{}, common.DryRunServer) + err = invClient.Replace(copyInventory(), object.ObjMetadataSet{}, nil, common.DryRunServer) if err != nil { t.Fatalf("unexpected error received: %s", err) } @@ -307,7 +352,7 @@ func TestReplace(t *testing.T) { WrapInventoryObj, InvInfoToConfigMap) require.NoError(t, err) wrappedInv := invClient.InventoryFactoryFunc(inventoryObj) - if err := wrappedInv.Store(tc.clusterObjs); err != nil { + if err := wrappedInv.Store(tc.clusterObjs, tc.objStatus); err != nil { t.Fatalf("unexpected error storing inventory objects: %s", err) } inv, err := wrappedInv.GetObject() @@ -315,7 +360,7 @@ func TestReplace(t *testing.T) { t.Fatalf("unexpected error storing inventory objects: %s", err) } // Call replaceInventory with the new set of "localObjs" - inv, err = invClient.replaceInventory(inv, tc.localObjs) + inv, err = invClient.replaceInventory(inv, tc.localObjs, tc.objStatus) if err != nil { t.Fatalf("unexpected error received: %s", err) } @@ -328,6 +373,13 @@ func TestReplace(t *testing.T) { if !tc.localObjs.Equal(actualObjs) { t.Errorf("expected objects (%s), got (%s)", tc.localObjs, actualObjs) } + data, _, err := unstructured.NestedStringMap(inv.Object, "data") + if err != nil { + t.Fatalf("unexpected error received: %s", err) + } + if diff := cmp.Diff(data, tc.data); diff != "" { + t.Fatalf(diff) + } }) } } @@ -390,6 +442,7 @@ func TestDeleteInventoryObj(t *testing.T) { tests := map[string]struct { inv Info localObjs object.ObjMetadataSet + objStatus []actuation.ObjectStatus }{ "Nil local inventory object is an error": { inv: nil, @@ -404,6 +457,7 @@ func TestDeleteInventoryObj(t *testing.T) { localObjs: object.ObjMetadataSet{ ignoreErrInfoToObjMeta(pod2Info), }, + objStatus: []actuation.ObjectStatus{podStatus(pod2Info)}, }, "Local inventory with multiple objects": { inv: localInv, @@ -411,6 +465,11 @@ func TestDeleteInventoryObj(t *testing.T) { ignoreErrInfoToObjMeta(pod1Info), ignoreErrInfoToObjMeta(pod2Info), ignoreErrInfoToObjMeta(pod3Info)}, + objStatus: []actuation.ObjectStatus{ + podStatus(pod1Info), + podStatus(pod2Info), + podStatus(pod3Info), + }, }, } @@ -426,7 +485,7 @@ func TestDeleteInventoryObj(t *testing.T) { require.NoError(t, err) inv := invClient.invToUnstructuredFunc(tc.inv) if inv != nil { - inv = storeObjsInInventory(tc.inv, tc.localObjs) + inv = storeObjsInInventory(tc.inv, tc.localObjs, tc.objStatus) } err = invClient.deleteInventoryObjByName(inv, drs) if err != nil { @@ -454,3 +513,10 @@ func toReactionFunc(objs object.ObjMetadataSet) clienttesting.ReactionFunc { return true, list, err } } + +func storeObjsInInventory(info Info, objs object.ObjMetadataSet, status []actuation.ObjectStatus) *unstructured.Unstructured { + wrapped := WrapInventoryObj(InvInfoToConfigMap(info)) + _ = wrapped.Store(objs, status) + inv, _ := wrapped.GetObject() + return inv +} diff --git a/pkg/inventory/inventory.go b/pkg/inventory/inventory.go index 923c6bba..099671f8 100644 --- a/pkg/inventory/inventory.go +++ b/pkg/inventory/inventory.go @@ -17,6 +17,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/klog/v2" + "sigs.k8s.io/cli-utils/pkg/apis/actuation" "sigs.k8s.io/cli-utils/pkg/common" "sigs.k8s.io/cli-utils/pkg/object" ) @@ -31,7 +32,7 @@ type Storage interface { // Load retrieves the set of object metadata from the inventory object Load() (object.ObjMetadataSet, error) // Store the set of object metadata in the inventory object - Store(objs object.ObjMetadataSet) error + Store(objs object.ObjMetadataSet, status []actuation.ObjectStatus) error // GetObject returns the object that stores the inventory GetObject() (*unstructured.Unstructured, error) } diff --git a/pkg/inventory/inventory_test.go b/pkg/inventory/inventory_test.go index 3ccfcfe8..2f50c596 100644 --- a/pkg/inventory/inventory_test.go +++ b/pkg/inventory/inventory_test.go @@ -420,10 +420,3 @@ func copyInventory() Info { u := inventoryObj.DeepCopy() return WrapInventoryInfoObj(u) } - -func storeObjsInInventory(info Info, objs object.ObjMetadataSet) *unstructured.Unstructured { - wrapped := WrapInventoryObj(InvInfoToConfigMap(info)) - _ = wrapped.Store(objs) - inv, _ := wrapped.GetObject() - return inv -} diff --git a/pkg/inventory/inventorycm.go b/pkg/inventory/inventorycm.go index 28780a06..8c841673 100644 --- a/pkg/inventory/inventorycm.go +++ b/pkg/inventory/inventorycm.go @@ -9,9 +9,11 @@ package inventory import ( + "encoding/json" "fmt" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/cli-utils/pkg/apis/actuation" "sigs.k8s.io/cli-utils/pkg/common" "sigs.k8s.io/cli-utils/pkg/object" ) @@ -42,8 +44,9 @@ func InvInfoToConfigMap(inv Info) *unstructured.Unstructured { // the Inventory interface. This wrapper loads and stores the // object metadata (inventory) to and from the wrapped ConfigMap. type ConfigMap struct { - inv *unstructured.Unstructured - objMetas object.ObjMetadataSet + inv *unstructured.Unstructured + objMetas object.ObjMetadataSet + objStatus []actuation.ObjectStatus } var _ Info = &ConfigMap{} @@ -94,8 +97,9 @@ func (icm *ConfigMap) Load() (object.ObjMetadataSet, error) { // Store is an Inventory interface function implemented to store // the object metadata in the wrapped ConfigMap. Actual storing // happens in "GetObject". -func (icm *ConfigMap) Store(objMetas object.ObjMetadataSet) error { +func (icm *ConfigMap) Store(objMetas object.ObjMetadataSet, status []actuation.ObjectStatus) error { icm.objMetas = objMetas + icm.objStatus = status return nil } @@ -103,11 +107,14 @@ func (icm *ConfigMap) Store(objMetas object.ObjMetadataSet) error { // or an error if one occurs. func (icm *ConfigMap) GetObject() (*unstructured.Unstructured, error) { // Create the objMap of all the resources, and compute the hash. - objMap := buildObjMap(icm.objMetas) + objMap, err := buildObjMap(icm.objMetas, icm.objStatus) + if err != nil { + return nil, err + } // Create the inventory object by copying the template. invCopy := icm.inv.DeepCopy() // Adds the inventory map to the ConfigMap "data" section. - err := unstructured.SetNestedStringMap(invCopy.UnstructuredContent(), + err = unstructured.SetNestedStringMap(invCopy.UnstructuredContent(), objMap, "data") if err != nil { return nil, err @@ -115,10 +122,33 @@ func (icm *ConfigMap) GetObject() (*unstructured.Unstructured, error) { return invCopy, nil } -func buildObjMap(objMetas object.ObjMetadataSet) map[string]string { +func buildObjMap(objMetas object.ObjMetadataSet, objStatus []actuation.ObjectStatus) (map[string]string, error) { objMap := map[string]string{} + objStatusMap := map[object.ObjMetadata]actuation.ObjectStatus{} + for _, status := range objStatus { + objStatusMap[ObjMetadataFromObjectReference(status.ObjectReference)] = status + } for _, objMetadata := range objMetas { - objMap[objMetadata.String()] = "" + if status, found := objStatusMap[objMetadata]; found { + objMap[objMetadata.String()] = stringFrom(status) + } else { + // This should never happen since the objStatus have captured all the + // object status + return nil, fmt.Errorf("object status not found for %s", objMetadata) + } + } + return objMap, nil +} + +func stringFrom(status actuation.ObjectStatus) string { + tmp := map[string]string{ + "strategy": status.Strategy.String(), + "actuation": status.Actuation.String(), + "reconcile": status.Reconcile.String(), + } + data, err := json.Marshal(tmp) + if err != nil || string(data) == "{}" { + return "" } - return objMap + return string(data) } diff --git a/pkg/inventory/inventorycm_test.go b/pkg/inventory/inventorycm_test.go new file mode 100644 index 00000000..ce36d8a2 --- /dev/null +++ b/pkg/inventory/inventorycm_test.go @@ -0,0 +1,77 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package inventory + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "sigs.k8s.io/cli-utils/pkg/apis/actuation" + "sigs.k8s.io/cli-utils/pkg/object" +) + +func TestBuildObjMap(t *testing.T) { + obj1 := actuation.ObjectReference{ + Group: "group1", + Kind: "Kind", + Namespace: "ns", + Name: "na", + } + obj2 := actuation.ObjectReference{ + Group: "group2", + Kind: "Kind", + Namespace: "ns", + Name: "na", + } + + tests := map[string]struct { + objSet object.ObjMetadataSet + objStatus []actuation.ObjectStatus + expected map[string]string + hasError bool + }{ + "objMetadata matches the status": { + objSet: object.ObjMetadataSet{ObjMetadataFromObjectReference(obj1), ObjMetadataFromObjectReference(obj2)}, + objStatus: []actuation.ObjectStatus{ + { + ObjectReference: obj1, + Strategy: actuation.ActuationStrategyApply, + Actuation: actuation.ActuationSucceeded, + Reconcile: actuation.ReconcilePending, + }, + { + ObjectReference: obj2, + Strategy: actuation.ActuationStrategyDelete, + Actuation: actuation.ActuationSkipped, + Reconcile: actuation.ReconcileSucceeded, + }, + }, + expected: map[string]string{ + "ns_na_group1_Kind": `{"actuation":"Succeeded","reconcile":"Pending","strategy":"Apply"}`, + "ns_na_group2_Kind": `{"actuation":"Skipped","reconcile":"Succeeded","strategy":"Delete"}`, + }, + }, + "empty object status list": { + objSet: object.ObjMetadataSet{ObjMetadataFromObjectReference(obj1), ObjMetadataFromObjectReference(obj2)}, + hasError: true, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + actual, err := buildObjMap(tc.objSet, tc.objStatus) + if tc.hasError { + if err == nil { + t.Fatalf("expected erroe, but not happened") + } + return + } + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(actual, tc.expected); diff != "" { + t.Errorf(diff) + } + }) + } +} diff --git a/test/e2e/customprovider/provider.go b/test/e2e/customprovider/provider.go index 0062f6fe..8a428060 100644 --- a/test/e2e/customprovider/provider.go +++ b/test/e2e/customprovider/provider.go @@ -9,6 +9,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/kubectl/pkg/cmd/util" + "sigs.k8s.io/cli-utils/pkg/apis/actuation" "sigs.k8s.io/cli-utils/pkg/common" "sigs.k8s.io/cli-utils/pkg/inventory" "sigs.k8s.io/cli-utils/pkg/object" @@ -156,7 +157,7 @@ func (i InventoryCustomType) Load() (object.ObjMetadataSet, error) { return inv, nil } -func (i InventoryCustomType) Store(objs object.ObjMetadataSet) error { +func (i InventoryCustomType) Store(objs object.ObjMetadataSet, _ []actuation.ObjectStatus) error { var inv []interface{} for _, obj := range objs { inv = append(inv, map[string]interface{}{