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

952 lines
26 KiB
Go

// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package task
import (
"fmt"
"strings"
"sync"
"testing"
"gotest.tools/assert"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/dynamic"
"k8s.io/kubectl/pkg/cmd/util"
"sigs.k8s.io/cli-utils/pkg/apply/event"
"sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
"sigs.k8s.io/cli-utils/pkg/common"
"sigs.k8s.io/cli-utils/pkg/inventory"
"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
uid types.UID
generation int64
}
// Tests that the correct "applied" objects are sent
// to the TaskContext correctly, since these are the
// applied objects added to the final inventory.
func TestApplyTask_BasicAppliedObjects(t *testing.T) {
testCases := map[string]struct {
applied []resourceInfo
}{
"apply single namespaced resource": {
applied: []resourceInfo{
{
group: "apps",
apiVersion: "apps/v1",
kind: "Deployment",
name: "foo",
namespace: "default",
uid: types.UID("my-uid"),
generation: int64(42),
},
},
},
"apply multiple clusterscoped resources": {
applied: []resourceInfo{
{
group: "custom.io",
apiVersion: "custom.io/v1beta1",
kind: "Custom",
name: "bar",
uid: types.UID("uid-1"),
generation: int64(32),
},
{
group: "custom2.io",
apiVersion: "custom2.io/v1",
kind: "Custom2",
name: "foo",
uid: types.UID("uid-2"),
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)
objs := toUnstructureds(tc.applied)
oldAO := applyOptionsFactoryFunc
applyOptionsFactoryFunc = func(chan event.Event, common.ServerSideOptions, common.DryRunStrategy, util.Factory) (applyOptions, dynamic.Interface, error) {
return &fakeApplyOptions{}, nil, nil
}
defer func() { applyOptionsFactoryFunc = oldAO }()
restMapper := testutil.NewFakeRESTMapper(schema.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
}, schema.GroupVersionKind{
Group: "anothercustom.io",
Version: "v2",
Kind: "AnotherCustom",
})
applyTask := &ApplyTask{
Objects: objs,
Mapper: restMapper,
InfoHelper: &fakeInfoHelper{},
InvInfo: &fakeInventoryInfo{},
}
getClusterObj = func(d dynamic.Interface, info *resource.Info) (*unstructured.Unstructured, error) {
return objs[0], nil
}
applyTask.Start(taskContext)
<-taskContext.TaskChannel()
// The applied resources should be stored in the TaskContext
// for the final inventory.
expected := object.UnstructuredsToObjMetas(objs)
actual := taskContext.AppliedResources()
if !object.SetEquals(expected, actual) {
t.Errorf("expected (%s) inventory resources, got (%s)", expected, actual)
}
})
}
}
// Checks the inventory stored in the task context applied
// resources is correct, given a retrieval error and
// a specific previous inventory. Also, an apply failure
// for an object in the previous inventory should remain
// in the inventory, while an apply failure that is not
// in the previous inventory (creation) should not be
// in the final inventory.
func TestApplyTask_ApplyFailuresAndInventory(t *testing.T) {
resInfo := resourceInfo{
group: "apps",
apiVersion: "apps/v1",
kind: "Deployment",
name: "foo",
namespace: "default",
uid: types.UID("my-uid"),
generation: int64(42),
}
resID, _ := object.CreateObjMetadata("default", "foo",
schema.GroupKind{Group: "apps", Kind: "Deployment"})
applyFailInfo := resourceInfo{
group: "apps",
apiVersion: "apps/v1",
kind: "Deployment",
name: "failure",
namespace: "default",
uid: types.UID("my-uid"),
generation: int64(42),
}
applyFailID, _ := object.CreateObjMetadata("default", "failure",
schema.GroupKind{Group: "apps", Kind: "Deployment"})
testCases := map[string]struct {
applied []resourceInfo
prevInventory []object.ObjMetadata
expected []object.ObjMetadata
err error
}{
"not found error with successful apply is in final inventory": {
applied: []resourceInfo{resInfo},
prevInventory: []object.ObjMetadata{},
expected: []object.ObjMetadata{resID},
err: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "pod"}, "fake"),
},
"unknown error, but in previous inventory: object is in final inventory": {
applied: []resourceInfo{resInfo},
prevInventory: []object.ObjMetadata{resID},
expected: []object.ObjMetadata{resID},
err: apierrors.NewUnauthorized("not authorized"),
},
"unknown error, not in previous inventory: object is NOT in final inventory": {
applied: []resourceInfo{resInfo},
prevInventory: []object.ObjMetadata{},
expected: []object.ObjMetadata{},
err: apierrors.NewUnauthorized("not authorized"),
},
"apply failure, in previous inventory: object is in final inventory": {
applied: []resourceInfo{applyFailInfo},
prevInventory: []object.ObjMetadata{applyFailID},
expected: []object.ObjMetadata{applyFailID},
err: nil,
},
"apply failure, not in previous inventory: object is NOT in final inventory": {
applied: []resourceInfo{applyFailInfo},
prevInventory: []object.ObjMetadata{},
expected: []object.ObjMetadata{},
err: nil,
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
eventChannel := make(chan event.Event)
taskContext := taskrunner.NewTaskContext(eventChannel)
objs := toUnstructureds(tc.applied)
oldAO := applyOptionsFactoryFunc
applyOptionsFactoryFunc = func(chan event.Event, common.ServerSideOptions, common.DryRunStrategy, util.Factory) (applyOptions, dynamic.Interface, error) {
return &fakeApplyOptions{}, nil, nil
}
defer func() { applyOptionsFactoryFunc = oldAO }()
restMapper := testutil.NewFakeRESTMapper(schema.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
})
prevInv := map[object.ObjMetadata]bool{}
for _, id := range tc.prevInventory {
prevInv[id] = true
}
applyTask := &ApplyTask{
Objects: objs,
PrevInventory: prevInv,
Mapper: restMapper,
InfoHelper: &fakeInfoHelper{},
InvInfo: &fakeInventoryInfo{},
}
getClusterObj = func(d dynamic.Interface, info *resource.Info) (*unstructured.Unstructured, error) {
return objs[0], nil
}
if tc.err != nil {
getClusterObj = func(d dynamic.Interface, info *resource.Info) (*unstructured.Unstructured, error) {
return nil, tc.err
}
}
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()
// The applied resources should be stored in the TaskContext
// for the final inventory.
actual := taskContext.AppliedResources()
if !object.SetEquals(tc.expected, actual) {
t.Errorf("expected (%s) inventory resources, got (%s)", tc.expected, actual)
}
})
}
}
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",
uid: types.UID("my-uid"),
generation: int64(42),
},
},
},
"multiple clusterscoped resources": {
rss: []resourceInfo{
{
group: "custom.io",
apiVersion: "custom.io/v1beta1",
kind: "Custom",
name: "bar",
uid: types.UID("uid-1"),
generation: int64(32),
},
{
group: "custom2.io",
apiVersion: "custom2.io/v1",
kind: "Custom2",
name: "foo",
uid: types.UID("uid-2"),
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)
objs := toUnstructureds(tc.rss)
oldAO := applyOptionsFactoryFunc
applyOptionsFactoryFunc = func(chan event.Event, common.ServerSideOptions, common.DryRunStrategy, util.Factory) (applyOptions, dynamic.Interface, error) {
return &fakeApplyOptions{}, nil, nil
}
defer func() { applyOptionsFactoryFunc = oldAO }()
applyTask := &ApplyTask{
Objects: objs,
InfoHelper: &fakeInfoHelper{},
InvInfo: &fakeInventoryInfo{},
}
getClusterObj = func(d dynamic.Interface, info *resource.Info) (*unstructured.Unstructured, error) {
return objs[0], nil
}
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,
}
uid, _ := taskContext.ResourceUID(id)
assert.Equal(t, info.uid, uid)
gen, _ := taskContext.ResourceGeneration(id)
assert.Equal(t, info.generation, gen)
}
})
}
}
func TestApplyTask_DryRun(t *testing.T) {
testCases := map[string]struct {
objs []*unstructured.Unstructured
crds []*unstructured.Unstructured
expectedObjects []object.ObjMetadata
expectedEvents []event.Event
}{
"dry run with no CRDs or CRs": {
objs: []*unstructured.Unstructured{
toUnstructured(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: []*unstructured.Unstructured{
toUnstructured(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",
},
},
},
}),
},
objs: []*unstructured.Unstructured{
toUnstructured(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: []*unstructured.Unstructured{
toUnstructured(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",
},
},
},
}),
},
objs: []*unstructured.Unstructured{
toUnstructured(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 {
for i := range common.Strategies {
drs := common.Strategies[i]
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",
})
ao := &fakeApplyOptions{}
oldAO := applyOptionsFactoryFunc
applyOptionsFactoryFunc = func(chan event.Event, common.ServerSideOptions, common.DryRunStrategy, util.Factory) (applyOptions, dynamic.Interface, error) {
return ao, nil, nil
}
defer func() { applyOptionsFactoryFunc = oldAO }()
getClusterObj = func(d dynamic.Interface, info *resource.Info) (*unstructured.Unstructured, error) {
return addOwningInventory(tc.objs[0], "id"), nil
}
applyTask := &ApplyTask{
Objects: tc.objs,
InfoHelper: &fakeInfoHelper{},
Mapper: restMapper,
DryRunStrategy: drs,
CRDs: tc.crds,
InvInfo: &fakeInventoryInfo{},
}
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(ao.objects))
for i, obj := range ao.objects {
actual, err := object.InfoToObjMeta(obj)
if err != nil {
continue
}
assert.Equal(t, tc.expectedObjects[i], actual)
}
assert.Equal(t, len(tc.expectedEvents), len(events))
for i, e := range events {
assert.Equal(t, tc.expectedEvents[i].Type, e.Type)
}
})
}
}
}
func TestApplyTaskWithError(t *testing.T) {
testCases := map[string]struct {
objs []*unstructured.Unstructured
crds []*unstructured.Unstructured
expectedObjects []object.ObjMetadata
expectedEvents []event.Event
}{
"some resources have apply error": {
crds: []*unstructured.Unstructured{
toUnstructured(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",
},
},
},
}),
},
objs: []*unstructured.Unstructured{
toUnstructured(map[string]interface{}{
"apiVersion": "anothercustom.io/v2",
"kind": "AnotherCustom",
"metadata": map[string]interface{}{
"name": "bar",
"namespace": "barbar",
},
}),
toUnstructured(map[string]interface{}{
"apiVersion": "anothercustom.io/v2",
"kind": "AnotherCustom",
"metadata": map[string]interface{}{
"name": "bar-with-failure",
"namespace": "barbar",
},
}),
},
expectedObjects: []object.ObjMetadata{
{
GroupKind: schema.GroupKind{
Group: "anothercustom.io",
Kind: "AnotherCustom",
},
Name: "bar",
Namespace: "barbar",
},
},
expectedEvents: []event.Event{
{
Type: event.ApplyType,
ApplyEvent: event.ApplyEvent{
Error: fmt.Errorf("expected apply error"),
},
},
},
},
}
for tn, tc := range testCases {
drs := common.DryRunNone
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",
})
ao := &fakeApplyOptions{}
oldAO := applyOptionsFactoryFunc
applyOptionsFactoryFunc = func(chan event.Event, common.ServerSideOptions, common.DryRunStrategy, util.Factory) (applyOptions, dynamic.Interface, error) {
return ao, nil, nil
}
defer func() { applyOptionsFactoryFunc = oldAO }()
getClusterObj = func(d dynamic.Interface, info *resource.Info) (*unstructured.Unstructured, error) {
return addOwningInventory(tc.objs[0], "id"), nil
}
applyTask := &ApplyTask{
Objects: tc.objs,
InfoHelper: &fakeInfoHelper{},
Mapper: restMapper,
DryRunStrategy: drs,
CRDs: tc.crds,
InvInfo: &fakeInventoryInfo{},
}
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(ao.passedObjects))
for i, obj := range ao.passedObjects {
actual, err := object.InfoToObjMeta(obj)
if err != nil {
continue
}
assert.Equal(t, tc.expectedObjects[i], actual)
}
assert.Equal(t, len(tc.expectedEvents), len(events))
for i, e := range events {
assert.Equal(t, tc.expectedEvents[i].Type, e.Type)
assert.Equal(t, tc.expectedEvents[i].ApplyEvent.Error.Error(), e.ApplyEvent.Error.Error())
}
})
}
}
var deployment = toUnstructured(map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy",
"namespace": "default",
"uid": "uid-deployment",
},
})
var deploymentObjMetadata = []object.ObjMetadata{
{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "Deployment",
},
Name: "deploy",
Namespace: "default",
},
}
func TestApplyTaskWithDifferentInventoryAnnotation(t *testing.T) {
testCases := map[string]struct {
obj *unstructured.Unstructured
clusterObj *unstructured.Unstructured
policy inventory.InventoryPolicy
expectedObjects []object.ObjMetadata
expectedEvents []event.Event
}{
"InventoryPolicyMustMatch with object doesn't exist on cluster - Can Apply": {
obj: deployment,
clusterObj: nil,
policy: inventory.InventoryPolicyMustMatch,
expectedObjects: deploymentObjMetadata,
expectedEvents: []event.Event{},
},
"InventoryPolicyMustMatch with object annotation is empty - Can't Apply": {
obj: deployment,
clusterObj: removeOwningInventory(deployment),
policy: inventory.InventoryPolicyMustMatch,
expectedObjects: nil,
expectedEvents: []event.Event{
{
Type: event.ApplyType,
ApplyEvent: event.ApplyEvent{
Error: inventory.NewNeedAdoptionError(
fmt.Errorf("can't adopt an object without the annotation config.k8s.io/owning-inventory")),
},
},
},
},
"InventoryPolicyMustMatch with object annotation doesn't match - Can't Apply": {
obj: deployment,
clusterObj: addOwningInventory(deployment, "unmatchd"),
policy: inventory.InventoryPolicyMustMatch,
expectedObjects: nil,
expectedEvents: []event.Event{
{
Type: event.ApplyType,
ApplyEvent: event.ApplyEvent{
Error: inventory.NewInventoryOverlapError(
fmt.Errorf("can't apply the resource since its annotation config.k8s.io/owning-inventory is a different inventory object")),
},
},
},
},
"InventoryPolicyMustMatch with object annotation matches - Can Apply": {
obj: deployment,
clusterObj: addOwningInventory(deployment, "id"),
policy: inventory.InventoryPolicyMustMatch,
expectedObjects: deploymentObjMetadata,
expectedEvents: nil,
},
"AdoptIfNoInventory with object doesn't exist on cluster - Can Apply": {
obj: deployment,
clusterObj: nil,
policy: inventory.AdoptIfNoInventory,
expectedObjects: deploymentObjMetadata,
expectedEvents: []event.Event{},
},
"AdoptIfNoInventory with object annotation is empty - Can Apply": {
obj: deployment,
clusterObj: removeOwningInventory(deployment),
policy: inventory.AdoptIfNoInventory,
expectedObjects: deploymentObjMetadata,
expectedEvents: []event.Event{},
},
"AdoptIfNoInventory with object annotation doesn't match - Can't Apply": {
obj: deployment,
clusterObj: addOwningInventory(deployment, "notmatch"),
policy: inventory.AdoptIfNoInventory,
expectedObjects: nil,
expectedEvents: []event.Event{
{
Type: event.ApplyType,
ApplyEvent: event.ApplyEvent{
Error: inventory.NewInventoryOverlapError(
fmt.Errorf("can't apply the resource since its annotation config.k8s.io/owning-inventory is a different inventory object")),
},
},
},
},
"AdoptIfNoInventory with object annotation matches - Can Apply": {
obj: deployment,
clusterObj: addOwningInventory(deployment, "id"),
policy: inventory.AdoptIfNoInventory,
expectedObjects: deploymentObjMetadata,
expectedEvents: []event.Event{},
},
"AdoptAll with object doesn't exist on cluster - Can Apply": {
obj: deployment,
clusterObj: nil,
policy: inventory.AdoptAll,
expectedObjects: deploymentObjMetadata,
expectedEvents: []event.Event{},
},
}
for tn, tc := range testCases {
drs := common.DryRunNone
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",
})
ao := &fakeApplyOptions{}
oldAO := applyOptionsFactoryFunc
applyOptionsFactoryFunc = func(chan event.Event, common.ServerSideOptions, common.DryRunStrategy, util.Factory) (applyOptions, dynamic.Interface, error) {
return ao, nil, nil
}
defer func() { applyOptionsFactoryFunc = oldAO }()
getClusterObj = func(d dynamic.Interface, info *resource.Info) (*unstructured.Unstructured, error) {
return tc.clusterObj, nil
}
applyTask := &ApplyTask{
Objects: []*unstructured.Unstructured{tc.obj},
InfoHelper: &fakeInfoHelper{},
Mapper: restMapper,
DryRunStrategy: drs,
InvInfo: &fakeInventoryInfo{},
InventoryPolicy: tc.policy,
}
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(ao.passedObjects))
for i, obj := range ao.passedObjects {
actual, err := object.InfoToObjMeta(obj)
if err != nil {
continue
}
assert.Equal(t, tc.expectedObjects[i], actual)
}
assert.Equal(t, len(tc.expectedEvents), len(events))
for i, e := range events {
assert.Equal(t, tc.expectedEvents[i].Type, e.Type)
assert.Equal(t, tc.expectedEvents[i].ApplyEvent.Error.Error(), e.ApplyEvent.Error.Error())
}
actualUids := taskContext.AllResourceUIDs()
assert.Equal(t, len(actualUids), 1)
})
}
}
func toUnstructured(obj map[string]interface{}) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: obj,
}
}
func toUnstructureds(rss []resourceInfo) []*unstructured.Unstructured {
var objs []*unstructured.Unstructured
for _, rs := range rss {
objs = append(objs, &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": rs.apiVersion,
"kind": rs.kind,
"metadata": map[string]interface{}{
"name": rs.name,
"namespace": rs.namespace,
"uid": string(rs.uid),
"generation": rs.generation,
"annotations": map[string]interface{}{
"config.k8s.io/owning-inventory": "id",
},
},
},
})
}
return objs
}
type fakeApplyOptions struct {
objects []*resource.Info
passedObjects []*resource.Info
}
func (f *fakeApplyOptions) Run() error {
var err error
for _, obj := range f.objects {
if strings.Contains(obj.Name, "failure") {
err = fmt.Errorf("expected apply error")
} else {
f.passedObjects = append(f.passedObjects, obj)
}
}
return err
}
func (f *fakeApplyOptions) SetObjects(objects []*resource.Info) {
f.objects = objects
}
type fakeInfoHelper struct{}
func (f *fakeInfoHelper) UpdateInfo(*resource.Info) error {
return nil
}
func (f *fakeInfoHelper) BuildInfos(objs []*unstructured.Unstructured) ([]*resource.Info, error) {
return object.UnstructuredsToInfos(objs)
}
func (f *fakeInfoHelper) BuildInfo(obj *unstructured.Unstructured) (*resource.Info, error) {
return object.UnstructuredToInfo(obj)
}
type fakeInventoryInfo struct{}
func (fi *fakeInventoryInfo) Name() string {
return "name"
}
func (fi *fakeInventoryInfo) Namespace() string {
return "namespace"
}
func (fi *fakeInventoryInfo) ID() string {
return "id"
}
func addOwningInventory(obj *unstructured.Unstructured, id string) *unstructured.Unstructured {
if obj == nil {
return nil
}
newObj := obj.DeepCopy()
annotations := newObj.GetAnnotations()
if len(annotations) == 0 {
annotations = make(map[string]string)
}
annotations["config.k8s.io/owning-inventory"] = id
newObj.SetAnnotations(annotations)
return newObj
}
func removeOwningInventory(obj *unstructured.Unstructured) *unstructured.Unstructured {
if obj == nil {
return nil
}
newObj := obj.DeepCopy()
annotations := newObj.GetAnnotations()
if len(annotations) == 0 {
return newObj
}
delete(annotations, "config.k8s.io/owning-inventory")
newObj.SetAnnotations(annotations)
return newObj
}