Adds functions to add/retrieve inventory to/from grouping object

This commit is contained in:
Sean Sullivan
2020-01-09 16:01:07 -08:00
parent 488bc5aceb
commit 595e41a3ec
3 changed files with 309 additions and 21 deletions

View File

@@ -4,7 +4,6 @@ go 1.13
require (
github.com/spf13/cobra v0.0.5
k8s.io/api v0.17.0
k8s.io/apimachinery v0.17.0
k8s.io/cli-runtime v0.17.0
k8s.io/client-go v0.17.0

View File

@@ -5,7 +5,10 @@
package kubectlcobra
import (
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/resource"
)
@@ -57,3 +60,86 @@ func sortGroupingObject(infos []*resource.Info) bool {
}
return false
}
// Adds the inventory of all objects (passed as infos) to the
// grouping object. Returns an error if a grouping object does not
// exist, or we are unable to successfully add the inventory to
// the grouping object; nil otherwise. Each object is in
// unstructured.Unstructured format.
func addInventoryToGroupingObj(infos []*resource.Info) error {
// Iterate through the objects (infos), creating an Inventory struct
// as metadata for the object, or if it's the grouping object, store it.
var groupingObj *unstructured.Unstructured
inventoryMap := map[string]string{}
for _, info := range infos {
obj := info.Object
if isGroupingObject(obj) {
// If we have more than one grouping object--error.
if groupingObj != nil {
return fmt.Errorf("Error--applying more than one grouping object.")
}
var ok bool
groupingObj, ok = obj.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("Grouping object is not an Unstructured: %#v", groupingObj)
}
} else {
if obj == nil {
return fmt.Errorf("Creating inventory; object is nil")
}
gk := obj.GetObjectKind().GroupVersionKind().GroupKind()
inventory, err := createInventory(info.Namespace, info.Name, gk)
if err != nil {
return err
}
inventoryMap[inventory.String()] = ""
}
}
// If we've found the grouping object, store the object metadata inventory
// in the grouping config map.
if groupingObj == nil {
return fmt.Errorf("Grouping object not found")
}
err := unstructured.SetNestedStringMap(groupingObj.UnstructuredContent(), inventoryMap, "data")
if err != nil {
return err
}
return nil
}
// retrieveInventoryFromGroupingObj returns a slice of pointers to the
// inventory metadata. This function finds the grouping object, then
// parses the stored resource metadata into Inventory structs. Returns
// an error if there is a problem parsing the data into Inventory
// structs, or if the grouping object is not in Unstructured format; nil
// otherwise. If a grouping object does not exist, or it does not have a
// "data" map, then returns an empty slice and no error.
func retrieveInventoryFromGroupingObj(infos []*resource.Info) ([]*Inventory, error) {
inventory := []*Inventory{}
groupingInfo, exists := findGroupingObject(infos)
if exists {
groupingObj, ok := groupingInfo.Object.(*unstructured.Unstructured)
if !ok {
err := fmt.Errorf("Grouping object is not an Unstructured: %#v", groupingObj)
return inventory, err
}
invMap, exists, err := unstructured.NestedStringMap(groupingObj.Object, "data")
if err != nil {
err := fmt.Errorf("Error retrieving inventory from grouping object.")
return inventory, err
}
if exists {
for invStr := range invMap {
inv, err := parseInventory(invStr)
if err != nil {
return inventory, err
}
inventory = append(inventory, inv)
}
}
}
return inventory, nil
}

View File

