From abc57e481be171cf511cfc31ce57f58f72a1b776 Mon Sep 17 00:00:00 2001 From: Sean Sullivan Date: Tue, 7 Jan 2020 15:23:09 -0800 Subject: [PATCH] Adds helper functions for apply grouping objects --- cmd/kubectl/go.mod | 2 + cmd/kubectl/kubectlcobra/grouping.go | 59 ++++++ cmd/kubectl/kubectlcobra/grouping_test.go | 217 ++++++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 cmd/kubectl/kubectlcobra/grouping.go create mode 100644 cmd/kubectl/kubectlcobra/grouping_test.go diff --git a/cmd/kubectl/go.mod b/cmd/kubectl/go.mod index 259bd5a59..aee17a388 100644 --- a/cmd/kubectl/go.mod +++ b/cmd/kubectl/go.mod @@ -4,6 +4,8 @@ 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 k8s.io/component-base v0.17.0 // indirect diff --git a/cmd/kubectl/kubectlcobra/grouping.go b/cmd/kubectl/kubectlcobra/grouping.go new file mode 100644 index 000000000..568682093 --- /dev/null +++ b/cmd/kubectl/kubectlcobra/grouping.go @@ -0,0 +1,59 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +// package kubectlcobra contains cobra commands from kubectl +package kubectlcobra + +import ( + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/resource" +) + +const GroupingLabel = "kustomize.k8s.io/group-id" + +// isGroupingObject returns true if the passed object has the +// grouping label. +// TODO(seans3): Check type is ConfigMap. +func isGroupingObject(obj runtime.Object) bool { + if obj == nil { + return false + } + accessor, err := meta.Accessor(obj) + if err == nil { + labels := accessor.GetLabels() + _, exists := labels[GroupingLabel] + if exists { + return true + } + } + return false +} + +// findGroupingObject returns the "Grouping" object (ConfigMap with +// grouping label) if it exists, and a boolean describing if it was found. +func findGroupingObject(infos []*resource.Info) (*resource.Info, bool) { + for _, info := range infos { + if info != nil && isGroupingObject(info.Object) { + return info, true + } + } + return nil, false +} + +// sortGroupingObject reorders the infos slice to place the grouping +// object in the first position. Returns true if grouping object found, +// false otherwise. +func sortGroupingObject(infos []*resource.Info) bool { + for i, info := range infos { + if info != nil && isGroupingObject(info.Object) { + // If the grouping object is not already in the first position, + // swap the grouping object with the first object. + if i > 0 { + infos[0], infos[i] = infos[i], infos[0] + } + return true + } + } + return false +} diff --git a/cmd/kubectl/kubectlcobra/grouping_test.go b/cmd/kubectl/kubectlcobra/grouping_test.go new file mode 100644 index 000000000..12911cf52 --- /dev/null +++ b/cmd/kubectl/kubectlcobra/grouping_test.go @@ -0,0 +1,217 @@ +// Copyright 2020 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +// package kubectlcobra contains cobra commands from kubectl +package kubectlcobra + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/resource" +) + +var testNamespace = "test-grouping-namespace" +var groupingObjName = "test-grouping-obj" +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 groupingInfo = &resource.Info{ + Namespace: testNamespace, + Name: groupingObjName, + Object: &groupingObj, +} + +var pod1 = corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: pod1Name, + }, +} + +var pod1Info = &resource.Info{ + Namespace: testNamespace, + Name: pod1Name, + Object: &pod1, +} + +var pod2 = corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: pod2Name, + }, +} + +var pod2Info = &resource.Info{ + Namespace: testNamespace, + Name: pod2Name, + Object: &pod2, +} + +var pod3 = corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: pod3Name, + }, +} + +var pod3Info = &resource.Info{ + Namespace: testNamespace, + Name: pod3Name, + Object: &pod3, +} + +func TestIsGroupingObject(t *testing.T) { + tests := []struct { + obj runtime.Object + isGrouping bool + }{ + { + obj: nil, + isGrouping: false, + }, + { + obj: &groupingObj, + isGrouping: true, + }, + { + obj: &pod2, + isGrouping: false, + }, + } + + for _, test := range tests { + grouping := isGroupingObject(test.obj) + if test.isGrouping && !grouping { + t.Errorf("Grouping object not identified: %#v", test.obj) + } + if !test.isGrouping && grouping { + t.Errorf("Non-grouping object identifed as grouping obj: %#v", test.obj) + } + } +} + +func TestFindGroupingObject(t *testing.T) { + tests := []struct { + infos []*resource.Info + exists bool + name string + }{ + { + infos: []*resource.Info{}, + exists: false, + name: "", + }, + { + infos: []*resource.Info{nil}, + exists: false, + name: "", + }, + { + infos: []*resource.Info{groupingInfo}, + exists: true, + name: groupingObjName, + }, + { + infos: []*resource.Info{pod1Info}, + exists: false, + name: "", + }, + { + infos: []*resource.Info{pod1Info, pod2Info, pod3Info}, + exists: false, + name: "", + }, + { + infos: []*resource.Info{pod1Info, pod2Info, groupingInfo, pod3Info}, + exists: true, + name: groupingObjName, + }, + } + + for _, test := range tests { + groupingObj, found := findGroupingObject(test.infos) + if test.exists && !found { + t.Errorf("Should have found grouping object") + } + if !test.exists && found { + t.Errorf("Grouping object found, but it does not exist: %#v", groupingObj) + } + if test.exists && found && test.name != groupingObj.Name { + t.Errorf("Grouping object name does not match: %s/%s", test.name, groupingObj.Name) + } + } +} + +func TestSortGroupingObject(t *testing.T) { + tests := []struct { + infos []*resource.Info + sorted bool + }{ + { + infos: []*resource.Info{}, + sorted: false, + }, + { + infos: []*resource.Info{groupingInfo}, + sorted: true, + }, + { + infos: []*resource.Info{pod1Info}, + sorted: false, + }, + { + infos: []*resource.Info{pod1Info, pod2Info}, + sorted: false, + }, + { + infos: []*resource.Info{groupingInfo, pod1Info}, + sorted: true, + }, + { + infos: []*resource.Info{pod1Info, groupingInfo}, + sorted: true, + }, + { + infos: []*resource.Info{pod1Info, pod2Info, groupingInfo, pod3Info}, + sorted: true, + }, + { + infos: []*resource.Info{pod1Info, pod2Info, pod3Info, groupingInfo}, + sorted: true, + }, + { + infos: []*resource.Info{groupingInfo, pod1Info, pod2Info, pod3Info}, + sorted: true, + }, + } + + for _, test := range tests { + wasSorted := sortGroupingObject(test.infos) + if wasSorted && !test.sorted { + t.Errorf("Grouping object was sorted, but it shouldn't have been") + } + if !wasSorted && test.sorted { + t.Errorf("Grouping object was NOT sorted, but it should have been") + } + if wasSorted { + first := test.infos[0] + if !isGroupingObject(first.Object) { + t.Errorf("Grouping object was not sorted into first position") + } + } + } +}