cli-utils/pkg/apply/task/apply_task_test.go

313 lines
6.9 KiB
Go

// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package task
import (
"sync"
"testing"
"gotest.tools/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/resource"
"sigs.k8s.io/cli-utils/pkg/apply/event"
"sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
"sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/cli-utils/pkg/testutil"
)
type resourceInfo struct {
group string
apiVersion string
kind string
name string
namespace string
generation int64
}
func TestApplyTask_FetchGeneration(t *testing.T) {
testCases := map[string]struct {
rss []resourceInfo
}{
"single namespaced resource": {
rss: []resourceInfo{
{
group: "apps",
apiVersion: "apps/v1",
kind: "Deployment",
name: "foo",
namespace: "default",
generation: int64(42),
},
},
},
"multiple clusterscoped resources": {
rss: []resourceInfo{
{
group: "custom.io",
apiVersion: "custom.io/v1beta1",
kind: "Custom",
name: "bar",
generation: int64(32),
},
{
group: "custom2.io",
apiVersion: "custom2.io/v1",
kind: "Custom2",
name: "foo",
generation: int64(1),
},
},
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
eventChannel := make(chan event.Event)
defer close(eventChannel)
taskContext := taskrunner.NewTaskContext(eventChannel)
infos := toInfos(tc.rss)
applyOptions := &fakeApplyOptions{}
applyTask := &ApplyTask{
ApplyOptions: applyOptions,
Objects: infos,
InfoHelper: &fakeInfoHelper{},
}
applyTask.Start(taskContext)
<-taskContext.TaskChannel()
for _, info := range tc.rss {
id := object.ObjMetadata{
GroupKind: schema.GroupKind{
Group: info.group,
Kind: info.kind,
},
Name: info.name,
Namespace: info.namespace,
}
gen := taskContext.ResourceGeneration(id)
assert.Equal(t, info.generation, gen)
}
})
}
}
func TestApplyTask_DryRun(t *testing.T) {
testCases := map[string]struct {
infos []*resource.Info
crds []*resource.Info
expectedObjects []object.ObjMetadata
expectedEvents []event.Event
}{
"dry run with no CRDs or CRs": {
infos: []*resource.Info{
toInfo(map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "foo",
"namespace": "default",
},
}),
},
expectedObjects: []object.ObjMetadata{
{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "Deployment",
},
Name: "foo",
Namespace: "default",
},
},
expectedEvents: []event.Event{},
},
"dry run with CRD and CR": {
crds: []*resource.Info{
toInfo(map[string]interface{}{
"apiVersion": "apiextensions.k8s.io/v1",
"kind": "CustomResourceDefinition",
"metadata": map[string]interface{}{
"name": "foo",
},
"spec": map[string]interface{}{
"group": "custom.io",
"names": map[string]interface{}{
"kind": "Custom",
},
"versions": []interface{}{
map[string]interface{}{
"name": "v1alpha1",
},
},
},
}),
},
infos: []*resource.Info{
toInfo(map[string]interface{}{
"apiVersion": "custom.io/v1alpha1",
"kind": "Custom",
"metadata": map[string]interface{}{
"name": "bar",
},
}),
},
expectedObjects: []object.ObjMetadata{},
expectedEvents: []event.Event{
{
Type: event.ApplyType,
},
},
},
"dry run with CRD and CR and CRD already installed": {
crds: []*resource.Info{
toInfo(map[string]interface{}{
"apiVersion": "apiextensions.k8s.io/v1",
"kind": "CustomResourceDefinition",
"metadata": map[string]interface{}{
"name": "foo",
},
"spec": map[string]interface{}{
"group": "anothercustom.io",
"names": map[string]interface{}{
"kind": "AnotherCustom",
},
"versions": []interface{}{
map[string]interface{}{
"name": "v2",
},
},
},
}),
},
infos: []*resource.Info{
toInfo(map[string]interface{}{
"apiVersion": "anothercustom.io/v2",
"kind": "AnotherCustom",
"metadata": map[string]interface{}{
"name": "bar",
"namespace": "barbar",
},
}),
},
expectedObjects: []object.ObjMetadata{
{
GroupKind: schema.GroupKind{
Group: "anothercustom.io",
Kind: "AnotherCustom",
},
Name: "bar",
Namespace: "barbar",
},
},
expectedEvents: []event.Event{},
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
eventChannel := make(chan event.Event)
taskContext := taskrunner.NewTaskContext(eventChannel)
restMapper := testutil.NewFakeRESTMapper(schema.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
}, schema.GroupVersionKind{
Group: "anothercustom.io",
Version: "v2",
Kind: "AnotherCustom",
})
applyOptions := &fakeApplyOptions{}
applyTask := &ApplyTask{
ApplyOptions: applyOptions,
Objects: tc.infos,
InfoHelper: &fakeInfoHelper{},
Mapper: restMapper,
DryRun: true,
CRDs: tc.crds,
}
var events []event.Event
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for msg := range eventChannel {
events = append(events, msg)
}
}()
applyTask.Start(taskContext)
<-taskContext.TaskChannel()
close(eventChannel)
wg.Wait()
assert.Equal(t, len(tc.expectedObjects), len(applyOptions.objects))
for i, obj := range applyOptions.objects {
assert.Equal(t, tc.expectedObjects[i], object.InfoToObjMeta(obj))
}
assert.Equal(t, len(tc.expectedEvents), len(events))
for i, e := range events {
assert.Equal(t, tc.expectedEvents[i].Type, e.Type)
}
})
}
}
func toInfo(obj map[string]interface{}) *resource.Info {
return &resource.Info{
Object: &unstructured.Unstructured{
Object: obj,
},
}
}
func toInfos(rss []resourceInfo) []*resource.Info {
var infos []*resource.Info
for _, rs := range rss {
infos = append(infos, &resource.Info{
Object: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": rs.apiVersion,
"kind": rs.kind,
"metadata": map[string]interface{}{
"name": rs.name,
"namespace": rs.namespace,
"generation": rs.generation,
},
},
},
})
}
return infos
}
type fakeApplyOptions struct {
objects []*resource.Info
}
func (f *fakeApplyOptions) Run() error {
return nil
}
func (f *fakeApplyOptions) SetObjects(objects []*resource.Info) {
f.objects = objects
}
type fakeInfoHelper struct{}
func (f *fakeInfoHelper) UpdateInfos([]*resource.Info) error {
return nil
}