diff --git a/pkg/kube/object.go b/pkg/kube/object.go index 9486d0646..a47371549 100644 --- a/pkg/kube/object.go +++ b/pkg/kube/object.go @@ -82,6 +82,12 @@ func IsClusterScopedKind(k string) bool { } } +// PartialObjectToLabels encodes the specified Object as a set of labels. +// +// If Object's name cannot be used as the value of the +// starboard.LabelResourceName label, as a fallback, this method will calculate +// a hash of the Object's name and use it as the value of the +// starboard.LabelResourceNameHash label. func PartialObjectToLabels(obj Object) map[string]string { labels := map[string]string{ starboard.LabelResourceKind: string(obj.Kind), @@ -95,6 +101,8 @@ func PartialObjectToLabels(obj Object) map[string]string { return labels } +// ObjectToObjectMetadata encodes the specified client.Object as a set of labels +// and annotations added to the given ObjectMeta. func ObjectToObjectMetadata(obj client.Object, meta *metav1.ObjectMeta) error { if meta.Labels == nil { meta.Labels = make(map[string]string) diff --git a/pkg/kube/object_test.go b/pkg/kube/object_test.go index 2d76f7979..8877ac3c5 100644 --- a/pkg/kube/object_test.go +++ b/pkg/kube/object_test.go @@ -59,6 +59,177 @@ func TestIsBuiltInWorkload(t *testing.T) { } } +func TestIsClusterScopedKind(t *testing.T) { + testCases := []struct { + kind string + want bool + }{ + { + kind: "Role", + want: false, + }, + { + kind: "RoleBinding", + want: false, + }, + { + kind: "ClusterRole", + want: true, + }, + { + kind: "ClusterRoleBinding", + want: true, + }, + { + kind: "CustomResourceDefinition", + want: true, + }, + { + kind: "Pod", + want: false, + }, + } + for _, tt := range testCases { + t.Run(fmt.Sprintf("Should return %t when controller kind is %s", tt.want, tt.kind), func(t *testing.T) { + assert.Equal(t, tt.want, kube.IsClusterScopedKind(tt.kind)) + }) + } +} + +func TestPartialObjectToLabels(t *testing.T) { + testCases := []struct { + name string + object kube.Object + labels map[string]string + }{ + { + name: "Should map object with simple name", + object: kube.Object{ + Kind: kube.KindPod, + Name: "my-pod", + Namespace: "production", + }, + labels: map[string]string{ + starboard.LabelResourceKind: "Pod", + starboard.LabelResourceNamespace: "production", + starboard.LabelResourceName: "my-pod", + }, + }, + { + name: "Should map object with name that is not a valid label", + object: kube.Object{ + Kind: kube.KindClusterRole, + Name: "system:controller:namespace-controller", + }, + labels: map[string]string{ + starboard.LabelResourceKind: "ClusterRole", + starboard.LabelResourceNameHash: kube.ComputeHash("system:controller:namespace-controller"), + starboard.LabelResourceNamespace: "", + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.labels, kube.PartialObjectToLabels(tc.object)) + }) + } +} + +func TestObjectToObjectMetadata(t *testing.T) { + testCases := []struct { + name string + meta metav1.ObjectMeta + object client.Object + expected metav1.ObjectMeta + }{ + { + name: "Should map object with simple name", + meta: metav1.ObjectMeta{}, + object: &corev1.Pod{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-pod", + Namespace: "production", + }, + }, + expected: metav1.ObjectMeta{ + Labels: map[string]string{ + starboard.LabelResourceKind: "Pod", + starboard.LabelResourceName: "my-pod", + starboard.LabelResourceNamespace: "production", + }, + }, + }, + { + name: "Should map object with name that is not a valid label", + meta: metav1.ObjectMeta{}, + object: &corev1.Pod{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "rbac.authorization.k8s.io/v1", + Kind: "ClusterRole", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "system:controller:node-controller", + }, + }, + expected: metav1.ObjectMeta{ + Labels: map[string]string{ + starboard.LabelResourceKind: "ClusterRole", + starboard.LabelResourceNameHash: kube.ComputeHash("system:controller:node-controller"), + starboard.LabelResourceNamespace: "", + }, + Annotations: map[string]string{ + starboard.LabelResourceName: "system:controller:node-controller", + }, + }, + }, + { + name: "Should map object and merge labels and annotations", + meta: metav1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + Annotations: map[string]string{ + "kee": "pass", + }, + }, + object: &corev1.Pod{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "rbac.authorization.k8s.io/v1", + Kind: "ClusterRole", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "system:controller:node-controller", + }, + }, + expected: metav1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + + starboard.LabelResourceKind: "ClusterRole", + starboard.LabelResourceNameHash: kube.ComputeHash("system:controller:node-controller"), + starboard.LabelResourceNamespace: "", + }, + Annotations: map[string]string{ + "kee": "pass", + + starboard.LabelResourceName: "system:controller:node-controller", + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := kube.ObjectToObjectMetadata(tc.object, &tc.meta) + require.NoError(t, err) + assert.Equal(t, tc.expected, tc.meta) + }) + } +} + func TestObjectFromLabelsSet(t *testing.T) { testCases := []struct { name string @@ -492,43 +663,6 @@ func TestObjectResolver_GetRelatedReplicasetName(t *testing.T) { } -func TestIsClusterScopedKind(t *testing.T) { - testCases := []struct { - kind string - want bool - }{ - { - kind: "Role", - want: false, - }, - { - kind: "RoleBinding", - want: false, - }, - { - kind: "ClusterRole", - want: true, - }, - { - kind: "ClusterRoleBinding", - want: true, - }, - { - kind: "CustomResourceDefinition", - want: true, - }, - { - kind: "Pod", - want: false, - }, - } - for _, tt := range testCases { - t.Run(fmt.Sprintf("Should return %t when controller kind is %s", tt.want, tt.kind), func(t *testing.T) { - assert.Equal(t, tt.want, kube.IsClusterScopedKind(tt.kind)) - }) - } -} - func TestPartialObjectFromObjectMetadata(t *testing.T) { testCases := []struct { name string