diff --git a/cmd/kubectl/kubectlcobra/inventory.go b/cmd/kubectl/kubectlcobra/inventory.go index 09e367bfef..336bbb5e47 100644 --- a/cmd/kubectl/kubectlcobra/inventory.go +++ b/cmd/kubectl/kubectlcobra/inventory.go @@ -6,6 +6,7 @@ package kubectlcobra import ( "fmt" + "sort" "strings" "k8s.io/apimachinery/pkg/runtime/schema" @@ -81,3 +82,99 @@ func (i *Inventory) String() string { i.GroupKind.Group, fieldSeparator, i.GroupKind.Kind) } + +// InventorySet encapsulates a grouping of unique Inventory +// structs. Organizes the Inventory structs with a map, +// which ensures there are no duplicates. Allows set +// operations such as merging sets and subtracting sets. +type InventorySet struct { + set map[string]*Inventory +} + +// NewInventorySet returns a pointer to an InventorySet +// struct grouping the passed Inventory items. +func NewInventorySet(items []*Inventory) *InventorySet { + invSet := InventorySet{set: map[string]*Inventory{}} + invSet.AddItems(items) + return &invSet +} + +// GetItems returns the set of pointers to Inventory +// structs. +func (is *InventorySet) GetItems() []*Inventory { + items := []*Inventory{} + for _, item := range is.set { + items = append(items, item) + } + return items +} + +// AddItems adds Inventory structs to the set which +// are not already in the set. +func (is *InventorySet) AddItems(items []*Inventory) { + for _, item := range items { + if item != nil { + is.set[item.String()] = item + } + } +} + +// DeleteItem removes an Inventory struct from the +// set if it exists in the set. Returns true if the +// Inventory item was deleted, false if it did not exist +// in the set. +func (is *InventorySet) DeleteItem(item *Inventory) bool { + if item == nil { + return false + } + if _, ok := is.set[item.String()]; ok { + delete(is.set, item.String()) + return true + } + return false +} + +// Merge combines the unique set of Inventory items from the +// current set with the passed "other" set, returning a new +// set or error. Returns an error if the passed set to merge +// is nil. +func (is *InventorySet) Merge(other *InventorySet) (*InventorySet, error) { + if other == nil { + return nil, fmt.Errorf("InventorySet to merge is nil.") + } + // Copy the current InventorySet into result + result := NewInventorySet(is.GetItems()) + result.AddItems(other.GetItems()) + return result, nil +} + +// Subtract removes the Inventory items in the "other" set from the +// current set, returning a new set. This does not modify the current +// set. Returns an error if the passed set to subtract is nil. +func (is *InventorySet) Subtract(other *InventorySet) (*InventorySet, error) { + if other == nil { + return nil, fmt.Errorf("InventorySet to subtract is nil.") + } + // Copy the current InventorySet into result + result := NewInventorySet(is.GetItems()) + // Remove each item in "other" which exists in "result" + for _, item := range other.GetItems() { + result.DeleteItem(item) + } + return result, nil +} + +// String returns a string describing set of Inventory structs. +func (is *InventorySet) String() string { + strs := []string{} + for _, item := range is.GetItems() { + strs = append(strs, item.String()) + } + sort.Strings(strs) + return strings.Join(strs, ", ") +} + +// Size returns the number of Inventory structs in the set. +func (is *InventorySet) Size() int { + return len(is.set) +} diff --git a/cmd/kubectl/kubectlcobra/inventory_test.go b/cmd/kubectl/kubectlcobra/inventory_test.go index 2f6aaf080b..93fd9a0e87 100644 --- a/cmd/kubectl/kubectlcobra/inventory_test.go +++ b/cmd/kubectl/kubectlcobra/inventory_test.go @@ -216,3 +216,274 @@ func TestParseInventory(t *testing.T) { } } } + +var inventory1 = Inventory{ + Namespace: "test-namespace", + Name: "test-inv-1", + GroupKind: schema.GroupKind{ + Group: "apps", + Kind: "Deployment", + }, +} + +var inventory2 = Inventory{ + Namespace: "test-namespace", + Name: "test-inv-2", + GroupKind: schema.GroupKind{ + Group: "", + Kind: "Pod", + }, +} + +var inventory3 = Inventory{ + Namespace: "test-namespace", + Name: "test-inv-3", + GroupKind: schema.GroupKind{ + Group: "", + Kind: "Service", + }, +} + +var inventory4 = Inventory{ + Namespace: "test-namespace", + Name: "test-inv-4", + GroupKind: schema.GroupKind{ + Group: "apps", + Kind: "DaemonSet", + }, +} + +func TestNewInventorySet(t *testing.T) { + tests := []struct { + items []*Inventory + expectedStr string + expectedSize int + }{ + { + items: []*Inventory{}, + expectedStr: "", + expectedSize: 0, + }, + { + items: []*Inventory{&inventory1}, + expectedStr: "test-namespace_test-inv-1_apps_Deployment", + expectedSize: 1, + }, + { + items: []*Inventory{&inventory1, &inventory2}, + expectedStr: "test-namespace_test-inv-1_apps_Deployment, test-namespace_test-inv-2__Pod", + expectedSize: 2, + }, + } + + for _, test := range tests { + invSet := NewInventorySet(test.items) + actualStr := invSet.String() + actualSize := invSet.Size() + if test.expectedStr != actualStr { + t.Errorf("Expected InventorySet (%s), got (%s)\n", test.expectedStr, actualStr) + } + if test.expectedSize != actualSize { + t.Errorf("Expected InventorySet size (%d), got (%d)\n", test.expectedSize, actualSize) + } + actualItems := invSet.GetItems() + if len(test.items) != len(actualItems) { + t.Errorf("Expected num inventory items (%d), got (%d)\n", len(test.items), len(actualItems)) + } + } +} + +func TestInventorySetAddItems(t *testing.T) { + tests := []struct { + initialItems []*Inventory + addItems []*Inventory + expectedItems []*Inventory + }{ + // Adding no items to empty inventory set. + { + initialItems: []*Inventory{}, + addItems: []*Inventory{}, + expectedItems: []*Inventory{}, + }, + // Adding item to empty inventory set. + { + initialItems: []*Inventory{}, + addItems: []*Inventory{&inventory1}, + expectedItems: []*Inventory{&inventory1}, + }, + // Adding no items does not change the inventory set + { + initialItems: []*Inventory{&inventory1}, + addItems: []*Inventory{}, + expectedItems: []*Inventory{&inventory1}, + }, + // Adding an item which alread exists does not increase size. + { + initialItems: []*Inventory{&inventory1, &inventory2}, + addItems: []*Inventory{&inventory1}, + expectedItems: []*Inventory{&inventory1, &inventory2}, + }, + { + initialItems: []*Inventory{&inventory1, &inventory2}, + addItems: []*Inventory{&inventory3, &inventory4}, + expectedItems: []*Inventory{&inventory1, &inventory2, &inventory3, &inventory4}, + }, + } + + for _, test := range tests { + invSet := NewInventorySet(test.initialItems) + invSet.AddItems(test.addItems) + if len(test.expectedItems) != invSet.Size() { + t.Errorf("Expected num inventory items (%d), got (%d)\n", len(test.expectedItems), invSet.Size()) + } + } +} + +func TestInventorySetDeleteItem(t *testing.T) { + tests := []struct { + initialItems []*Inventory + deleteItem *Inventory + expected bool + expectedItems []*Inventory + }{ + { + initialItems: []*Inventory{}, + deleteItem: nil, + expected: false, + expectedItems: []*Inventory{}, + }, + { + initialItems: []*Inventory{}, + deleteItem: &inventory1, + expected: false, + expectedItems: []*Inventory{}, + }, + { + initialItems: []*Inventory{&inventory2}, + deleteItem: &inventory1, + expected: false, + expectedItems: []*Inventory{&inventory2}, + }, + { + initialItems: []*Inventory{&inventory1}, + deleteItem: &inventory1, + expected: true, + expectedItems: []*Inventory{}, + }, + { + initialItems: []*Inventory{&inventory1, &inventory2}, + deleteItem: &inventory1, + expected: true, + expectedItems: []*Inventory{&inventory2}, + }, + } + + for _, test := range tests { + invSet := NewInventorySet(test.initialItems) + actual := invSet.DeleteItem(test.deleteItem) + if test.expected != actual { + t.Errorf("Expected return value (%t), got (%t)\n", test.expected, actual) + } + if len(test.expectedItems) != invSet.Size() { + t.Errorf("Expected num inventory items (%d), got (%d)\n", len(test.expectedItems), invSet.Size()) + } + } +} + +func TestInventorySetMerge(t *testing.T) { + tests := []struct { + set1 []*Inventory + set2 []*Inventory + merged []*Inventory + }{ + { + set1: []*Inventory{}, + set2: []*Inventory{}, + merged: []*Inventory{}, + }, + { + set1: []*Inventory{}, + set2: []*Inventory{&inventory1}, + merged: []*Inventory{&inventory1}, + }, + { + set1: []*Inventory{&inventory1}, + set2: []*Inventory{}, + merged: []*Inventory{&inventory1}, + }, + { + set1: []*Inventory{&inventory1, &inventory2}, + set2: []*Inventory{&inventory1}, + merged: []*Inventory{&inventory1, &inventory2}, + }, + { + set1: []*Inventory{&inventory1, &inventory2}, + set2: []*Inventory{&inventory1, &inventory2}, + merged: []*Inventory{&inventory1, &inventory2}, + }, + { + set1: []*Inventory{&inventory1, &inventory2}, + set2: []*Inventory{&inventory3, &inventory4}, + merged: []*Inventory{&inventory1, &inventory2, &inventory3, &inventory4}, + }, + } + + for _, test := range tests { + invSet1 := NewInventorySet(test.set1) + invSet2 := NewInventorySet(test.set2) + expected := NewInventorySet(test.merged) + merged, _ := invSet1.Merge(invSet2) + if expected.Size() != merged.Size() { + t.Errorf("Expected merged inventory set size (%d), got (%d)\n", expected.Size(), merged.Size()) + } + } +} + +func TestInventorySetSubtract(t *testing.T) { + tests := []struct { + initialItems []*Inventory + subtractItems []*Inventory + expected []*Inventory + }{ + { + initialItems: []*Inventory{}, + subtractItems: []*Inventory{}, + expected: []*Inventory{}, + }, + { + initialItems: []*Inventory{}, + subtractItems: []*Inventory{&inventory1}, + expected: []*Inventory{}, + }, + { + initialItems: []*Inventory{&inventory1}, + subtractItems: []*Inventory{}, + expected: []*Inventory{&inventory1}, + }, + { + initialItems: []*Inventory{&inventory1, &inventory2}, + subtractItems: []*Inventory{&inventory1}, + expected: []*Inventory{&inventory2}, + }, + { + initialItems: []*Inventory{&inventory1, &inventory2}, + subtractItems: []*Inventory{&inventory1, &inventory2}, + expected: []*Inventory{}, + }, + { + initialItems: []*Inventory{&inventory1, &inventory2}, + subtractItems: []*Inventory{&inventory3, &inventory4}, + expected: []*Inventory{&inventory1, &inventory2}, + }, + } + + for _, test := range tests { + invInitialItems := NewInventorySet(test.initialItems) + invSubtractItems := NewInventorySet(test.subtractItems) + expected := NewInventorySet(test.expected) + actual, _ := invInitialItems.Subtract(invSubtractItems) + if expected.Size() != actual.Size() { + t.Errorf("Expected subtracted inventory set size (%d), got (%d)\n", expected.Size(), actual.Size()) + } + } +}