Add ObjMetadataSet to encapsulate set functions

- Refactor usages of []ObjMetadata to use ObjMetadataSet
- Move Union, Diff, Contains, Hash, Remove, and Equal into
  ObjMetadataSet
- Add ToStringMap and FromStringMap for inventory serialization
This commit is contained in:
Karl Isenberg 2021-10-06 13:30:08 -07:00
parent 2a6b353b46
commit d83ce93efd
54 changed files with 727 additions and 791 deletions

View File

@ -121,7 +121,7 @@ func (ef *formatter) FormatActionGroupEvent(age event.ActionGroupEvent, ags []ev
for id, se := range c.LatestStatus() {
// Only print information about objects that we actually care about
// for this wait task.
if found := object.ObjMetas(ag.Identifiers).Contains(id); found {
if found := ag.Identifiers.Contains(id); found {
ef.printResourceStatus(id, se)
}
}

View File

@ -112,7 +112,7 @@ func (jf *formatter) FormatActionGroupEvent(age event.ActionGroupEvent, ags []ev
for id, se := range c.LatestStatus() {
// Only print information about objects that we actually care about
// for this wait task.
if found := object.ObjMetas(ag.Identifiers).Contains(id); found {
if found := ag.Identifiers.Contains(id); found {
if err := jf.printResourceStatus(se); err != nil {
return err
}

View File

@ -46,7 +46,7 @@ func TestResourceStateCollector_New(t *testing.T) {
resourceGroups: []event.ActionGroup{
{
Action: event.ApplyAction,
Identifiers: []object.ObjMetadata{
Identifiers: object.ObjMetadataSet{
depID, customID,
},
},
@ -64,13 +64,13 @@ func TestResourceStateCollector_New(t *testing.T) {
resourceGroups: []event.ActionGroup{
{
Action: event.ApplyAction,
Identifiers: []object.ObjMetadata{
Identifiers: object.ObjMetadataSet{
customID,
},
},
{
Action: event.PruneAction,
Identifiers: []object.ObjMetadata{
Identifiers: object.ObjMetadataSet{
depID,
},
},
@ -117,7 +117,7 @@ func TestResourceStateCollector_ProcessStatusEvent(t *testing.T) {
resourceGroups: []event.ActionGroup{
{
Action: event.ApplyAction,
Identifiers: []object.ObjMetadata{depID},
Identifiers: object.ObjMetadataSet{depID},
},
},
statusEvent: event.StatusEvent{
@ -130,7 +130,7 @@ func TestResourceStateCollector_ProcessStatusEvent(t *testing.T) {
resourceGroups: []event.ActionGroup{
{
Action: event.ApplyAction,
Identifiers: []object.ObjMetadata{
Identifiers: object.ObjMetadataSet{
depID, customID,
},
},
@ -146,13 +146,13 @@ func TestResourceStateCollector_ProcessStatusEvent(t *testing.T) {
resourceGroups: []event.ActionGroup{
{
Action: event.ApplyAction,
Identifiers: []object.ObjMetadata{
Identifiers: object.ObjMetadataSet{
customID,
},
},
{
Action: event.PruneAction,
Identifiers: []object.ObjMetadata{
Identifiers: object.ObjMetadataSet{
depID,
},
},

View File

@ -59,7 +59,7 @@ func TestStatusCommand(t *testing.T) {
printer string
timeout time.Duration
input string
inventory []object.ObjMetadata
inventory object.ObjMetadataSet
events []pollevent.Event
expectedErrMsg string
expectedOutput string
@ -76,7 +76,7 @@ func TestStatusCommand(t *testing.T) {
pollUntil: "known",
printer: "events",
input: inventoryTemplate,
inventory: []object.ObjMetadata{
inventory: object.ObjMetadataSet{
depObject,
stsObject,
},
@ -107,7 +107,7 @@ statefulset.apps/bar is Current: current
pollUntil: "current",
printer: "events",
input: inventoryTemplate,
inventory: []object.ObjMetadata{
inventory: object.ObjMetadataSet{
depObject,
stsObject,
},
@ -156,7 +156,7 @@ deployment.apps/foo is Current: current
pollUntil: "deleted",
printer: "events",
input: inventoryTemplate,
inventory: []object.ObjMetadata{
inventory: object.ObjMetadataSet{
depObject,
stsObject,
},
@ -188,7 +188,7 @@ deployment.apps/foo is NotFound: notFound
printer: "events",
timeout: 2 * time.Second,
input: inventoryTemplate,
inventory: []object.ObjMetadata{
inventory: object.ObjMetadataSet{
depObject,
stsObject,
},
@ -261,7 +261,7 @@ type fakePoller struct {
events []pollevent.Event
}
func (f *fakePoller) Poll(ctx context.Context, _ []object.ObjMetadata,
func (f *fakePoller) Poll(ctx context.Context, _ object.ObjMetadataSet,
_ polling.Options) <-chan pollevent.Event {
eventChannel := make(chan pollevent.Event)
go func() {

View File

@ -31,7 +31,7 @@ func NewEventPrinter(ioStreams genericclioptions.IOStreams) *eventPrinter {
// until the channel is closed. The provided cancelFunc is consulted on
// every event and is responsible for stopping the poller when appropriate.
// This function will block.
func (ep *eventPrinter) Print(ch <-chan pollevent.Event, identifiers []object.ObjMetadata,
func (ep *eventPrinter) Print(ch <-chan pollevent.Event, identifiers object.ObjMetadataSet,
cancelFunc collector.ObserverFunc) error {
coll := collector.NewResourceStatusCollector(identifiers)
// The actual work is done by the collector, which will invoke the

View File

@ -21,5 +21,5 @@ type Printer interface {
// needs to, and that it has completed shutting down. The latter is important
// to make sure the printer has a chance to output all data before the
// program terminates.
Print(ch <-chan event.Event, identifiers []object.ObjMetadata, cancelFunc collector.ObserverFunc) error
Print(ch <-chan event.Event, identifiers object.ObjMetadataSet, cancelFunc collector.ObserverFunc) error
}

View File

@ -33,7 +33,7 @@ func NewTablePrinter(ioStreams genericclioptions.IOStreams) *tablePrinter {
// Print take an event channel and outputs the status events on the channel
// until the channel is closed .
func (t *tablePrinter) Print(ch <-chan event.Event, identifiers []object.ObjMetadata,
func (t *tablePrinter) Print(ch <-chan event.Event, identifiers object.ObjMetadataSet,
cancelFunc collector.ObserverFunc) error {
coll := collector.NewResourceStatusCollector(identifiers)
stop := make(chan struct{})

View File

@ -77,7 +77,7 @@ type inventoryInfo struct {
name string
namespace string
id string
list []object.ObjMetadata
set object.ObjMetadataSet
}
func (i inventoryInfo) toWrapped() inventory.InventoryInfo {
@ -229,7 +229,7 @@ func TestApplier(t *testing.T) {
name: "inv-123",
namespace: "default",
id: "test",
list: []object.ObjMetadata{
set: object.ObjMetadataSet{
object.UnstructuredToObjMetaOrDie(
testutil.Unstructured(t, resources["deployment"]),
),
@ -302,7 +302,7 @@ func TestApplier(t *testing.T) {
name: "inv-123",
namespace: "default",
id: "test",
list: []object.ObjMetadata{
set: object.ObjMetadataSet{
object.UnstructuredToObjMetaOrDie(
testutil.Unstructured(t, resources["deployment"]),
),
@ -423,7 +423,7 @@ func TestApplier(t *testing.T) {
name: "abc-123",
namespace: "default",
id: "test",
list: []object.ObjMetadata{
set: object.ObjMetadataSet{
object.UnstructuredToObjMetaOrDie(
testutil.Unstructured(t, resources["deployment"]),
),
@ -466,7 +466,7 @@ func TestApplier(t *testing.T) {
name: "abc-123",
namespace: "default",
id: "test",
list: []object.ObjMetadata{
set: object.ObjMetadataSet{
object.UnstructuredToObjMetaOrDie(
testutil.Unstructured(t, resources["deployment"]),
),
@ -540,7 +540,7 @@ func TestApplier(t *testing.T) {
inventoryName: tc.invInfo.name,
inventoryNamespace: tc.invInfo.namespace,
inventoryID: tc.invInfo.id,
inventoryList: tc.invInfo.list,
inventorySet: tc.invInfo.set,
},
}, &genericHandler{
resources: objs,
@ -851,7 +851,7 @@ type inventoryObjectHandler struct {
inventoryName string
inventoryNamespace string
inventoryID string
inventoryList []object.ObjMetadata
inventorySet object.ObjMetadataSet
inventoryObj *v1.ConfigMap
}
@ -874,15 +874,11 @@ func (i *inventoryObjectHandler) handle(t *testing.T, req *http.Request) (*http.
}
if cm.Name == i.inventoryName && cm.Namespace == i.inventoryNamespace {
i.inventoryObj = &cm
var inventoryList []object.ObjMetadata
for s := range cm.Data {
objMeta, err := object.ParseObjMetadata(s)
if err != nil {
return nil, false, err
}
inventoryList = append(inventoryList, objMeta)
inventorySet, err := object.FromStringMap(cm.Data)
if err != nil {
return nil, false, err
}
i.inventoryList = inventoryList
i.inventorySet = inventorySet
bodyRC := ioutil.NopCloser(bytes.NewReader(b))
var statusCode int
@ -904,7 +900,7 @@ func (i *inventoryObjectHandler) handle(t *testing.T, req *http.Request) (*http.
},
Items: []v1.ConfigMap{},
}
if len(i.inventoryList) > 0 {
if len(i.inventorySet) > 0 {
cmList.Items = append(cmList.Items, i.currentInvObj())
}
bodyRC := ioutil.NopCloser(bytes.NewReader(toJSONBytes(t, &cmList)))
@ -912,7 +908,7 @@ func (i *inventoryObjectHandler) handle(t *testing.T, req *http.Request) (*http.
}
if req.Method == http.MethodGet && invObjPathRegex.Match([]byte(req.URL.Path)) {
if len(i.inventoryList) == 0 {
if len(i.inventorySet) == 0 {
return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, true, nil
}
invObj := i.currentInvObj()
@ -923,10 +919,6 @@ func (i *inventoryObjectHandler) handle(t *testing.T, req *http.Request) (*http.
}
func (i *inventoryObjectHandler) currentInvObj() v1.ConfigMap {
inv := make(map[string]string)
for _, objMeta := range i.inventoryList {
inv[objMeta.String()] = ""
}
return v1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: v1.SchemeGroupVersion.String(),
@ -939,7 +931,7 @@ func (i *inventoryObjectHandler) currentInvObj() v1.ConfigMap {
common.InventoryLabel: i.inventoryID,
},
},
Data: inv,
Data: i.inventorySet.ToStringMap(),
}
}
@ -977,7 +969,7 @@ type fakePoller struct {
events []pollevent.Event
}
func (f *fakePoller) Poll(ctx context.Context, _ []object.ObjMetadata, _ polling.Options) <-chan pollevent.Event {
func (f *fakePoller) Poll(ctx context.Context, _ object.ObjMetadataSet, _ polling.Options) <-chan pollevent.Event {
eventChannel := make(chan pollevent.Event)
go func() {
defer close(eventChannel)

View File

@ -78,7 +78,7 @@ const (
type ActionGroup struct {
Name string
Action ResourceAction
Identifiers []object.ObjMetadata
Identifiers object.ObjMetadataSet
}
type ErrorEvent struct {

View File

@ -21,5 +21,5 @@ const DefaultPollInterval = 2 * time.Second
// The options allows callers to override some of the settings of the poller,
// like the polling frequency and the caching strategy.
type Poller interface {
Poll(ctx context.Context, identifiers []object.ObjMetadata, options polling.Options) <-chan pollevent.Event
Poll(ctx context.Context, identifiers object.ObjMetadataSet, options polling.Options) <-chan pollevent.Event
}

View File

@ -164,7 +164,7 @@ func (po *PruneOptions) GetPruneObjs(inv inventory.InventoryInfo,
if err != nil {
return nil, err
}
pruneIds := object.SetDiff(prevInvIds, localIds)
pruneIds := prevInvIds.Diff(localIds)
pruneObjs := []*unstructured.Unstructured{}
for _, pruneID := range pruneIds {
pruneObj, err := po.GetObject(pruneID)

View File

@ -581,7 +581,7 @@ func TestGetPruneObjs(t *testing.T) {
expectedIds, err := object.UnstructuredsToObjMetas(tc.expectedObjs)
require.NoError(t, err)
if !object.SetEquals(expectedIds, actualIds) {
if !object.ObjMetadataSetEquals(expectedIds, actualIds) {
t.Errorf("expected prune objects (%v), got (%v)", expectedIds, actualIds)
}
})
@ -615,7 +615,7 @@ func TestPrune_PropagationPolicy(t *testing.T) {
t.Run(name, func(t *testing.T) {
captureClient := &optionsCaptureNamespaceClient{}
po := PruneOptions{
InvClient: inventory.NewFakeInventoryClient([]object.ObjMetadata{}),
InvClient: inventory.NewFakeInventoryClient(object.ObjMetadataSet{}),
Client: &fakeDynamicClient{
resourceInterface: captureClient,
},

View File

@ -177,7 +177,7 @@ func (t *TaskQueueBuilder) AppendApplyTask(applyObjs []*unstructured.Unstructure
// AppendInvAddTask appends a task to wait on the passed objects to the task queue.
// Returns a pointer to the Builder to chain function calls.
func (t *TaskQueueBuilder) AppendWaitTask(waitIds []object.ObjMetadata, condition taskrunner.Condition,
func (t *TaskQueueBuilder) AppendWaitTask(waitIds object.ObjMetadataSet, condition taskrunner.Condition,
waitTimeout time.Duration) *TaskQueueBuilder {
klog.V(2).Infoln("adding wait task")
t.tasks = append(t.tasks, taskrunner.NewWaitTask(

View File

@ -146,7 +146,7 @@ func TestTaskQueueBuilder_AppendApplyWaitTasks(t *testing.T) {
},
taskrunner.NewWaitTask(
"wait-0",
[]object.ObjMetadata{
object.ObjMetadataSet{
testutil.ToIdentifier(t, resources["deployment"]),
testutil.ToIdentifier(t, resources["secret"]),
},
@ -212,7 +212,7 @@ func TestTaskQueueBuilder_AppendApplyWaitTasks(t *testing.T) {
},
taskrunner.NewWaitTask(
"wait-0",
[]object.ObjMetadata{
object.ObjMetadataSet{
testutil.ToIdentifier(t, resources["crd"]),
},
taskrunner.AllCurrent, 1*time.Second,
@ -226,7 +226,7 @@ func TestTaskQueueBuilder_AppendApplyWaitTasks(t *testing.T) {
},
taskrunner.NewWaitTask(
"wait-1",
[]object.ObjMetadata{
object.ObjMetadataSet{
testutil.ToIdentifier(t, resources["crontab1"]),
testutil.ToIdentifier(t, resources["crontab2"]),
},
@ -277,7 +277,7 @@ func TestTaskQueueBuilder_AppendApplyWaitTasks(t *testing.T) {
},
taskrunner.NewWaitTask(
"wait-0",
[]object.ObjMetadata{
object.ObjMetadataSet{
testutil.ToIdentifier(t, resources["namespace"]),
},
taskrunner.AllCurrent, 1*time.Second,
@ -291,7 +291,7 @@ func TestTaskQueueBuilder_AppendApplyWaitTasks(t *testing.T) {
},
taskrunner.NewWaitTask(
"wait-1",
[]object.ObjMetadata{
object.ObjMetadataSet{
testutil.ToIdentifier(t, resources["pod"]),
testutil.ToIdentifier(t, resources["secret"]),
},
@ -315,7 +315,7 @@ func TestTaskQueueBuilder_AppendApplyWaitTasks(t *testing.T) {
},
taskrunner.NewWaitTask(
"wait-0",
[]object.ObjMetadata{
object.ObjMetadataSet{
testutil.ToIdentifier(t, resources["secret"]),
},
taskrunner.AllCurrent, 1*time.Second,
@ -328,7 +328,7 @@ func TestTaskQueueBuilder_AppendApplyWaitTasks(t *testing.T) {
},
taskrunner.NewWaitTask(
"wait-1",
[]object.ObjMetadata{
object.ObjMetadataSet{
testutil.ToIdentifier(t, resources["deployment"]),
},
taskrunner.AllCurrent, 1*time.Second,
@ -384,7 +384,7 @@ func TestTaskQueueBuilder_AppendApplyWaitTasks(t *testing.T) {
actWaitTask := toWaitTask(t, actualTask)
assert.Equal(t, len(expTsk.Ids), len(actWaitTask.Ids))
// Order is NOT important for ids stored within task.
if !object.SetEquals(expTsk.Ids, actWaitTask.Ids) {
if !expTsk.Ids.Equal(actWaitTask.Ids) {
t.Errorf("expected wait ids (%v), got (%v)",
expTsk.Ids, actWaitTask.Ids)
}
@ -457,7 +457,7 @@ func TestTaskQueueBuilder_AppendPruneWaitTasks(t *testing.T) {
},
taskrunner.NewWaitTask(
"wait-0",
[]object.ObjMetadata{
object.ObjMetadataSet{
testutil.ToIdentifier(t, resources["pod"]),
},
taskrunner.AllCurrent, 1*time.Second,
@ -470,7 +470,7 @@ func TestTaskQueueBuilder_AppendPruneWaitTasks(t *testing.T) {
},
taskrunner.NewWaitTask(
"wait-1",
[]object.ObjMetadata{
object.ObjMetadataSet{
testutil.ToIdentifier(t, resources["secret"]),
},
taskrunner.AllCurrent, 1*time.Second,
@ -495,7 +495,7 @@ func TestTaskQueueBuilder_AppendPruneWaitTasks(t *testing.T) {
},
taskrunner.NewWaitTask(
"wait-0",
[]object.ObjMetadata{
object.ObjMetadataSet{
testutil.ToIdentifier(t, resources["pod"]),
},
taskrunner.AllCurrent,
@ -544,7 +544,7 @@ func TestTaskQueueBuilder_AppendPruneWaitTasks(t *testing.T) {
},
taskrunner.NewWaitTask(
"wait-0",
[]object.ObjMetadata{
object.ObjMetadataSet{
testutil.ToIdentifier(t, resources["crontab1"]),
testutil.ToIdentifier(t, resources["crontab2"]),
},
@ -558,7 +558,7 @@ func TestTaskQueueBuilder_AppendPruneWaitTasks(t *testing.T) {
},
taskrunner.NewWaitTask(
"wait-1",
[]object.ObjMetadata{
object.ObjMetadataSet{
testutil.ToIdentifier(t, resources["crd"]),
},
taskrunner.AllCurrent, 1*time.Second,
@ -611,7 +611,7 @@ func TestTaskQueueBuilder_AppendPruneWaitTasks(t *testing.T) {
},
taskrunner.NewWaitTask(
"wait-0",
[]object.ObjMetadata{
object.ObjMetadataSet{
testutil.ToIdentifier(t, resources["pod"]),
testutil.ToIdentifier(t, resources["secret"]),
},
@ -625,7 +625,7 @@ func TestTaskQueueBuilder_AppendPruneWaitTasks(t *testing.T) {
},
taskrunner.NewWaitTask(
"wait-1",
[]object.ObjMetadata{
object.ObjMetadataSet{
testutil.ToIdentifier(t, resources["namespace"]),
},
taskrunner.AllCurrent, 1*time.Second,
@ -676,7 +676,7 @@ func TestTaskQueueBuilder_AppendPruneWaitTasks(t *testing.T) {
case *taskrunner.WaitTask:
actWaitTask := toWaitTask(t, actualTask)
assert.Equal(t, len(expTsk.Ids), len(actWaitTask.Ids))
if !object.SetEquals(expTsk.Ids, actWaitTask.Ids) {
if !expTsk.Ids.Equal(actWaitTask.Ids) {
t.Errorf("expected wait ids (%v), got (%v)",
expTsk.Ids, actWaitTask.Ids)
}

View File

@ -68,7 +68,7 @@ func (a *ApplyTask) Action() event.ResourceAction {
return event.ApplyAction
}
func (a *ApplyTask) Identifiers() []object.ObjMetadata {
func (a *ApplyTask) Identifiers() object.ObjMetadataSet {
return object.UnstructuredsToObjMetasOrDie(a.Objects)
}

View File

@ -116,7 +116,7 @@ func TestApplyTask_BasicAppliedObjects(t *testing.T) {
require.NoError(t, err)
actual := taskContext.AppliedResources()
if !object.SetEquals(expected, actual) {
if !actual.Equal(expected) {
t.Errorf("expected (%s) inventory resources, got (%s)", expected, actual)
}
})

View File

@ -32,8 +32,8 @@ func (i *DeleteInvTask) Action() event.ResourceAction {
return event.InventoryAction
}
func (i *DeleteInvTask) Identifiers() []object.ObjMetadata {
return []object.ObjMetadata{}
func (i *DeleteInvTask) Identifiers() object.ObjMetadataSet {
return object.ObjMetadataSet{}
}
// Start deletes the inventory object from the cluster.

View File

@ -37,7 +37,7 @@ func TestDeleteInvTask(t *testing.T) {
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
client := inventory.NewFakeInventoryClient([]object.ObjMetadata{})
client := inventory.NewFakeInventoryClient(object.ObjMetadataSet{})
client.Err = tc.err
eventChannel := make(chan event.Event)
resourceCache := cache.NewResourceCacheMap()

View File

@ -32,7 +32,7 @@ func (i *InvAddTask) Action() event.ResourceAction {
return event.InventoryAction
}
func (i *InvAddTask) Identifiers() []object.ObjMetadata {
func (i *InvAddTask) Identifiers() object.ObjMetadataSet {
return object.UnstructuredsToObjMetasOrDie(i.Objects)
}

View File

@ -75,34 +75,34 @@ func TestInvAddTask(t *testing.T) {
id3 := object.UnstructuredToObjMetaOrDie(obj3)
tests := map[string]struct {
initialObjs []object.ObjMetadata
initialObjs object.ObjMetadataSet
applyObjs []*unstructured.Unstructured
expectedObjs []object.ObjMetadata
expectedObjs object.ObjMetadataSet
}{
"no initial inventory and no apply objects; no merged inventory": {
initialObjs: []object.ObjMetadata{},
initialObjs: object.ObjMetadataSet{},
applyObjs: []*unstructured.Unstructured{},
expectedObjs: []object.ObjMetadata{},
expectedObjs: object.ObjMetadataSet{},
},
"no initial inventory, one apply object; one merged inventory": {
initialObjs: []object.ObjMetadata{},
initialObjs: object.ObjMetadataSet{},
applyObjs: []*unstructured.Unstructured{obj1},
expectedObjs: []object.ObjMetadata{id1},
expectedObjs: object.ObjMetadataSet{id1},
},
"one initial inventory, no apply object; one merged inventory": {
initialObjs: []object.ObjMetadata{id2},
initialObjs: object.ObjMetadataSet{id2},
applyObjs: []*unstructured.Unstructured{},
expectedObjs: []object.ObjMetadata{id2},
expectedObjs: object.ObjMetadataSet{id2},
},
"one initial inventory, one apply object; one merged inventory": {
initialObjs: []object.ObjMetadata{id3},
initialObjs: object.ObjMetadataSet{id3},
applyObjs: []*unstructured.Unstructured{obj3},
expectedObjs: []object.ObjMetadata{id3},
expectedObjs: object.ObjMetadataSet{id3},
},
"three initial inventory, two same objects; three merged inventory": {
initialObjs: []object.ObjMetadata{id1, id2, id3},
initialObjs: object.ObjMetadataSet{id1, id2, id3},
applyObjs: []*unstructured.Unstructured{obj2, obj3},
expectedObjs: []object.ObjMetadata{id1, id2, id3},
expectedObjs: object.ObjMetadataSet{id1, id2, id3},
},
}
@ -125,7 +125,7 @@ func TestInvAddTask(t *testing.T) {
applyIds, err := object.UnstructuredsToObjMetas(tc.applyObjs)
require.NoError(t, err)
if !object.SetEquals(applyIds, task.Identifiers()) {
if !task.Identifiers().Equal(applyIds) {
t.Errorf("expected task ids (%s), got (%s)", applyIds, task.Identifiers())
}
task.Start(context)
@ -134,7 +134,7 @@ func TestInvAddTask(t *testing.T) {
t.Errorf("unexpected error running InvAddTask: %s", result.Err)
}
actual, _ := client.GetClusterObjs(nil, common.DryRunNone)
if !object.SetEquals(tc.expectedObjs, actual) {
if !tc.expectedObjs.Equal(actual) {
t.Errorf("expected merged inventory (%s), got (%s)", tc.expectedObjs, actual)
}
})

View File

@ -30,8 +30,8 @@ func (i *InvSetTask) Action() event.ResourceAction {
return event.InventoryAction
}
func (i *InvSetTask) Identifiers() []object.ObjMetadata {
return []object.ObjMetadata{}
func (i *InvSetTask) Identifiers() object.ObjMetadataSet {
return object.ObjMetadataSet{}
}
// Start sets the inventory using the resources applied and the
@ -46,7 +46,7 @@ func (i *InvSetTask) Start(taskContext *taskrunner.TaskContext) {
// the inventory, then keep it in the inventory so we don't lose
// track of it for next apply/prune. An object not found in the cluster
// is NOT stored as an apply failure (so it is properly removed from the inventory).
applyFailures := []object.ObjMetadata{}
applyFailures := object.ObjMetadataSet{}
for _, failure := range taskContext.ResourceFailures() {
if _, exists := i.PrevInventory[failure]; exists {
applyFailures = append(applyFailures, failure)
@ -55,8 +55,8 @@ func (i *InvSetTask) Start(taskContext *taskrunner.TaskContext) {
klog.V(4).Infof("keep in inventory %d applied failures", len(applyFailures))
pruneFailures := taskContext.PruneFailures()
klog.V(4).Infof("set inventory %d prune failures", len(pruneFailures))
allApplyObjs := object.Union(appliedObjs, applyFailures)
invObjs := object.Union(allApplyObjs, pruneFailures)
allApplyObjs := appliedObjs.Union(applyFailures)
invObjs := allApplyObjs.Union(pruneFailures)
klog.V(4).Infof("set inventory %d total objects", len(invObjs))
err := i.InvClient.Replace(i.InvInfo, invObjs, i.DryRun)
taskContext.TaskChannel() <- taskrunner.TaskResult{Err: err}

View File

@ -20,91 +20,91 @@ func TestInvSetTask(t *testing.T) {
id3 := object.UnstructuredToObjMetaOrDie(obj3)
tests := map[string]struct {
appliedObjs []object.ObjMetadata
applyFailures []object.ObjMetadata
prevInventory []object.ObjMetadata
pruneFailures []object.ObjMetadata
expectedObjs []object.ObjMetadata
appliedObjs object.ObjMetadataSet
applyFailures object.ObjMetadataSet
prevInventory object.ObjMetadataSet
pruneFailures object.ObjMetadataSet
expectedObjs object.ObjMetadataSet
}{
"no apply objs, no prune failures; no inventory": {
appliedObjs: []object.ObjMetadata{},
pruneFailures: []object.ObjMetadata{},
expectedObjs: []object.ObjMetadata{},
appliedObjs: object.ObjMetadataSet{},
pruneFailures: object.ObjMetadataSet{},
expectedObjs: object.ObjMetadataSet{},
},
"one apply objs, no prune failures; one inventory": {
appliedObjs: []object.ObjMetadata{id1},
pruneFailures: []object.ObjMetadata{},
expectedObjs: []object.ObjMetadata{id1},
appliedObjs: object.ObjMetadataSet{id1},
pruneFailures: object.ObjMetadataSet{},
expectedObjs: object.ObjMetadataSet{id1},
},
"no apply objs, one prune failures; one inventory": {
appliedObjs: []object.ObjMetadata{},
pruneFailures: []object.ObjMetadata{id1},
expectedObjs: []object.ObjMetadata{id1},
appliedObjs: object.ObjMetadataSet{},
pruneFailures: object.ObjMetadataSet{id1},
expectedObjs: object.ObjMetadataSet{id1},
},
"one apply objs, one prune failures; one inventory": {
appliedObjs: []object.ObjMetadata{id3},
pruneFailures: []object.ObjMetadata{id3},
expectedObjs: []object.ObjMetadata{id3},
appliedObjs: object.ObjMetadataSet{id3},
pruneFailures: object.ObjMetadataSet{id3},
expectedObjs: object.ObjMetadataSet{id3},
},
"two apply objs, two prune failures; three inventory": {
appliedObjs: []object.ObjMetadata{id1, id2},
pruneFailures: []object.ObjMetadata{id2, id3},
expectedObjs: []object.ObjMetadata{id1, id2, id3},
appliedObjs: object.ObjMetadataSet{id1, id2},
pruneFailures: object.ObjMetadataSet{id2, id3},
expectedObjs: object.ObjMetadataSet{id1, id2, id3},
},
"no apply objs, no apply failures, no prune failures; no inventory": {
appliedObjs: []object.ObjMetadata{},
applyFailures: []object.ObjMetadata{id3},
prevInventory: []object.ObjMetadata{},
pruneFailures: []object.ObjMetadata{},
expectedObjs: []object.ObjMetadata{},
appliedObjs: object.ObjMetadataSet{},
applyFailures: object.ObjMetadataSet{id3},
prevInventory: object.ObjMetadataSet{},
pruneFailures: object.ObjMetadataSet{},
expectedObjs: object.ObjMetadataSet{},
},
"one apply failure not in prev inventory; no inventory": {
appliedObjs: []object.ObjMetadata{},
applyFailures: []object.ObjMetadata{id3},
prevInventory: []object.ObjMetadata{},
pruneFailures: []object.ObjMetadata{},
expectedObjs: []object.ObjMetadata{},
appliedObjs: object.ObjMetadataSet{},
applyFailures: object.ObjMetadataSet{id3},
prevInventory: object.ObjMetadataSet{},
pruneFailures: object.ObjMetadataSet{},
expectedObjs: object.ObjMetadataSet{},
},
"one apply obj, one apply failure not in prev inventory; one inventory": {
appliedObjs: []object.ObjMetadata{id2},
applyFailures: []object.ObjMetadata{id3},
prevInventory: []object.ObjMetadata{},
pruneFailures: []object.ObjMetadata{},
expectedObjs: []object.ObjMetadata{id2},
appliedObjs: object.ObjMetadataSet{id2},
applyFailures: object.ObjMetadataSet{id3},
prevInventory: object.ObjMetadataSet{},
pruneFailures: object.ObjMetadataSet{},
expectedObjs: object.ObjMetadataSet{id2},
},
"one apply obj, one apply failure in prev inventory; one inventory": {
appliedObjs: []object.ObjMetadata{id2},
applyFailures: []object.ObjMetadata{id3},
prevInventory: []object.ObjMetadata{id3},
pruneFailures: []object.ObjMetadata{},
expectedObjs: []object.ObjMetadata{id2, id3},
appliedObjs: object.ObjMetadataSet{id2},
applyFailures: object.ObjMetadataSet{id3},
prevInventory: object.ObjMetadataSet{id3},
pruneFailures: object.ObjMetadataSet{},
expectedObjs: object.ObjMetadataSet{id2, id3},
},
"one apply obj, two apply failures with one in prev inventory; two inventory": {
appliedObjs: []object.ObjMetadata{id2},
applyFailures: []object.ObjMetadata{id1, id3},
prevInventory: []object.ObjMetadata{id3},
pruneFailures: []object.ObjMetadata{},
expectedObjs: []object.ObjMetadata{id2, id3},
appliedObjs: object.ObjMetadataSet{id2},
applyFailures: object.ObjMetadataSet{id1, id3},
prevInventory: object.ObjMetadataSet{id3},
pruneFailures: object.ObjMetadataSet{},
expectedObjs: object.ObjMetadataSet{id2, id3},
},
"three apply failures with two in prev inventory; two inventory": {
appliedObjs: []object.ObjMetadata{},
applyFailures: []object.ObjMetadata{id1, id2, id3},
prevInventory: []object.ObjMetadata{id2, id3},
pruneFailures: []object.ObjMetadata{},
expectedObjs: []object.ObjMetadata{id2, id3},
appliedObjs: object.ObjMetadataSet{},
applyFailures: object.ObjMetadataSet{id1, id2, id3},
prevInventory: object.ObjMetadataSet{id2, id3},
pruneFailures: object.ObjMetadataSet{},
expectedObjs: object.ObjMetadataSet{id2, id3},
},
"three apply failures with three in prev inventory; three inventory": {
appliedObjs: []object.ObjMetadata{},
applyFailures: []object.ObjMetadata{id1, id2, id3},
prevInventory: []object.ObjMetadata{id2, id3, id1},
pruneFailures: []object.ObjMetadata{},
expectedObjs: []object.ObjMetadata{id2, id1, id3},
appliedObjs: object.ObjMetadataSet{},
applyFailures: object.ObjMetadataSet{id1, id2, id3},
prevInventory: object.ObjMetadataSet{id2, id3, id1},
pruneFailures: object.ObjMetadataSet{},
expectedObjs: object.ObjMetadataSet{id2, id1, id3},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
client := inventory.NewFakeInventoryClient([]object.ObjMetadata{})
client := inventory.NewFakeInventoryClient(object.ObjMetadataSet{})
eventChannel := make(chan event.Event)
resourceCache := cache.NewResourceCacheMap()
context := taskrunner.NewTaskContext(eventChannel, resourceCache)
@ -137,7 +137,7 @@ func TestInvSetTask(t *testing.T) {
t.Errorf("unexpected error running InvAddTask: %s", result.Err)
}
actual, _ := client.GetClusterObjs(nil, common.DryRunNone)
if !object.SetEquals(tc.expectedObjs, actual) {
if !tc.expectedObjs.Equal(actual) {
t.Errorf("expected merged inventory (%s), got (%s)", tc.expectedObjs, actual)
}
})

View File

@ -43,7 +43,7 @@ func (p *PruneTask) Action() event.ResourceAction {
return action
}
func (p *PruneTask) Identifiers() []object.ObjMetadata {
func (p *PruneTask) Identifiers() object.ObjMetadataSet {
return object.UnstructuredsToObjMetasOrDie(p.Objects)
}

View File

@ -39,7 +39,7 @@ func (c Condition) Meets(s status.Status) bool {
// conditionMet tests whether the provided Condition holds true for
// all resources in the list, according to the ResourceCache.
// Resources in the cache older that the applied generation are non-matches.
func conditionMet(taskContext *TaskContext, ids []object.ObjMetadata, c Condition) bool {
func conditionMet(taskContext *TaskContext, ids object.ObjMetadataSet, c Condition) bool {
switch c {
case AllCurrent:
return allMatchStatus(taskContext, ids, status.CurrentStatus)
@ -52,7 +52,7 @@ func conditionMet(taskContext *TaskContext, ids []object.ObjMetadata, c Conditio
// allMatchStatus checks whether all of the resources provided have the provided status.
// Resources with older generations are considered non-matching.
func allMatchStatus(taskContext *TaskContext, ids []object.ObjMetadata, s status.Status) bool {
func allMatchStatus(taskContext *TaskContext, ids object.ObjMetadataSet, s status.Status) bool {
for _, id := range ids {
cached := taskContext.ResourceCache().Get(id)
if cached.Status != s {
@ -74,7 +74,7 @@ func allMatchStatus(taskContext *TaskContext, ids []object.ObjMetadata, s status
// allMatchStatus checks whether none of the resources provided have the provided status.
// Resources with older generations are considered matching.
func noneMatchStatus(taskContext *TaskContext, ids []object.ObjMetadata, s status.Status) bool {
func noneMatchStatus(taskContext *TaskContext, ids object.ObjMetadataSet, s status.Status) bool {
for _, id := range ids {
cached := taskContext.ResourceCache().Get(id)
if cached.Status == s {

View File

@ -64,7 +64,7 @@ func TestCollector_ConditionMet(t *testing.T) {
testCases := map[string]struct {
cacheContents []cache.ResourceStatus
appliedGen map[object.ObjMetadata]int64
ids []object.ObjMetadata
ids object.ObjMetadataSet
condition Condition
expectedResult bool
}{
@ -78,7 +78,7 @@ func TestCollector_ConditionMet(t *testing.T) {
appliedGen: map[object.ObjMetadata]int64{
deployment1Meta: 42,
},
ids: []object.ObjMetadata{
ids: object.ObjMetadataSet{
deployment1Meta,
},
condition: AllCurrent,
@ -94,7 +94,7 @@ func TestCollector_ConditionMet(t *testing.T) {
appliedGen: map[object.ObjMetadata]int64{
deployment1Meta: 42,
},
ids: []object.ObjMetadata{
ids: object.ObjMetadataSet{
deployment1Meta,
},
condition: AllCurrent,
@ -115,7 +115,7 @@ func TestCollector_ConditionMet(t *testing.T) {
deployment1Meta: 42,
custom1Meta: 0,
},
ids: []object.ObjMetadata{
ids: object.ObjMetadataSet{
deployment1Meta,
custom1Meta,
},
@ -137,7 +137,7 @@ func TestCollector_ConditionMet(t *testing.T) {
deployment1Meta: 42,
custom1Meta: 5,
},
ids: []object.ObjMetadata{
ids: object.ObjMetadataSet{
deployment1Meta,
custom1Meta,
},

View File

@ -82,8 +82,8 @@ func (tc *TaskContext) ResourceUID(id object.ObjMetadata) (types.UID, bool) {
// AppliedResources returns all the objects (as ObjMetadata) that
// were added as applied resources to the TaskContext.
func (tc *TaskContext) AppliedResources() []object.ObjMetadata {
all := make([]object.ObjMetadata, 0, len(tc.appliedResources))
func (tc *TaskContext) AppliedResources() object.ObjMetadataSet {
all := make(object.ObjMetadataSet, 0, len(tc.appliedResources))
for r := range tc.appliedResources {
all = append(all, r)
}
@ -129,8 +129,8 @@ func (tc *TaskContext) CaptureResourceFailure(id object.ObjMetadata) {
tc.failedResources[id] = struct{}{}
}
func (tc *TaskContext) ResourceFailures() []object.ObjMetadata {
failures := make([]object.ObjMetadata, 0, len(tc.failedResources))
func (tc *TaskContext) ResourceFailures() object.ObjMetadataSet {
failures := make(object.ObjMetadataSet, 0, len(tc.failedResources))
for f := range tc.failedResources {
failures = append(failures, f)
}
@ -141,8 +141,8 @@ func (tc *TaskContext) CapturePruneFailure(id object.ObjMetadata) {
tc.pruneFailures[id] = struct{}{}
}
func (tc *TaskContext) PruneFailures() []object.ObjMetadata {
failures := make([]object.ObjMetadata, 0, len(tc.pruneFailures))
func (tc *TaskContext) PruneFailures() object.ObjMetadataSet {
failures := make(object.ObjMetadataSet, 0, len(tc.pruneFailures))
for f := range tc.pruneFailures {
failures = append(failures, f)
}

View File

@ -19,7 +19,7 @@ import (
)
// NewTaskStatusRunner returns a new TaskStatusRunner.
func NewTaskStatusRunner(identifiers []object.ObjMetadata, statusPoller poller.Poller, cache cache.ResourceCache) *taskStatusRunner {
func NewTaskStatusRunner(identifiers object.ObjMetadataSet, statusPoller poller.Poller, cache cache.ResourceCache) *taskStatusRunner {
return &taskStatusRunner{
identifiers: identifiers,
statusPoller: statusPoller,
@ -31,7 +31,7 @@ func NewTaskStatusRunner(identifiers []object.ObjMetadata, statusPoller poller.P
// tasks while at the same time uses the statusPoller to
// keep track of the status of the resources.
type taskStatusRunner struct {
identifiers []object.ObjMetadata
identifiers object.ObjMetadataSet
statusPoller poller.Poller
baseRunner *baseRunner
@ -338,7 +338,7 @@ type TaskResult struct {
type TimeoutError struct {
// Identifiers contains the identifiers of all resources that the
// WaitTask was waiting for.
Identifiers []object.ObjMetadata
Identifiers object.ObjMetadataSet
// Timeout is the amount of time it took before it timed out.
Timeout time.Duration

View File

@ -41,7 +41,6 @@ var (
func TestBaseRunner(t *testing.T) {
testCases := map[string]struct {
identifiers []object.ObjMetadata
tasks []Task
statusEventsDelay time.Duration
statusEvents []pollevent.Event
@ -51,7 +50,6 @@ func TestBaseRunner(t *testing.T) {
expectedErrorMsg string
}{
"wait task runs until condition is met": {
identifiers: []object.ObjMetadata{depID, cmID},
tasks: []Task{
&fakeApplyTask{
resultEvent: event.Event{
@ -59,7 +57,7 @@ func TestBaseRunner(t *testing.T) {
},
duration: 3 * time.Second,
},
NewWaitTask("wait", []object.ObjMetadata{depID, cmID}, AllCurrent,
NewWaitTask("wait", object.ObjMetadataSet{depID, cmID}, AllCurrent,
1*time.Minute, testutil.NewFakeRESTMapper()),
&fakeApplyTask{
resultEvent: event.Event{
@ -99,9 +97,8 @@ func TestBaseRunner(t *testing.T) {
},
},
"wait task times out eventually (Unknown)": {
identifiers: []object.ObjMetadata{depID, cmID},
tasks: []Task{
NewWaitTask("wait", []object.ObjMetadata{depID, cmID}, AllCurrent,
NewWaitTask("wait", object.ObjMetadataSet{depID, cmID}, AllCurrent,
2*time.Second, testutil.NewFakeRESTMapper()),
},
statusEventsDelay: time.Second,
@ -128,9 +125,8 @@ func TestBaseRunner(t *testing.T) {
expectedErrorMsg: "timeout after 2 seconds waiting for 2 resources ([default_cm__ConfigMap default_dep_apps_Deployment]) to reach condition AllCurrent",
},
"wait task times out eventually (InProgress)": {
identifiers: []object.ObjMetadata{depID, cmID},
tasks: []Task{
NewWaitTask("wait", []object.ObjMetadata{depID, cmID}, AllCurrent,
NewWaitTask("wait", object.ObjMetadataSet{depID, cmID}, AllCurrent,
2*time.Second, testutil.NewFakeRESTMapper()),
},
statusEventsDelay: time.Second,
@ -163,7 +159,6 @@ func TestBaseRunner(t *testing.T) {
expectedErrorMsg: "timeout after 2 seconds waiting for 2 resources ([default_cm__ConfigMap default_dep_apps_Deployment]) to reach condition AllCurrent",
},
"tasks run in order": {
identifiers: []object.ObjMetadata{},
tasks: []Task{
&fakeApplyTask{
resultEvent: event.Event{
@ -279,7 +274,6 @@ func TestBaseRunnerCancellation(t *testing.T) {
testError := fmt.Errorf("this is a test error")
testCases := map[string]struct {
identifiers []object.ObjMetadata
tasks []Task
statusEventsDelay time.Duration
statusEvents []pollevent.Event
@ -288,7 +282,6 @@ func TestBaseRunnerCancellation(t *testing.T) {
expectedEventTypes []event.Type
}{
"cancellation while custom task is running": {
identifiers: []object.ObjMetadata{depID},
tasks: []Task{
&fakeApplyTask{
resultEvent: event.Event{
@ -311,9 +304,8 @@ func TestBaseRunnerCancellation(t *testing.T) {
},
},
"cancellation while wait task is running": {
identifiers: []object.ObjMetadata{depID},
tasks: []Task{
NewWaitTask("wait", []object.ObjMetadata{depID}, AllCurrent,
NewWaitTask("wait", object.ObjMetadataSet{depID}, AllCurrent,
20*time.Second, testutil.NewFakeRESTMapper()),
&fakeApplyTask{
resultEvent: event.Event{
@ -329,7 +321,6 @@ func TestBaseRunnerCancellation(t *testing.T) {
},
},
"error while custom task is running": {
identifiers: []object.ObjMetadata{depID},
tasks: []Task{
&fakeApplyTask{
resultEvent: event.Event{
@ -354,9 +345,8 @@ func TestBaseRunnerCancellation(t *testing.T) {
},
},
"error from status poller while wait task is running": {
identifiers: []object.ObjMetadata{depID},
tasks: []Task{
NewWaitTask("wait", []object.ObjMetadata{depID}, AllCurrent,
NewWaitTask("wait", object.ObjMetadataSet{depID}, AllCurrent,
20*time.Second, testutil.NewFakeRESTMapper()),
&fakeApplyTask{
resultEvent: event.Event{
@ -461,8 +451,8 @@ func (f *fakeApplyTask) Action() event.ResourceAction {
return event.ApplyAction
}
func (f *fakeApplyTask) Identifiers() []object.ObjMetadata {
return []object.ObjMetadata{}
func (f *fakeApplyTask) Identifiers() object.ObjMetadataSet {
return object.ObjMetadataSet{}
}
func (f *fakeApplyTask) Start(taskContext *TaskContext) {

View File

@ -22,14 +22,14 @@ import (
type Task interface {
Name() string
Action() event.ResourceAction
Identifiers() []object.ObjMetadata
Identifiers() object.ObjMetadataSet
Start(taskContext *TaskContext)
ClearTimeout()
}
// NewWaitTask creates a new wait task where we will wait until
// the resources specifies by ids all meet the specified condition.
func NewWaitTask(name string, ids []object.ObjMetadata, cond Condition, timeout time.Duration, mapper meta.RESTMapper) *WaitTask {
func NewWaitTask(name string, ids object.ObjMetadataSet, cond Condition, timeout time.Duration, mapper meta.RESTMapper) *WaitTask {
// Create the token channel and only add one item.
tokenChannel := make(chan struct{}, 1)
tokenChannel <- struct{}{}
@ -56,7 +56,7 @@ type WaitTask struct {
// name allows providing a name for the task.
name string
// Ids is the list of resources that we are waiting for.
Ids []object.ObjMetadata
Ids object.ObjMetadataSet
// Condition defines the status we want all resources to reach
Condition Condition
// Timeout defines how long we are willing to wait for the condition
@ -86,7 +86,7 @@ func (w *WaitTask) Action() event.ResourceAction {
return event.WaitAction
}
func (w *WaitTask) Identifiers() []object.ObjMetadata {
func (w *WaitTask) Identifiers() object.ObjMetadataSet {
return w.Ids
}
@ -136,8 +136,8 @@ func (w *WaitTask) checkCondition(taskContext *TaskContext) bool {
// pending returns the set of resources being waited on excluding
// apply/delete failures. This includes resources which are skipped because of
// filtering.
func (w *WaitTask) pending(taskContext *TaskContext) []object.ObjMetadata {
var ids []object.ObjMetadata
func (w *WaitTask) pending(taskContext *TaskContext) object.ObjMetadataSet {
var ids object.ObjMetadataSet
for _, id := range w.Ids {
if (w.Condition == AllCurrent && taskContext.ResourceFailed(id)) ||
(w.Condition == AllNotFound && taskContext.PruneFailed(id)) {

View File

@ -15,7 +15,7 @@ import (
)
func TestWaitTask_TimeoutTriggered(t *testing.T) {
task := NewWaitTask("wait", []object.ObjMetadata{}, AllCurrent,
task := NewWaitTask("wait", object.ObjMetadataSet{}, AllCurrent,
2*time.Second, testutil.NewFakeRESTMapper())
eventChannel := make(chan event.Event)
@ -39,7 +39,7 @@ func TestWaitTask_TimeoutTriggered(t *testing.T) {
}
func TestWaitTask_TimeoutCancelled(t *testing.T) {
task := NewWaitTask("wait", []object.ObjMetadata{}, AllCurrent,
task := NewWaitTask("wait", object.ObjMetadataSet{}, AllCurrent,
2*time.Second, testutil.NewFakeRESTMapper())
eventChannel := make(chan event.Event)
@ -60,7 +60,7 @@ func TestWaitTask_TimeoutCancelled(t *testing.T) {
}
func TestWaitTask_SingleTaskResult(t *testing.T) {
task := NewWaitTask("wait", []object.ObjMetadata{}, AllCurrent,
task := NewWaitTask("wait", object.ObjMetadataSet{}, AllCurrent,
2*time.Second, testutil.NewFakeRESTMapper())
eventChannel := make(chan event.Event)

View File

@ -55,7 +55,7 @@ func TestTextForError(t *testing.T) {
"timeout error": {
err: &taskrunner.TimeoutError{
Timeout: 2 * time.Second,
Identifiers: []object.ObjMetadata{
Identifiers: object.ObjMetadataSet{
{
GroupKind: schema.GroupKind{
Kind: "Deployment",

View File

@ -32,13 +32,13 @@ var (
// FakeBuilder encapsulates a resource Builder which will hard-code the return
// of an inventory object with the encoded past invObjs.
type FakeBuilder struct {
invObjs []object.ObjMetadata
invObjs object.ObjMetadataSet
}
// SetInventoryObjs sets the objects which will be encoded in
// an inventory object to be returned when queried for the cluster
// inventory object.
func (fb *FakeBuilder) SetInventoryObjs(objs []object.ObjMetadata) {
func (fb *FakeBuilder) SetInventoryObjs(objs object.ObjMetadataSet) {
fb.invObjs = objs
}
@ -59,7 +59,7 @@ func (fb *FakeBuilder) GetBuilder() func() *resource.Builder {
// fakeClient hard codes the return of an inventory object that encodes the passed
// objects into the inventory object when a GET of configmaps is called.
func fakeClient(objs []object.ObjMetadata) resource.FakeClientFunc {
func fakeClient(objs object.ObjMetadataSet) resource.FakeClientFunc {
return func(version schema.GroupVersion) (resource.RESTClient, error) {
return &fake.RESTClient{
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
@ -94,7 +94,7 @@ func fakeClient(objs []object.ObjMetadata) resource.FakeClientFunc {
Name: "inventory",
Namespace: "test-namespace",
},
Data: objMap(objs),
Data: objs.ToStringMap(),
}
cmList.Items = append(cmList.Items, cm)
bodyRC := ioutil.NopCloser(bytes.NewReader(toJSONBytes(&cmList)))
@ -110,12 +110,3 @@ func toJSONBytes(obj runtime.Object) []byte {
objBytes, _ := runtime.Encode(unstructured.NewJSONFallbackEncoder(codec), obj)
return objBytes
}
func objMap(objs []object.ObjMetadata) map[string]string {
objMap := map[string]string{}
for _, obj := range objs {
objStr := obj.String()
objMap[objStr] = ""
}
return objMap
}

View File

@ -12,7 +12,7 @@ import (
// FakeInventoryClient is a testing implementation of the InventoryClient interface.
type FakeInventoryClient struct {
Objs []object.ObjMetadata
Objs object.ObjMetadataSet
Err error
}
@ -21,14 +21,14 @@ var (
_ InventoryClientFactory = FakeInventoryClientFactory{}
)
type FakeInventoryClientFactory []object.ObjMetadata
type FakeInventoryClientFactory object.ObjMetadataSet
func (f FakeInventoryClientFactory) NewInventoryClient(factory cmdutil.Factory) (InventoryClient, error) {
return NewFakeInventoryClient(f), nil
return NewFakeInventoryClient(object.ObjMetadataSet(f)), nil
}
// NewFakeInventoryClient returns a FakeInventoryClient.
func NewFakeInventoryClient(initObjs []object.ObjMetadata) *FakeInventoryClient {
func NewFakeInventoryClient(initObjs object.ObjMetadataSet) *FakeInventoryClient {
return &FakeInventoryClient{
Objs: initObjs,
Err: nil,
@ -36,9 +36,9 @@ func NewFakeInventoryClient(initObjs []object.ObjMetadata) *FakeInventoryClient
}
// GetClusterObjs returns currently stored set of objects.
func (fic *FakeInventoryClient) GetClusterObjs(InventoryInfo, common.DryRunStrategy) ([]object.ObjMetadata, error) {
func (fic *FakeInventoryClient) GetClusterObjs(InventoryInfo, common.DryRunStrategy) (object.ObjMetadataSet, error) {
if fic.Err != nil {
return []object.ObjMetadata{}, fic.Err
return object.ObjMetadataSet{}, fic.Err
}
return fic.Objs, nil
}
@ -46,19 +46,19 @@ func (fic *FakeInventoryClient) GetClusterObjs(InventoryInfo, common.DryRunStrat
// Merge stores the passed objects with the current stored cluster inventory
// objects. Returns the set difference of the current set of objects minus
// the passed set of objects, or an error if one is set up.
func (fic *FakeInventoryClient) Merge(_ InventoryInfo, objs []object.ObjMetadata, _ common.DryRunStrategy) ([]object.ObjMetadata, error) {
func (fic *FakeInventoryClient) Merge(_ InventoryInfo, objs object.ObjMetadataSet, _ common.DryRunStrategy) (object.ObjMetadataSet, error) {
if fic.Err != nil {
return []object.ObjMetadata{}, fic.Err
return object.ObjMetadataSet{}, fic.Err
}
diffObjs := object.SetDiff(fic.Objs, objs)
fic.Objs = object.Union(fic.Objs, objs)
diffObjs := fic.Objs.Diff(objs)
fic.Objs = fic.Objs.Union(objs)
return diffObjs, nil
}
// Replace the stored cluster inventory objs with the passed obj, or an
// error if one is set up.
func (fic *FakeInventoryClient) Replace(_ InventoryInfo, objs []object.ObjMetadata, _ common.DryRunStrategy) error {
func (fic *FakeInventoryClient) Replace(_ InventoryInfo, objs object.ObjMetadataSet, _ common.DryRunStrategy) error {
if fic.Err != nil {
return fic.Err
}

View File

@ -27,15 +27,15 @@ type InventoryClient interface {
// GetCluster returns the set of previously applied objects as ObjMetadata,
// or an error if one occurred. This set of previously applied object references
// is stored in the inventory objects living in the cluster.
GetClusterObjs(inv InventoryInfo, dryRun common.DryRunStrategy) ([]object.ObjMetadata, error)
GetClusterObjs(inv InventoryInfo, dryRun common.DryRunStrategy) (object.ObjMetadataSet, error)
// Merge applies the union of the passed objects with the currently
// stored objects in the inventory object. Returns the slice of
// objects which are a set diff (objects to be pruned). Otherwise,
// returns an error if one happened.
Merge(inv InventoryInfo, objs []object.ObjMetadata, dryRun common.DryRunStrategy) ([]object.ObjMetadata, error)
// stored objects in the inventory object. Returns the set of
// objects which are not in the passed objects (objects to be pruned).
// Otherwise, returns an error if one happened.
Merge(inv InventoryInfo, 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 InventoryInfo, objs []object.ObjMetadata, dryRun common.DryRunStrategy) error
Replace(inv InventoryInfo, objs object.ObjMetadataSet, dryRun common.DryRunStrategy) error
// DeleteInventoryObj deletes the passed inventory object from the APIServer.
DeleteInventoryObj(inv InventoryInfo, dryRun common.DryRunStrategy) error
// ApplyInventoryNamespace applies the Namespace that the inventory object should be in.
@ -94,8 +94,8 @@ func NewInventoryClient(factory cmdutil.Factory,
// to prune. Creates the initial cluster inventory object storing the passed
// objects if an inventory object does not exist. Returns an error if one
// occurred.
func (cic *ClusterInventoryClient) Merge(localInv InventoryInfo, objs []object.ObjMetadata, dryRun common.DryRunStrategy) ([]object.ObjMetadata, error) {
pruneIds := []object.ObjMetadata{}
func (cic *ClusterInventoryClient) Merge(localInv InventoryInfo, objs object.ObjMetadataSet, dryRun common.DryRunStrategy) (object.ObjMetadataSet, error) {
pruneIds := object.ObjMetadataSet{}
invObj := cic.invToUnstructuredFunc(localInv)
clusterInv, err := cic.GetClusterInventoryInfo(localInv, dryRun)
if err != nil {
@ -121,12 +121,12 @@ func (cic *ClusterInventoryClient) Merge(localInv InventoryInfo, objs []object.O
if err != nil {
return pruneIds, err
}
if object.SetEquals(objs, clusterObjs) {
if objs.Equal(clusterObjs) {
klog.V(4).Infof("applied objects same as cluster inventory: do nothing")
return pruneIds, nil
}
pruneIds = object.SetDiff(clusterObjs, objs)
unionObjs := object.Union(clusterObjs, objs)
pruneIds = clusterObjs.Diff(objs)
unionObjs := clusterObjs.Union(objs)
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)
@ -150,7 +150,7 @@ func (cic *ClusterInventoryClient) Merge(localInv InventoryInfo, objs []object.O
// Replace stores the passed objects in the cluster inventory object, or
// an error if one occurred.
func (cic *ClusterInventoryClient) Replace(localInv InventoryInfo, objs []object.ObjMetadata, dryRun common.DryRunStrategy) error {
func (cic *ClusterInventoryClient) Replace(localInv InventoryInfo, objs object.ObjMetadataSet, dryRun common.DryRunStrategy) error {
// Skip entire function for dry-run.
if dryRun.ClientOrServerDryRun() {
klog.V(4).Infoln("dry-run replace inventory object: not applied")
@ -160,7 +160,7 @@ func (cic *ClusterInventoryClient) Replace(localInv InventoryInfo, objs []object
if err != nil {
return err
}
if object.SetEquals(objs, clusterObjs) {
if objs.Equal(clusterObjs) {
klog.V(4).Infof("applied objects same as cluster inventory: do nothing")
return nil
}
@ -181,7 +181,7 @@ func (cic *ClusterInventoryClient) Replace(localInv InventoryInfo, objs []object
}
// replaceInventory stores the passed objects into the passed inventory object.
func (cic *ClusterInventoryClient) replaceInventory(inv *unstructured.Unstructured, objs []object.ObjMetadata) (*unstructured.Unstructured, error) {
func (cic *ClusterInventoryClient) replaceInventory(inv *unstructured.Unstructured, objs object.ObjMetadataSet) (*unstructured.Unstructured, error) {
wrappedInv := cic.InventoryFactoryFunc(inv)
if err := wrappedInv.Store(objs); err != nil {
return nil, err
@ -223,8 +223,8 @@ func (cic *ClusterInventoryClient) deleteInventoryObjsByLabel(inv InventoryInfo,
// GetClusterObjs returns the objects stored in the cluster inventory object, or
// an error if one occurred.
func (cic *ClusterInventoryClient) GetClusterObjs(localInv InventoryInfo, dryRun common.DryRunStrategy) ([]object.ObjMetadata, error) {
var objs []object.ObjMetadata
func (cic *ClusterInventoryClient) GetClusterObjs(localInv InventoryInfo, dryRun common.DryRunStrategy) (object.ObjMetadataSet, error) {
var objs object.ObjMetadataSet
clusterInv, err := cic.GetClusterInventoryInfo(localInv, dryRun)
if err != nil {
return objs, err
@ -378,7 +378,7 @@ func (cic *ClusterInventoryClient) mergeClusterInventory(invObjs []*unstructured
if err != nil {
return nil, err
}
retainedObjs = object.Union(retainedObjs, mergeObjs)
retainedObjs = retainedObjs.Union(mergeObjs)
}
if err := wrapRetained.Store(retainedObjs); err != nil {
return nil, err

View File

@ -22,29 +22,29 @@ import (
func TestGetClusterInventoryInfo(t *testing.T) {
tests := map[string]struct {
inv InventoryInfo
localObjs []object.ObjMetadata
localObjs object.ObjMetadataSet
isError bool
}{
"Nil local inventory object is an error": {
inv: nil,
localObjs: []object.ObjMetadata{},
localObjs: object.ObjMetadataSet{},
isError: true,
},
"Empty local inventory object": {
inv: localInv,
localObjs: []object.ObjMetadata{},
localObjs: object.ObjMetadataSet{},
isError: false,
},
"Local inventory with a single object": {
inv: localInv,
localObjs: []object.ObjMetadata{
localObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod2Info),
},
isError: false,
},
"Local inventory with multiple objects": {
inv: localInv,
localObjs: []object.ObjMetadata{
localObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod1Info),
ignoreErrInfoToObjMeta(pod2Info),
ignoreErrInfoToObjMeta(pod3Info)},
@ -82,7 +82,7 @@ func TestGetClusterInventoryInfo(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error received: %s", err)
}
if !object.SetEquals(tc.localObjs, clusterObjs) {
if !tc.localObjs.Equal(clusterObjs) {
t.Fatalf("expected cluster objs (%v), got (%v)", tc.localObjs, clusterObjs)
}
}
@ -93,60 +93,60 @@ func TestGetClusterInventoryInfo(t *testing.T) {
func TestMerge(t *testing.T) {
tests := map[string]struct {
localInv InventoryInfo
localObjs []object.ObjMetadata
clusterObjs []object.ObjMetadata
pruneObjs []object.ObjMetadata
localObjs object.ObjMetadataSet
clusterObjs object.ObjMetadataSet
pruneObjs object.ObjMetadataSet
isError bool
}{
"Nil local inventory object is error": {
localInv: nil,
localObjs: []object.ObjMetadata{},
clusterObjs: []object.ObjMetadata{},
pruneObjs: []object.ObjMetadata{},
localObjs: object.ObjMetadataSet{},
clusterObjs: object.ObjMetadataSet{},
pruneObjs: object.ObjMetadataSet{},
isError: true,
},
"Cluster and local inventories empty: no prune objects; no change": {
localInv: copyInventory(),
localObjs: []object.ObjMetadata{},
clusterObjs: []object.ObjMetadata{},
pruneObjs: []object.ObjMetadata{},
localObjs: object.ObjMetadataSet{},
clusterObjs: object.ObjMetadataSet{},
pruneObjs: object.ObjMetadataSet{},
isError: false,
},
"Cluster and local inventories same: no prune objects; no change": {
localInv: copyInventory(),
localObjs: []object.ObjMetadata{
localObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod1Info),
},
clusterObjs: []object.ObjMetadata{
clusterObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod1Info),
},
pruneObjs: []object.ObjMetadata{},
pruneObjs: object.ObjMetadataSet{},
isError: false,
},
"Cluster two obj, local one: prune obj": {
localInv: copyInventory(),
localObjs: []object.ObjMetadata{
localObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod1Info),
},
clusterObjs: []object.ObjMetadata{
clusterObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod1Info),
ignoreErrInfoToObjMeta(pod3Info),
},
pruneObjs: []object.ObjMetadata{
pruneObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod3Info),
},
isError: false,
},
"Cluster multiple objs, local multiple different objs: prune objs": {
localInv: copyInventory(),
localObjs: []object.ObjMetadata{
localObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod2Info),
},
clusterObjs: []object.ObjMetadata{
clusterObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod1Info),
ignoreErrInfoToObjMeta(pod2Info),
ignoreErrInfoToObjMeta(pod3Info)},
pruneObjs: []object.ObjMetadata{
pruneObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod1Info),
ignoreErrInfoToObjMeta(pod3Info),
},
@ -180,7 +180,7 @@ func TestMerge(t *testing.T) {
if !tc.isError && err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !object.SetEquals(tc.pruneObjs, pruneObjs) {
if !tc.pruneObjs.Equal(pruneObjs) {
t.Errorf("expected (%v) prune objs; got (%v)", tc.pruneObjs, pruneObjs)
}
})
@ -191,29 +191,29 @@ func TestMerge(t *testing.T) {
func TestCreateInventory(t *testing.T) {
tests := map[string]struct {
inv InventoryInfo
localObjs []object.ObjMetadata
localObjs object.ObjMetadataSet
isError bool
}{
"Nil local inventory object is an error": {
inv: nil,
localObjs: []object.ObjMetadata{},
localObjs: object.ObjMetadataSet{},
isError: true,
},
"Empty local inventory object": {
inv: localInv,
localObjs: []object.ObjMetadata{},
localObjs: object.ObjMetadataSet{},
isError: false,
},
"Local inventory with a single object": {
inv: localInv,
localObjs: []object.ObjMetadata{
localObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod2Info),
},
isError: false,
},
"Local inventory with multiple objects": {
inv: localInv,
localObjs: []object.ObjMetadata{
localObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod1Info),
ignoreErrInfoToObjMeta(pod2Info),
ignoreErrInfoToObjMeta(pod3Info)},
@ -267,35 +267,35 @@ func TestCreateInventory(t *testing.T) {
func TestReplace(t *testing.T) {
tests := map[string]struct {
localObjs []object.ObjMetadata
clusterObjs []object.ObjMetadata
localObjs object.ObjMetadataSet
clusterObjs object.ObjMetadataSet
}{
"Cluster and local inventories empty": {
localObjs: []object.ObjMetadata{},
clusterObjs: []object.ObjMetadata{},
localObjs: object.ObjMetadataSet{},
clusterObjs: object.ObjMetadataSet{},
},
"Cluster and local inventories same": {
localObjs: []object.ObjMetadata{
localObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod1Info),
},
clusterObjs: []object.ObjMetadata{
clusterObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod1Info),
},
},
"Cluster two obj, local one": {
localObjs: []object.ObjMetadata{
localObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod1Info),
},
clusterObjs: []object.ObjMetadata{
clusterObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod1Info),
ignoreErrInfoToObjMeta(pod3Info),
},
},
"Cluster multiple objs, local multiple different objs": {
localObjs: []object.ObjMetadata{
localObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod2Info),
},
clusterObjs: []object.ObjMetadata{
clusterObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod1Info),
ignoreErrInfoToObjMeta(pod2Info),
ignoreErrInfoToObjMeta(pod3Info)},
@ -307,11 +307,11 @@ func TestReplace(t *testing.T) {
// Client and server dry-run do not throw errors.
invClient, _ := NewInventoryClient(tf, WrapInventoryObj, InvInfoToConfigMap)
err := invClient.Replace(copyInventory(), []object.ObjMetadata{}, common.DryRunClient)
err := invClient.Replace(copyInventory(), object.ObjMetadataSet{}, common.DryRunClient)
if err != nil {
t.Fatalf("unexpected error received: %s", err)
}
err = invClient.Replace(copyInventory(), []object.ObjMetadata{}, common.DryRunServer)
err = invClient.Replace(copyInventory(), object.ObjMetadataSet{}, common.DryRunServer)
if err != nil {
t.Fatalf("unexpected error received: %s", err)
}
@ -340,7 +340,7 @@ func TestReplace(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error received: %s", err)
}
if !object.SetEquals(tc.localObjs, actualObjs) {
if !tc.localObjs.Equal(actualObjs) {
t.Errorf("expected objects (%s), got (%s)", tc.localObjs, actualObjs)
}
})
@ -350,27 +350,27 @@ func TestReplace(t *testing.T) {
func TestGetClusterObjs(t *testing.T) {
tests := map[string]struct {
localInv InventoryInfo
clusterObjs []object.ObjMetadata
clusterObjs object.ObjMetadataSet
isError bool
}{
"Nil cluster inventory is error": {
localInv: nil,
clusterObjs: []object.ObjMetadata{},
clusterObjs: object.ObjMetadataSet{},
isError: true,
},
"No cluster objs": {
localInv: copyInventory(),
clusterObjs: []object.ObjMetadata{},
clusterObjs: object.ObjMetadataSet{},
isError: false,
},
"Single cluster obj": {
localInv: copyInventory(),
clusterObjs: []object.ObjMetadata{ignoreErrInfoToObjMeta(pod1Info)},
clusterObjs: object.ObjMetadataSet{ignoreErrInfoToObjMeta(pod1Info)},
isError: false,
},
"Multiple cluster objs": {
localInv: copyInventory(),
clusterObjs: []object.ObjMetadata{ignoreErrInfoToObjMeta(pod1Info), ignoreErrInfoToObjMeta(pod3Info)},
clusterObjs: object.ObjMetadataSet{ignoreErrInfoToObjMeta(pod1Info), ignoreErrInfoToObjMeta(pod3Info)},
isError: false,
},
}
@ -397,7 +397,7 @@ func TestGetClusterObjs(t *testing.T) {
if !tc.isError && err != nil {
t.Fatalf("unexpected error received: %s", err)
}
if !object.SetEquals(tc.clusterObjs, clusterObjs) {
if !tc.clusterObjs.Equal(clusterObjs) {
t.Errorf("expected (%v) cluster inventory objs; got (%v)", tc.clusterObjs, clusterObjs)
}
})
@ -407,25 +407,25 @@ func TestGetClusterObjs(t *testing.T) {
func TestDeleteInventoryObj(t *testing.T) {
tests := map[string]struct {
inv InventoryInfo
localObjs []object.ObjMetadata
localObjs object.ObjMetadataSet
}{
"Nil local inventory object is an error": {
inv: nil,
localObjs: []object.ObjMetadata{},
localObjs: object.ObjMetadataSet{},
},
"Empty local inventory object": {
inv: localInv,
localObjs: []object.ObjMetadata{},
localObjs: object.ObjMetadataSet{},
},
"Local inventory with a single object": {
inv: localInv,
localObjs: []object.ObjMetadata{
localObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod2Info),
},
},
"Local inventory with multiple objects": {
inv: localInv,
localObjs: []object.ObjMetadata{
localObjs: object.ObjMetadataSet{
ignoreErrInfoToObjMeta(pod1Info),
ignoreErrInfoToObjMeta(pod2Info),
ignoreErrInfoToObjMeta(pod3Info)},
@ -477,7 +477,7 @@ func TestDeleteInventoryObj(t *testing.T) {
type invAndObjs struct {
inv InventoryInfo
invObjs []object.ObjMetadata
invObjs object.ObjMetadataSet
}
func TestMergeInventoryObjs(t *testing.T) {
@ -486,68 +486,68 @@ func TestMergeInventoryObjs(t *testing.T) {
pod3Obj := ignoreErrInfoToObjMeta(pod3Info)
tests := map[string]struct {
invs []invAndObjs
expected []object.ObjMetadata
expected object.ObjMetadataSet
}{
"Single inventory object with no inventory is valid": {
invs: []invAndObjs{
{
inv: copyInventory(),
invObjs: []object.ObjMetadata{},
invObjs: object.ObjMetadataSet{},
},
},
expected: []object.ObjMetadata{},
expected: object.ObjMetadataSet{},
},
"Single inventory object returns same objects": {
invs: []invAndObjs{
{
inv: copyInventory(),
invObjs: []object.ObjMetadata{pod1Obj},
invObjs: object.ObjMetadataSet{pod1Obj},
},
},
expected: []object.ObjMetadata{pod1Obj},
expected: object.ObjMetadataSet{pod1Obj},
},
"Two inventories with the same objects returns them": {
invs: []invAndObjs{
{
inv: copyInventory(),
invObjs: []object.ObjMetadata{pod1Obj},
invObjs: object.ObjMetadataSet{pod1Obj},
},
{
inv: copyInventory(),
invObjs: []object.ObjMetadata{pod1Obj},
invObjs: object.ObjMetadataSet{pod1Obj},
},
},
expected: []object.ObjMetadata{pod1Obj},
expected: object.ObjMetadataSet{pod1Obj},
},
"Two inventories with different retain the union": {
invs: []invAndObjs{
{
inv: copyInventory(),
invObjs: []object.ObjMetadata{pod1Obj},
invObjs: object.ObjMetadataSet{pod1Obj},
},
{
inv: copyInventory(),
invObjs: []object.ObjMetadata{pod2Obj},
invObjs: object.ObjMetadataSet{pod2Obj},
},
},
expected: []object.ObjMetadata{pod1Obj, pod2Obj},
expected: object.ObjMetadataSet{pod1Obj, pod2Obj},
},
"More than two inventory objects retains all objects": {
invs: []invAndObjs{
{
inv: copyInventory(),
invObjs: []object.ObjMetadata{pod1Obj, pod2Obj},
invObjs: object.ObjMetadataSet{pod1Obj, pod2Obj},
},
{
inv: copyInventory(),
invObjs: []object.ObjMetadata{pod2Obj},
invObjs: object.ObjMetadataSet{pod2Obj},
},
{
inv: copyInventory(),
invObjs: []object.ObjMetadata{pod3Obj},
invObjs: object.ObjMetadataSet{pod3Obj},
},
},
expected: []object.ObjMetadata{pod1Obj, pod2Obj, pod3Obj},
expected: object.ObjMetadataSet{pod1Obj, pod2Obj, pod3Obj},
},
}
@ -571,7 +571,7 @@ func TestMergeInventoryObjs(t *testing.T) {
}
wrapped := WrapInventoryObj(retained)
mergedObjs, _ := wrapped.Load()
if !object.SetEquals(tc.expected, mergedObjs) {
if !tc.expected.Equal(mergedObjs) {
t.Errorf("expected merged inventory objects (%v), got (%v)", tc.expected, mergedObjs)
}
})

View File

@ -29,9 +29,9 @@ const legacyInvName = "inventory"
// operations.
type Inventory interface {
// Load retrieves the set of object metadata from the inventory object
Load() ([]object.ObjMetadata, error)
Load() (object.ObjMetadataSet, error)
// Store the set of object metadata in the inventory object
Store(objs []object.ObjMetadata) error
Store(objs object.ObjMetadataSet) error
// GetObject returns the object that stores the inventory
GetObject() (*unstructured.Unstructured, error)
}

View File

@ -418,7 +418,7 @@ func copyInventory() InventoryInfo {
return WrapInventoryInfoObj(u)
}
func storeObjsInInventory(info InventoryInfo, objs []object.ObjMetadata) *unstructured.Unstructured {
func storeObjsInInventory(info InventoryInfo, objs object.ObjMetadataSet) *unstructured.Unstructured {
wrapped := WrapInventoryObj(InvInfoToConfigMap(info))
_ = wrapped.Store(objs)
inv, _ := wrapped.GetObject()

View File

@ -43,7 +43,7 @@ func InvInfoToConfigMap(inv InventoryInfo) *unstructured.Unstructured {
// object metadata (inventory) to and from the wrapped ConfigMap.
type InventoryConfigMap struct {
inv *unstructured.Unstructured
objMetas []object.ObjMetadata
objMetas object.ObjMetadataSet
}
var _ InventoryInfo = &InventoryConfigMap{}
@ -72,8 +72,8 @@ func (icm *InventoryConfigMap) UnstructuredInventory() *unstructured.Unstructure
// Load is an Inventory interface function returning the set of
// object metadata from the wrapped ConfigMap, or an error.
func (icm *InventoryConfigMap) Load() ([]object.ObjMetadata, error) {
objs := []object.ObjMetadata{}
func (icm *InventoryConfigMap) Load() (object.ObjMetadataSet, error) {
objs := object.ObjMetadataSet{}
objMap, exists, err := unstructured.NestedStringMap(icm.inv.Object, "data")
if err != nil {
err := fmt.Errorf("error retrieving object metadata from inventory object")
@ -94,7 +94,7 @@ func (icm *InventoryConfigMap) Load() ([]object.ObjMetadata, error) {
// Store is an Inventory interface function implemented to store
// the object metadata in the wrapped ConfigMap. Actual storing
// happens in "GetObject".
func (icm *InventoryConfigMap) Store(objMetas []object.ObjMetadata) error {
func (icm *InventoryConfigMap) Store(objMetas object.ObjMetadataSet) error {
icm.objMetas = objMetas
return nil
}
@ -115,7 +115,7 @@ func (icm *InventoryConfigMap) GetObject() (*unstructured.Unstructured, error) {
return invCopy, nil
}
func buildObjMap(objMetas []object.ObjMetadata) map[string]string {
func buildObjMap(objMetas object.ObjMetadataSet) map[string]string {
objMap := map[string]string{}
for _, objMetadata := range objMetas {
objMap[objMetadata.String()] = ""

View File

@ -45,11 +45,11 @@ var genGroupKinds = map[schema.GroupKind][]schema.GroupKind{
// NewCachingClusterReader returns a new instance of the ClusterReader. The
// ClusterReader needs will use the clusterreader to fetch resources from the cluster,
// while the mapper is used to resolve the version for GroupKinds. The list of
// while the mapper is used to resolve the version for GroupKinds. The set of
// identifiers is needed so the ClusterReader can figure out which GroupKind
// and namespace combinations it needs to cache when the Sync function is called.
// We only want to fetch the resources that are actually needed.
func NewCachingClusterReader(reader client.Reader, mapper meta.RESTMapper, identifiers []object.ObjMetadata) (*CachingClusterReader, error) {
func NewCachingClusterReader(reader client.Reader, mapper meta.RESTMapper, identifiers object.ObjMetadataSet) (*CachingClusterReader, error) {
gvkNamespaceSet := newGnSet()
for _, id := range identifiers {
// For every identifier, add the GroupVersionKind and namespace combination to the gvkNamespaceSet and

View File

@ -30,14 +30,14 @@ var (
func TestSync(t *testing.T) {
testCases := map[string]struct {
identifiers []object.ObjMetadata
identifiers object.ObjMetadataSet
expectedSynced []gkNamespace
}{
"no identifiers": {
identifiers: []object.ObjMetadata{},
identifiers: object.ObjMetadataSet{},
},
"same GVK in multiple namespaces": {
identifiers: []object.ObjMetadata{
identifiers: object.ObjMetadataSet{
{
GroupKind: deploymentGVK.GroupKind(),
Name: "deployment",
@ -152,7 +152,7 @@ func TestSync_Errors(t *testing.T) {
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
identifiers := []object.ObjMetadata{
identifiers := object.ObjMetadataSet{
{
Name: "my-crd",
GroupKind: schema.GroupKind{

View File

@ -12,7 +12,7 @@ import (
"sigs.k8s.io/cli-utils/pkg/object"
)
func NewResourceStatusCollector(identifiers []object.ObjMetadata) *ResourceStatusCollector {
func NewResourceStatusCollector(identifiers object.ObjMetadataSet) *ResourceStatusCollector {
resourceStatuses := make(map[object.ObjMetadata]*event.ResourceStatus)
for _, id := range identifiers {
resourceStatuses[id] = &event.ResourceStatus{

View File

@ -17,7 +17,7 @@ import (
)
func TestCollectorStopsWhenEventChannelIsClosed(t *testing.T) {
var identifiers []object.ObjMetadata
var identifiers object.ObjMetadataSet
collector := NewResourceStatusCollector(identifiers)
@ -39,7 +39,7 @@ func TestCollectorStopsWhenEventChannelIsClosed(t *testing.T) {
}
func TestCollectorWithFatalError(t *testing.T) {
var identifiers []object.ObjMetadata
var identifiers object.ObjMetadataSet
collector := NewResourceStatusCollector(identifiers)
@ -91,12 +91,12 @@ var (
func TestCollectorEventProcessing(t *testing.T) {
testCases := map[string]struct {
identifiers []object.ObjMetadata
identifiers object.ObjMetadataSet
events []event.Event
}{
"no resources and no events": {},
"single resource and single event": {
identifiers: []object.ObjMetadata{
identifiers: object.ObjMetadataSet{
resourceIdentifiers["deployment"],
},
events: []event.Event{
@ -109,7 +109,7 @@ func TestCollectorEventProcessing(t *testing.T) {
},
},
"multiple resources and multiple events": {
identifiers: []object.ObjMetadata{
identifiers: object.ObjMetadataSet{
resourceIdentifiers["deployment"],
resourceIdentifiers["statefulSet"],
},

View File

@ -18,7 +18,7 @@ import (
// ClusterReaderFactoryFunc defines the signature for the function the PollerEngine will use to create
// a new ClusterReader for each statusPollerRunner.
type ClusterReaderFactoryFunc func(reader client.Reader, mapper meta.RESTMapper,
identifiers []object.ObjMetadata) (ClusterReader, error)
identifiers object.ObjMetadataSet) (ClusterReader, error)
// StatusReadersFactoryFunc defines the signature for the function the PollerEngine will use to
// create the resource statusReaders and the default engine for each statusPollerRunner.
@ -36,7 +36,7 @@ type PollerEngine struct {
// context passed in.
// The context can be used to stop the polling process by using timeout, deadline or
// cancellation.
func (s *PollerEngine) Poll(ctx context.Context, identifiers []object.ObjMetadata, options Options) <-chan event.Event {
func (s *PollerEngine) Poll(ctx context.Context, identifiers object.ObjMetadataSet, options Options) <-chan event.Event {
eventChannel := make(chan event.Event)
go func() {
@ -97,7 +97,7 @@ func (s *PollerEngine) validate(options Options) error {
// validateIdentifiers makes sure that all namespaced resources
// passed in
func (s *PollerEngine) validateIdentifiers(identifiers []object.ObjMetadata) error {
func (s *PollerEngine) validateIdentifiers(identifiers object.ObjMetadataSet) error {
for _, id := range identifiers {
mapping, err := s.Mapper.RESTMapping(id.GroupKind)
if err != nil {
@ -162,9 +162,9 @@ type statusPollerRunner struct {
// doesn't have a specific engine in the statusReaders map.
defaultStatusReader StatusReader
// identifiers contains the list of identifiers for the resources that should be polled.
// identifiers contains the set of identifiers for the resources that should be polled.
// Each resource is identified by GroupKind, namespace and name.
identifiers []object.ObjMetadata
identifiers object.ObjMetadataSet
// previousResourceStatuses keeps track of the last event for each
// of the polled resources. This is used to make sure we only

View File

@ -25,12 +25,12 @@ import (
func TestStatusPollerRunner(t *testing.T) {
testCases := map[string]struct {
identifiers []object.ObjMetadata
identifiers object.ObjMetadataSet
defaultStatusReader StatusReader
expectedEventTypes []event.EventType
}{
"single resource": {
identifiers: []object.ObjMetadata{
identifiers: object.ObjMetadataSet{
{
GroupKind: schema.GroupKind{
Group: "apps",
@ -55,7 +55,7 @@ func TestStatusPollerRunner(t *testing.T) {
},
},
"multiple resources": {
identifiers: []object.ObjMetadata{
identifiers: object.ObjMetadataSet{
{
GroupKind: schema.GroupKind{
Group: "apps",
@ -114,7 +114,7 @@ func TestStatusPollerRunner(t *testing.T) {
options := Options{
PollInterval: 2 * time.Second,
ClusterReaderFactoryFunc: func(_ client.Reader, _ meta.RESTMapper, _ []object.ObjMetadata) (
ClusterReaderFactoryFunc: func(_ client.Reader, _ meta.RESTMapper, _ object.ObjMetadataSet) (
ClusterReader, error) {
return testutil.NewNoopClusterReader(), nil
},
@ -140,7 +140,7 @@ func TestStatusPollerRunner(t *testing.T) {
}
func TestNewStatusPollerRunnerCancellation(t *testing.T) {
identifiers := make([]object.ObjMetadata, 0)
identifiers := make(object.ObjMetadataSet, 0)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
@ -151,7 +151,7 @@ func TestNewStatusPollerRunnerCancellation(t *testing.T) {
options := Options{
PollInterval: 2 * time.Second,
ClusterReaderFactoryFunc: func(_ client.Reader, _ meta.RESTMapper, _ []object.ObjMetadata) (
ClusterReaderFactoryFunc: func(_ client.Reader, _ meta.RESTMapper, _ object.ObjMetadataSet) (
ClusterReader, error) {
return testutil.NewNoopClusterReader(), nil
},
@ -176,7 +176,7 @@ func TestNewStatusPollerRunnerCancellation(t *testing.T) {
}
func TestNewStatusPollerRunnerIdentifierValidation(t *testing.T) {
identifiers := []object.ObjMetadata{
identifiers := object.ObjMetadataSet{
{
GroupKind: schema.GroupKind{
Group: "apps",
@ -193,7 +193,7 @@ func TestNewStatusPollerRunnerIdentifierValidation(t *testing.T) {
}
eventChannel := engine.Poll(context.Background(), identifiers, Options{
ClusterReaderFactoryFunc: func(_ client.Reader, _ meta.RESTMapper, _ []object.ObjMetadata) (
ClusterReaderFactoryFunc: func(_ client.Reader, _ meta.RESTMapper, _ object.ObjMetadataSet) (
ClusterReader, error) {
return testutil.NewNoopClusterReader(), nil
},

View File

@ -38,7 +38,7 @@ type StatusPoller struct {
// Poll will create a new statusPollerRunner that will poll all the resources provided and report their status
// back on the event channel returned. The statusPollerRunner can be cancelled at any time by cancelling the
// context passed in.
func (s *StatusPoller) Poll(ctx context.Context, identifiers []object.ObjMetadata, options Options) <-chan event.Event {
func (s *StatusPoller) Poll(ctx context.Context, identifiers object.ObjMetadataSet, options Options) <-chan event.Event {
statusReaderFactory := createStatusReaders
if options.CustomStatusReadersFactoryFunc != nil {
statusReaderFactory = func(reader engine.ClusterReader, mapper meta.RESTMapper) (map[schema.GroupKind]engine.StatusReader, engine.StatusReader) {
@ -102,7 +102,7 @@ func createStatusReaders(reader engine.ClusterReader, mapper meta.RESTMapper) (m
// decided here rather than based on information passed in to the factory function. Thus, the decision
// for which implementation is decided when the StatusPoller is created.
func clusterReaderFactoryFunc(useCache bool) engine.ClusterReaderFactoryFunc {
return func(r client.Reader, mapper meta.RESTMapper, identifiers []object.ObjMetadata) (engine.ClusterReader, error) {
return func(r client.Reader, mapper meta.RESTMapper, identifiers object.ObjMetadataSet) (engine.ClusterReader, error) {
if useCache {
return clusterreader.NewCachingClusterReader(r, mapper, identifiers)
}

View File

@ -20,7 +20,7 @@ type fakeLoader struct {
var _ ManifestLoader = &fakeLoader{}
func NewFakeLoader(f util.Factory, objs []object.ObjMetadata) *fakeLoader {
func NewFakeLoader(f util.Factory, objs object.ObjMetadataSet) *fakeLoader {
return &fakeLoader{
factory: f,
InvClient: inventory.NewFakeInventoryClient(objs),

View File

@ -10,10 +10,10 @@ import (
// DependencySet is a set of object references.
// When testing equality, order is not importent.
type DependencySet []object.ObjMetadata
type DependencySet object.ObjMetadataSet
// Equal returns true if the ObjMetadata sets are equivalent, ignoring order.
// Fulfills Equal interface from github.com/google/go-cmp
func (a DependencySet) Equal(b DependencySet) bool {
return object.SetEquals(a, b)
return object.ObjMetadataSet(a).Equal(object.ObjMetadataSet(b))
}

View File

@ -18,7 +18,7 @@ import (
// vertices).
type Graph struct {
// map "from" vertex -> list of "to" vertices
edges map[object.ObjMetadata][]object.ObjMetadata
edges map[object.ObjMetadata]object.ObjMetadataSet
}
// Edge encapsulates a pair of vertices describing a
@ -31,7 +31,7 @@ type Edge struct {
// New returns a pointer to an empty Graph data structure.
func New() *Graph {
g := &Graph{}
g.edges = make(map[object.ObjMetadata][]object.ObjMetadata)
g.edges = make(map[object.ObjMetadata]object.ObjMetadataSet)
return g
}
@ -39,7 +39,7 @@ func New() *Graph {
// an initial empty set of edges from added vertex.
func (g *Graph) AddVertex(v object.ObjMetadata) {
if _, exists := g.edges[v]; !exists {
g.edges[v] = []object.ObjMetadata{}
g.edges[v] = object.ObjMetadataSet{}
}
}
@ -48,11 +48,11 @@ func (g *Graph) AddVertex(v object.ObjMetadata) {
func (g *Graph) AddEdge(from object.ObjMetadata, to object.ObjMetadata) {
// Add "from" vertex if it doesn't already exist.
if _, exists := g.edges[from]; !exists {
g.edges[from] = []object.ObjMetadata{}
g.edges[from] = object.ObjMetadataSet{}
}
// Add "to" vertex if it doesn't already exist.
if _, exists := g.edges[to]; !exists {
g.edges[to] = []object.ObjMetadata{}
g.edges[to] = object.ObjMetadataSet{}
}
// Add edge "from" -> "to" if it doesn't already exist
// into the adjacency list.
@ -100,31 +100,19 @@ func (g *Graph) Size() int {
func (g *Graph) removeVertex(r object.ObjMetadata) {
// First, remove the object from all adjacency lists.
for v, adj := range g.edges {
for i, a := range adj {
if a == r {
g.edges[v] = removeObj(adj, i)
break
}
}
g.edges[v] = adj.Remove(r)
}
// Finally, remove the vertex
delete(g.edges, r)
}
// removeObj removes the object at index "i" from the passed
// list of vertices, returning the new list.
func removeObj(adj []object.ObjMetadata, i int) []object.ObjMetadata {
adj[len(adj)-1], adj[i] = adj[i], adj[len(adj)-1]
return adj[:len(adj)-1]
}
// Sort returns the ordered set of vertices after
// a topological sort.
func (g *Graph) Sort() ([][]object.ObjMetadata, error) {
sorted := [][]object.ObjMetadata{}
func (g *Graph) Sort() ([]object.ObjMetadataSet, error) {
sorted := []object.ObjMetadataSet{}
for g.Size() > 0 {
// Identify all the leaf vertices.
leafVertices := []object.ObjMetadata{}
leafVertices := object.ObjMetadataSet{}
for v, adj := range g.edges {
if len(adj) == 0 {
leafVertices = append(leafVertices, v)
@ -133,7 +121,7 @@ func (g *Graph) Sort() ([][]object.ObjMetadata, error) {
// No leaf vertices means cycle in the directed graph,
// where remaining edges define the cycle.
if len(leafVertices) == 0 {
return [][]object.ObjMetadata{}, CyclicDependencyError{
return []object.ObjMetadataSet{}, CyclicDependencyError{
Edges: g.GetEdges(),
}
}

View File

@ -33,63 +33,63 @@ var (
func TestObjectGraphSort(t *testing.T) {
testCases := map[string]struct {
vertices []object.ObjMetadata
vertices object.ObjMetadataSet
edges []Edge
expected [][]object.ObjMetadata
expected []object.ObjMetadataSet
isError bool
}{
"one edge": {
vertices: []object.ObjMetadata{o1, o2},
vertices: object.ObjMetadataSet{o1, o2},
edges: []Edge{e1},
expected: [][]object.ObjMetadata{{o2}, {o1}},
expected: []object.ObjMetadataSet{{o2}, {o1}},
isError: false,
},
"two edges": {
vertices: []object.ObjMetadata{o1, o2, o3},
vertices: object.ObjMetadataSet{o1, o2, o3},
edges: []Edge{e1, e2},
expected: [][]object.ObjMetadata{{o3}, {o2}, {o1}},
expected: []object.ObjMetadataSet{{o3}, {o2}, {o1}},
isError: false,
},
"three edges": {
vertices: []object.ObjMetadata{o1, o2, o3},
vertices: object.ObjMetadataSet{o1, o2, o3},
edges: []Edge{e1, e3, e2},
expected: [][]object.ObjMetadata{{o3}, {o2}, {o1}},
expected: []object.ObjMetadataSet{{o3}, {o2}, {o1}},
isError: false,
},
"four edges": {
vertices: []object.ObjMetadata{o1, o2, o3, o4},
vertices: object.ObjMetadataSet{o1, o2, o3, o4},
edges: []Edge{e1, e2, e4, e5},
expected: [][]object.ObjMetadata{{o4}, {o3}, {o2}, {o1}},
expected: []object.ObjMetadataSet{{o4}, {o3}, {o2}, {o1}},
isError: false,
},
"five edges": {
vertices: []object.ObjMetadata{o1, o2, o3, o4},
vertices: object.ObjMetadataSet{o1, o2, o3, o4},
edges: []Edge{e5, e1, e3, e2, e4},
expected: [][]object.ObjMetadata{{o4}, {o3}, {o2}, {o1}},
expected: []object.ObjMetadataSet{{o4}, {o3}, {o2}, {o1}},
isError: false,
},
"no edges means all in the same first set": {
vertices: []object.ObjMetadata{o1, o2, o3, o4},
vertices: object.ObjMetadataSet{o1, o2, o3, o4},
edges: []Edge{},
expected: [][]object.ObjMetadata{{o4, o3, o2, o1}},
expected: []object.ObjMetadataSet{{o4, o3, o2, o1}},
isError: false,
},
"multiple objects in first set": {
vertices: []object.ObjMetadata{o1, o2, o3, o4, o5},
vertices: object.ObjMetadataSet{o1, o2, o3, o4, o5},
edges: []Edge{e1, e2, e5, e8},
expected: [][]object.ObjMetadata{{o5, o3}, {o4}, {o2}, {o1}},
expected: []object.ObjMetadataSet{{o5, o3}, {o4}, {o2}, {o1}},
isError: false,
},
"simple cycle in graph is an error": {
vertices: []object.ObjMetadata{o1, o2},
vertices: object.ObjMetadataSet{o1, o2},
edges: []Edge{e1, e6},
expected: [][]object.ObjMetadata{},
expected: []object.ObjMetadataSet{},
isError: true,
},
"multi-edge cycle in graph is an error": {
vertices: []object.ObjMetadata{o1, o2, o3},
vertices: object.ObjMetadataSet{o1, o2, o3},
edges: []Edge{e1, e2, e7},
expected: [][]object.ObjMetadata{},
expected: []object.ObjMetadataSet{},
isError: true,
},
}
@ -116,7 +116,7 @@ func TestObjectGraphSort(t *testing.T) {
}
for i, actualSet := range actual {
expectedSet := tc.expected[i]
if !object.SetEquals(expectedSet, actualSet) {
if !expectedSet.Equal(actualSet) {
t.Errorf("expected sorted objects (%s), got (%s)", tc.expected, actual)
}
}

View File

@ -17,9 +17,6 @@ package object
import (
"fmt"
"hash/fnv"
"sort"
"strconv"
"strings"
rbacv1 "k8s.io/api/rbac/v1"
@ -60,19 +57,6 @@ type ObjMetadata struct {
GroupKind schema.GroupKind
}
// ObjMetas is a slice of ObjMetadata.
type ObjMetas []ObjMetadata
// Contains checks if the provided ObjMetadata exists in the ObjMetas slice.
func (oms ObjMetas) Contains(id ObjMetadata) bool {
for _, om := range oms {
if om == id {
return true
}
}
return false
}
// CreateObjMetadata returns an ObjMetadata struct filled
// with the passed values. This function normalizes and validates the
// passed fields and returns an error for bad parameters.
@ -172,92 +156,3 @@ func RuntimeToObjMeta(obj runtime.Object) (ObjMetadata, error) {
return CreateObjMetadata(accessor.GetNamespace(), accessor.GetName(),
obj.GetObjectKind().GroupVersionKind().GroupKind())
}
// Hash returns a hash of the sorted strings from
// the object metadata, or an error if one occurred.
func Hash(objs []ObjMetadata) (string, error) {
objStrs := make([]string, 0, len(objs))
for _, obj := range objs {
objStrs = append(objStrs, obj.String())
}
hashInt, err := calcHash(objStrs)
if err != nil {
return "", err
}
return strconv.FormatUint(uint64(hashInt), 16), nil
}
// calcHash returns an unsigned int32 representing the hash
// of the obj metadata strings. If there is an error writing bytes to
// the hash, then the error is returned; nil is returned otherwise.
// Used to quickly identify the set of resources in the inventory object.
func calcHash(objs []string) (uint32, error) {
sort.Strings(objs)
h := fnv.New32a()
for _, obj := range objs {
_, err := h.Write([]byte(obj))
if err != nil {
return uint32(0), err
}
}
return h.Sum32(), nil
}
// SetDiff returns the slice of objects that exist in "a", but
// do not exist in "b" (A - B).
func SetDiff(setA []ObjMetadata, setB []ObjMetadata) []ObjMetadata {
// Create a map of the elements of A
m := make(map[ObjMetadata]struct{}, len(setA))
for _, a := range setA {
m[a] = struct{}{}
}
// Remove from A each element of B
for _, b := range setB {
delete(m, b) // OK to delete even if b not in m
}
// Create/return slice from the map of remaining items
diff := make([]ObjMetadata, 0, len(m))
for r := range m {
diff = append(diff, r)
}
return diff
}
// Union returns the slice of objects that is the set of unique
// items of the merging of set A and set B.
func Union(setA []ObjMetadata, setB []ObjMetadata) []ObjMetadata {
m := make(map[ObjMetadata]struct{}, len(setA)+len(setB))
for _, a := range setA {
m[a] = struct{}{}
}
for _, b := range setB {
m[b] = struct{}{}
}
union := make([]ObjMetadata, 0, len(m))
for u := range m {
union = append(union, u)
}
return union
}
// SetEquals returns true if the slice of objects in setA equals
// the slice of objects in setB.
func SetEquals(setA []ObjMetadata, setB []ObjMetadata) bool {
mapA := make(map[ObjMetadata]struct{}, len(setA))
for _, a := range setA {
mapA[a] = struct{}{}
}
mapB := make(map[ObjMetadata]struct{}, len(setB))
for _, b := range setB {
mapB[b] = struct{}{}
}
if len(mapA) != len(mapB) {
return false
}
for b := range mapB {
if _, exists := mapA[b]; !exists {
return false
}
}
return true
}

View File

@ -0,0 +1,154 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//
package object
import (
"hash/fnv"
"sort"
"strconv"
)
// ObjMetadataSet is an ordered list of ObjMetadata that acts like an unordered
// set for comparison purposes.
type ObjMetadataSet []ObjMetadata
// UnstructuredSetEquals returns true if the slice of objects in setA equals
// the slice of objects in setB.
func ObjMetadataSetEquals(setA []ObjMetadata, setB []ObjMetadata) bool {
return ObjMetadataSet(setA).Equal(ObjMetadataSet(setB))
}
// Equal returns true if the two sets contain equivalent objects. Duplicates are
// ignored.
// This function satisfies the cmp.Equal interface from github.com/google/go-cmp
func (setA ObjMetadataSet) Equal(setB ObjMetadataSet) bool {
mapA := make(map[ObjMetadata]struct{}, len(setA))
for _, a := range setA {
mapA[a] = struct{}{}
}
mapB := make(map[ObjMetadata]struct{}, len(setB))
for _, b := range setB {
mapB[b] = struct{}{}
}
if len(mapA) != len(mapB) {
return false
}
for b := range mapB {
if _, exists := mapA[b]; !exists {
return false
}
}
return true
}
// Contains checks if the provided ObjMetadata exists in the set.
func (setA ObjMetadataSet) Contains(id ObjMetadata) bool {
for _, om := range setA {
if om == id {
return true
}
}
return false
}
// Remove the object from the set and return the updated set.
func (setA ObjMetadataSet) Remove(obj ObjMetadata) ObjMetadataSet {
for i, a := range setA {
if a == obj {
setA[len(setA)-1], setA[i] = setA[i], setA[len(setA)-1]
return setA[:len(setA)-1]
}
}
return setA
}
// Union returns the set of unique objects from the merging of set A and set B.
func (setA ObjMetadataSet) Union(setB ObjMetadataSet) ObjMetadataSet {
m := make(map[ObjMetadata]struct{}, len(setA)+len(setB))
for _, a := range setA {
m[a] = struct{}{}
}
for _, b := range setB {
m[b] = struct{}{}
}
union := make(ObjMetadataSet, 0, len(m))
for u := range m {
union = append(union, u)
}
return union
}
// Diff returns the set of objects that exist in set A, but not in set B (A - B).
func (setA ObjMetadataSet) Diff(setB ObjMetadataSet) ObjMetadataSet {
// Create a map of the elements of A
m := make(map[ObjMetadata]struct{}, len(setA))
for _, a := range setA {
m[a] = struct{}{}
}
// Remove from A each element of B
for _, b := range setB {
delete(m, b) // OK to delete even if b not in m
}
// Create/return slice from the map of remaining items
diff := make(ObjMetadataSet, 0, len(m))
for r := range m {
diff = append(diff, r)
}
return diff
}
// Hash the objects in the set by serializing, sorting, concatonating, and
// hashing the result with the 32-bit FNV-1a algorithm.
func (setA ObjMetadataSet) Hash() (string, error) {
objStrs := make([]string, 0, len(setA))
for _, obj := range setA {
objStrs = append(objStrs, obj.String())
}
hashInt, err := calcHash(objStrs)
if err != nil {
return "", err
}
return strconv.FormatUint(uint64(hashInt), 16), nil
}
// calcHash returns an unsigned int32 representing the hash
// of the obj metadata strings. If there is an error writing bytes to
// the hash, then the error is returned; nil is returned otherwise.
// Used to quickly identify the set of resources in the inventory object.
func calcHash(objs []string) (uint32, error) {
sort.Strings(objs)
h := fnv.New32a()
for _, obj := range objs {
_, err := h.Write([]byte(obj))
if err != nil {
return uint32(0), err
}
}
return h.Sum32(), nil
}
// ToStringMap returns the set as a serializable map, with objMeta keys and
// empty string values.
func (setA ObjMetadataSet) ToStringMap() map[string]string {
stringMap := make(map[string]string, len(setA))
for _, objMeta := range setA {
stringMap[objMeta.String()] = ""
}
return stringMap
}
// FromStringMap returns a set from a serializable map, with objMeta keys and
// empty string values. Errors if parsing fails.
func FromStringMap(in map[string]string) (ObjMetadataSet, error) {
var set ObjMetadataSet
for s := range in {
objMeta, err := ParseObjMetadata(s)
if err != nil {
return nil, err
}
set = append(set, objMeta)
}
return set, nil
}

View File

@ -0,0 +1,226 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package object
import (
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
)
var objMeta1 = ObjMetadata{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "Deployment",
},
Name: "dep",
Namespace: "default",
}
var objMeta2 = ObjMetadata{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "StatefulSet",
},
Name: "dep",
Namespace: "default",
}
var objMeta3 = ObjMetadata{
GroupKind: schema.GroupKind{
Group: "",
Kind: "Pod",
},
Name: "pod-a",
Namespace: "default",
}
var objMeta4 = ObjMetadata{
GroupKind: schema.GroupKind{
Group: "",
Kind: "Pod",
},
Name: "pod-b",
Namespace: "default",
}
func TestObjMetadataSetEquals(t *testing.T) {
testCases := map[string]struct {
setA ObjMetadataSet
setB ObjMetadataSet
isEqual bool
}{
"Empty sets results in empty union": {
setA: ObjMetadataSet{},
setB: ObjMetadataSet{},
isEqual: true,
},
"Empty second set results in same set": {
setA: ObjMetadataSet{objMeta1, objMeta3},
setB: ObjMetadataSet{},
isEqual: false,
},
"Empty initial set results in empty diff": {
setA: ObjMetadataSet{},
setB: ObjMetadataSet{objMeta1, objMeta3},
isEqual: false,
},
"Different ordering are equal sets": {
setA: ObjMetadataSet{objMeta2, objMeta1},
setB: ObjMetadataSet{objMeta1, objMeta2},
isEqual: true,
},
"One item overlap": {
setA: ObjMetadataSet{objMeta2, objMeta1},
setB: ObjMetadataSet{objMeta1, objMeta3},
isEqual: false,
},
"Disjoint sets results in larger set": {
setA: ObjMetadataSet{objMeta1, objMeta2},
setB: ObjMetadataSet{objMeta3, objMeta4},
isEqual: false,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
actual := tc.setA.Equal(tc.setB)
if tc.isEqual != actual {
t.Errorf("SetEqual expected (%t), got (%t)", tc.isEqual, actual)
}
})
}
}
func TestObjMetadataSetUnion(t *testing.T) {
testCases := map[string]struct {
setA ObjMetadataSet
setB ObjMetadataSet
expected ObjMetadataSet
}{
"Empty sets results in empty union": {
setA: ObjMetadataSet{},
setB: ObjMetadataSet{},
expected: ObjMetadataSet{},
},
"Empty second set results in same set": {
setA: ObjMetadataSet{objMeta1, objMeta3},
setB: ObjMetadataSet{},
expected: ObjMetadataSet{objMeta1, objMeta3},
},
"Empty initial set results in empty diff": {
setA: ObjMetadataSet{},
setB: ObjMetadataSet{objMeta1, objMeta3},
expected: ObjMetadataSet{objMeta1, objMeta3},
},
"Same sets in different order results in same set": {
setA: ObjMetadataSet{objMeta2, objMeta1},
setB: ObjMetadataSet{objMeta1, objMeta2},
expected: ObjMetadataSet{objMeta1, objMeta2},
},
"One item overlap": {
setA: ObjMetadataSet{objMeta2, objMeta1},
setB: ObjMetadataSet{objMeta1, objMeta3},
expected: ObjMetadataSet{objMeta1, objMeta2, objMeta3},
},
"Disjoint sets results in larger set": {
setA: ObjMetadataSet{objMeta1, objMeta2},
setB: ObjMetadataSet{objMeta3, objMeta4},
expected: ObjMetadataSet{objMeta1, objMeta2, objMeta3, objMeta4},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
actual := tc.setA.Union(tc.setB)
if !tc.expected.Equal(actual) {
t.Errorf("SetDiff expected set (%s), got (%s)", tc.expected, actual)
}
})
}
}
func TestObjMetadataSetDiff(t *testing.T) {
testCases := map[string]struct {
setA ObjMetadataSet
setB ObjMetadataSet
expected ObjMetadataSet
}{
"Empty sets results in empty diff": {
setA: ObjMetadataSet{},
setB: ObjMetadataSet{},
expected: ObjMetadataSet{},
},
"Empty subtraction set results in same set": {
setA: ObjMetadataSet{objMeta1, objMeta3},
setB: ObjMetadataSet{},
expected: ObjMetadataSet{objMeta1, objMeta3},
},
"Empty initial set results in empty diff": {
setA: ObjMetadataSet{},
setB: ObjMetadataSet{objMeta1, objMeta3},
expected: ObjMetadataSet{},
},
"Sets equal results in empty diff": {
setA: ObjMetadataSet{objMeta2, objMeta1},
setB: ObjMetadataSet{objMeta1, objMeta2},
expected: ObjMetadataSet{},
},
"Basic diff": {
setA: ObjMetadataSet{objMeta2, objMeta1},
setB: ObjMetadataSet{objMeta1},
expected: ObjMetadataSet{objMeta2},
},
"Subtract non-elements results in no change": {
setA: ObjMetadataSet{objMeta1},
setB: ObjMetadataSet{objMeta3, objMeta4},
expected: ObjMetadataSet{objMeta1},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
actual := tc.setA.Diff(tc.setB)
if !tc.expected.Equal(actual) {
t.Errorf("SetDiff expected set (%s), got (%s)", tc.expected, actual)
}
})
}
}
func TestObjMetadataSetHash(t *testing.T) {
tests := map[string]struct {
objs ObjMetadataSet
expected string
}{
"No objects gives valid hash": {
objs: ObjMetadataSet{},
expected: "811c9dc5",
},
"Single object gives valid hash": {
objs: ObjMetadataSet{objMeta1},
expected: "3715cd95",
},
"Multiple objects gives valid hash": {
objs: ObjMetadataSet{objMeta1, objMeta2, objMeta3},
expected: "d69d726a",
},
"Different ordering gives same hash": {
objs: ObjMetadataSet{objMeta2, objMeta3, objMeta1},
expected: "d69d726a",
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
actual, err := tc.objs.Hash()
if err != nil {
t.Fatalf("Received unexpected error: %s", err)
}
if tc.expected != actual {
t.Errorf("expected hash string (%s), got (%s)", tc.expected, actual)
}
})
}
}

View File

@ -78,90 +78,6 @@ func TestCreateObjMetadata(t *testing.T) {
}
}
func TestObjMetadataEquals(t *testing.T) {
testCases := map[string]struct {
objMeta1 *ObjMetadata
objMeta2 *ObjMetadata
expectEquals bool
}{
"parameter is nil": {
objMeta1: &ObjMetadata{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "Deployment",
},
Name: "dep",
Namespace: "default",
},
objMeta2: nil,
expectEquals: false,
},
"different groupKind": {
objMeta1: &ObjMetadata{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "StatefulSet",
},
Name: "dep",
Namespace: "default",
},
objMeta2: &ObjMetadata{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "Deployment",
},
Name: "dep",
Namespace: "default",
},
expectEquals: false,
},
"both are missing groupKind": {
objMeta1: &ObjMetadata{
Name: "dep",
Namespace: "default",
},
objMeta2: &ObjMetadata{
Name: "dep",
Namespace: "default",
},
expectEquals: true,
},
"they are equal": {
objMeta1: &ObjMetadata{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "Deployment",
},
Name: "dep",
Namespace: "default",
},
objMeta2: &ObjMetadata{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "Deployment",
},
Name: "dep",
Namespace: "default",
},
expectEquals: true,
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
equal := tc.objMeta1.Equals(tc.objMeta2)
if tc.expectEquals && !equal {
t.Error("Expected objMetas to be equal, but they weren't")
}
if !tc.expectEquals && equal {
t.Error("Expected objMetas not to be equal, but they were")
}
})
}
}
func TestParseObjMetadata(t *testing.T) {
tests := map[string]struct {
invStr string
@ -272,219 +188,3 @@ func TestParseObjMetadata(t *testing.T) {
})
}
}
var objMeta1 = ObjMetadata{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "Deployment",
},
Name: "dep",
Namespace: "default",
}
var objMeta2 = ObjMetadata{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "StatefulSet",
},
Name: "dep",
Namespace: "default",
}
var objMeta3 = ObjMetadata{
GroupKind: schema.GroupKind{
Group: "",
Kind: "Pod",
},
Name: "pod-a",
Namespace: "default",
}
var objMeta4 = ObjMetadata{
GroupKind: schema.GroupKind{
Group: "",
Kind: "Pod",
},
Name: "pod-b",
Namespace: "default",
}
func TestHash(t *testing.T) {
tests := map[string]struct {
objs []ObjMetadata
expected string
}{
"No objects gives valid hash": {
objs: []ObjMetadata{},
expected: "811c9dc5",
},
"Single object gives valid hash": {
objs: []ObjMetadata{objMeta1},
expected: "3715cd95",
},
"Multiple objects gives valid hash": {
objs: []ObjMetadata{objMeta1, objMeta2, objMeta3},
expected: "d69d726a",
},
"Different ordering gives same hash": {
objs: []ObjMetadata{objMeta2, objMeta3, objMeta1},
expected: "d69d726a",
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
actual, err := Hash(tc.objs)
if err != nil {
t.Fatalf("Received unexpected error: %s", err)
}
if tc.expected != actual {
t.Errorf("expected hash string (%s), got (%s)", tc.expected, actual)
}
})
}
}
func TestSetDiff(t *testing.T) {
testCases := map[string]struct {
setA []ObjMetadata
setB []ObjMetadata
expected []ObjMetadata
}{
"Empty sets results in empty diff": {
setA: []ObjMetadata{},
setB: []ObjMetadata{},
expected: []ObjMetadata{},
},
"Empty subtraction set results in same set": {
setA: []ObjMetadata{objMeta1, objMeta3},
setB: []ObjMetadata{},
expected: []ObjMetadata{objMeta1, objMeta3},
},
"Empty initial set results in empty diff": {
setA: []ObjMetadata{},
setB: []ObjMetadata{objMeta1, objMeta3},
expected: []ObjMetadata{},
},
"Sets equal results in empty diff": {
setA: []ObjMetadata{objMeta2, objMeta1},
setB: []ObjMetadata{objMeta1, objMeta2},
expected: []ObjMetadata{},
},
"Basic diff": {
setA: []ObjMetadata{objMeta2, objMeta1},
setB: []ObjMetadata{objMeta1},
expected: []ObjMetadata{objMeta2},
},
"Subtract non-elements results in no change": {
setA: []ObjMetadata{objMeta1},
setB: []ObjMetadata{objMeta3, objMeta4},
expected: []ObjMetadata{objMeta1},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
actual := SetDiff(tc.setA, tc.setB)
if !SetEquals(tc.expected, actual) {
t.Errorf("SetDiff expected set (%s), got (%s)", tc.expected, actual)
}
})
}
}
func TestUnion(t *testing.T) {
testCases := map[string]struct {
setA []ObjMetadata
setB []ObjMetadata
expected []ObjMetadata
}{
"Empty sets results in empty union": {
setA: []ObjMetadata{},
setB: []ObjMetadata{},
expected: []ObjMetadata{},
},
"Empty second set results in same set": {
setA: []ObjMetadata{objMeta1, objMeta3},
setB: []ObjMetadata{},
expected: []ObjMetadata{objMeta1, objMeta3},
},
"Empty initial set results in empty diff": {
setA: []ObjMetadata{},
setB: []ObjMetadata{objMeta1, objMeta3},
expected: []ObjMetadata{objMeta1, objMeta3},
},
"Same sets in different order results in same set": {
setA: []ObjMetadata{objMeta2, objMeta1},
setB: []ObjMetadata{objMeta1, objMeta2},
expected: []ObjMetadata{objMeta1, objMeta2},
},
"One item overlap": {
setA: []ObjMetadata{objMeta2, objMeta1},
setB: []ObjMetadata{objMeta1, objMeta3},
expected: []ObjMetadata{objMeta1, objMeta2, objMeta3},
},
"Disjoint sets results in larger set": {
setA: []ObjMetadata{objMeta1, objMeta2},
setB: []ObjMetadata{objMeta3, objMeta4},
expected: []ObjMetadata{objMeta1, objMeta2, objMeta3, objMeta4},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
actual := Union(tc.setA, tc.setB)
if !SetEquals(tc.expected, actual) {
t.Errorf("SetDiff expected set (%s), got (%s)", tc.expected, actual)
}
})
}
}
func TestSetEquals(t *testing.T) {
testCases := map[string]struct {
setA []ObjMetadata
setB []ObjMetadata
isEqual bool
}{
"Empty sets results in empty union": {
setA: []ObjMetadata{},
setB: []ObjMetadata{},
isEqual: true,
},
"Empty second set results in same set": {
setA: []ObjMetadata{objMeta1, objMeta3},
setB: []ObjMetadata{},
isEqual: false,
},
"Empty initial set results in empty diff": {
setA: []ObjMetadata{},
setB: []ObjMetadata{objMeta1, objMeta3},
isEqual: false,
},
"Different ordering are equal sets": {
setA: []ObjMetadata{objMeta2, objMeta1},
setB: []ObjMetadata{objMeta1, objMeta2},
isEqual: true,
},
"One item overlap": {
setA: []ObjMetadata{objMeta2, objMeta1},
setB: []ObjMetadata{objMeta1, objMeta3},
isEqual: false,
},
"Disjoint sets results in larger set": {
setA: []ObjMetadata{objMeta1, objMeta2},
setB: []ObjMetadata{objMeta3, objMeta4},
isEqual: false,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
actual := SetEquals(tc.setA, tc.setB)
if tc.isEqual != actual {
t.Errorf("SetEqual expected (%t), got (%t)", tc.isEqual, actual)
}
})
}
}

View File

@ -128,8 +128,8 @@ func (i InventoryCustomType) ID() string {
return id
}
func (i InventoryCustomType) Load() ([]object.ObjMetadata, error) {
var inv []object.ObjMetadata
func (i InventoryCustomType) Load() (object.ObjMetadataSet, error) {
var inv object.ObjMetadataSet
s, found, err := unstructured.NestedSlice(i.inv.Object, "spec", "inventory")
if err != nil {
return inv, err
@ -155,7 +155,7 @@ func (i InventoryCustomType) Load() ([]object.ObjMetadata, error) {
return inv, nil
}
func (i InventoryCustomType) Store(objs []object.ObjMetadata) error {
func (i InventoryCustomType) Store(objs object.ObjMetadataSet) error {
var inv []interface{}
for _, obj := range objs {
inv = append(inv, map[string]interface{}{