@@ -7,9 +7,9 @@ package kubectlcobra
import (
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/resource"
)
@@ -19,12 +19,16 @@ var pod1Name = "pod-1"
var pod2Name = "pod-2"
var pod3Name = "pod-3"
var groupingObj = corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespace,
Name: groupingObjName,
Labels: map[string]string{
GroupingLabel: "true",
var groupingObj = unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": groupingObjName,
"namespace": testNamespace,
"labels": map[string]interface{}{
GroupingLabel: "true",
},
},
},
}
@@ -35,10 +39,14 @@ var groupingInfo = &resource.Info{
Object: &groupingObj,
}
var pod1 = corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespace,
Name: pod1Name,
var pod1 = unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Pod",
"metadata": map[string]interface{}{
"name": pod1Name,
"namespace": testNamespace,
},
},
}
@@ -48,10 +56,14 @@ var pod1Info = &resource.Info{
Object: &pod1,
}
var pod2 = corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespace,
Name: pod2Name,
var pod2 = unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Pod",
"metadata": map[string]interface{}{
"name": pod2Name,
"namespace": testNamespace,
},
},
}
@@ -61,10 +73,14 @@ var pod2Info = &resource.Info{
Object: &pod2,
}
var pod3 = corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespace,
Name: pod3Name,
var pod3 = unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Pod",
"metadata": map[string]interface{}{
"name": pod3Name,
"namespace": testNamespace,
},
},
}
@@ -215,3 +231,190 @@ func TestSortGroupingObject(t *testing.T) {
}
}
}
func TestAddRetrieveInventoryToFromGroupingObject(t *testing.T) {
tests := []struct {
infos []*resource.Info
expected []*Inventory
isError bool
}{
// No grouping object is an error.
{
infos: []*resource.Info{},
isError: true,
},
// No grouping object is an error.
{
infos: []*resource.Info{pod1Info, pod2Info},
isError: true,
},
// Grouping object without other objects is OK.
{
infos: []*resource.Info{groupingInfo},
expected: []*Inventory{},
isError: false,
},
// More than one grouping object is an error.
{
infos: []*resource.Info{groupingInfo, groupingInfo},
expected: []*Inventory{},
isError: true,
},
// More than one grouping object is an error.
{
infos: []*resource.Info{groupingInfo, pod1Info, groupingInfo},
expected: []*Inventory{},
isError: true,
},
{
infos: []*resource.Info{groupingInfo, pod1Info},
expected: []*Inventory{
&Inventory{
Namespace: testNamespace,
Name: pod1Name,
GroupKind: schema.GroupKind{
Group: "",
Kind: "Pod",
},
},
},
isError: false,
},
{
infos: []*resource.Info{pod1Info, groupingInfo},
expected: []*Inventory{
&Inventory{
Namespace: testNamespace,
Name: pod1Name,
GroupKind: schema.GroupKind{
Group: "",
Kind: "Pod",
},
},
},
isError: false,
},
{
infos: []*resource.Info{pod1Info, pod2Info, groupingInfo, pod3Info},
expected: []*Inventory{
&Inventory{
Namespace: testNamespace,
Name: pod1Name,
GroupKind: schema.GroupKind{
Group: "",
Kind: "Pod",
},
},
&Inventory{
Namespace: testNamespace,
Name: pod2Name,
GroupKind: schema.GroupKind{
Group: "",
Kind: "Pod",
},
},
&Inventory{
Namespace: testNamespace,
Name: pod3Name,
GroupKind: schema.GroupKind{
Group: "",
Kind: "Pod",
},
},
},
isError: false,
},
{
infos: []*resource.Info{pod1Info, pod2Info, pod3Info, groupingInfo},
expected: []*Inventory{
&Inventory{
Namespace: testNamespace,
Name: pod1Name,
GroupKind: schema.GroupKind{
Group: "",
Kind: "Pod",
},
},
&Inventory{
Namespace: testNamespace,
Name: pod2Name,
GroupKind: schema.GroupKind{
Group: "",
Kind: "Pod",
},
},
&Inventory{
Namespace: testNamespace,
Name: pod3Name,
GroupKind: schema.GroupKind{
Group: "",
Kind: "Pod",
},
},
},
isError: false,
},
{
infos: []*resource.Info{groupingInfo, pod1Info, pod2Info, pod3Info},
expected: []*Inventory{
&Inventory{
Namespace: testNamespace,
Name: pod1Name,
GroupKind: schema.GroupKind{
Group: "",
Kind: "Pod",
},
},
&Inventory{
Namespace: testNamespace,
Name: pod2Name,
GroupKind: schema.GroupKind{
Group: "",
Kind: "Pod",
},
},
&Inventory{
Namespace: testNamespace,
Name: pod3Name,
GroupKind: schema.GroupKind{
Group: "",
Kind: "Pod",
},
},
},
isError: false,
},
}
for _, test := range tests {
err := addInventoryToGroupingObj(test.infos)
if test.isError && err == nil {
t.Errorf("Should have produced an error, but returned none.")
}
if !test.isError {
if err != nil {
t.Errorf("Received error when expecting none (%s)\n", err)
} else {
retrieved, err := retrieveInventoryFromGroupingObj(test.infos)
if err != nil {
t.Errorf("Error retrieving inventory: %s\n", err)
}
if len(test.expected) != len(retrieved) {
t.Errorf("Expected inventory for %d resources, actual %d",
len(test.expected), len(retrieved))
}
for _, expected := range test.expected {
found := false
for _, actual := range retrieved {
if expected.Equals(actual) {
found = true
}
}
if !found {
t.Errorf("Expected inventory (%s) not found", expected)
}
}
}
}
}
}