mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-14 10:00:06 +00:00
(cherry picked from commit 552764d48f)
Signed-off-by: Brian Kane <briankane1@gmail.com>
Co-authored-by: Ayush Kumar <65535504+roguepikachu@users.noreply.github.com>
676 lines
20 KiB
Go
676 lines
20 KiB
Go
/*
|
|
Copyright 2021 The KubeVela Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package apply
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/crossplane/crossplane-runtime/pkg/test"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/pkg/errors"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
|
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
|
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/apimachinery/pkg/types"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
|
"github.com/oam-dev/kubevela/pkg/oam"
|
|
)
|
|
|
|
var ctx = context.Background()
|
|
var errFake = errors.New("fake error")
|
|
|
|
type testObject struct {
|
|
runtime.Object
|
|
metav1.ObjectMeta
|
|
}
|
|
|
|
func (t *testObject) DeepCopyObject() runtime.Object {
|
|
return &testObject{ObjectMeta: *t.ObjectMeta.DeepCopy()}
|
|
}
|
|
|
|
func (t *testObject) GetObjectKind() schema.ObjectKind {
|
|
return schema.EmptyObjectKind
|
|
}
|
|
|
|
type testNoMetaObject struct {
|
|
runtime.Object
|
|
}
|
|
|
|
func TestAPIApplicator(t *testing.T) {
|
|
existing := &testObject{}
|
|
existing.SetName("existing")
|
|
desired := &testObject{}
|
|
desired.SetName("desired")
|
|
// use Deployment as a registered API sample
|
|
testDeploy := &appsv1.Deployment{}
|
|
testDeploy.SetGroupVersionKind(schema.GroupVersionKind{
|
|
Group: "apps",
|
|
Version: "v1",
|
|
Kind: "Deployment",
|
|
})
|
|
type args struct {
|
|
existing client.Object
|
|
creatorErr error
|
|
patcherErr error
|
|
desired client.Object
|
|
ao []ApplyOption
|
|
}
|
|
|
|
cases := map[string]struct {
|
|
reason string
|
|
c client.Client
|
|
args args
|
|
want error
|
|
}{
|
|
"ErrorOccursCreatOrGetExisting": {
|
|
reason: "An error should be returned if cannot create or get existing",
|
|
args: args{
|
|
creatorErr: errFake,
|
|
},
|
|
want: errFake,
|
|
},
|
|
"CreateSuccessfully": {
|
|
reason: "No error should be returned if create successfully",
|
|
},
|
|
"CannotApplyApplyOptions": {
|
|
reason: "An error should be returned if cannot apply ApplyOption",
|
|
args: args{
|
|
existing: existing,
|
|
ao: []ApplyOption{
|
|
func(_ *applyAction, existing, desired client.Object) error {
|
|
return errFake
|
|
},
|
|
},
|
|
},
|
|
want: errors.Wrap(errFake, "cannot apply ApplyOption"),
|
|
},
|
|
"CalculatePatchError": {
|
|
reason: "An error should be returned if patch failed",
|
|
args: args{
|
|
existing: existing,
|
|
desired: desired,
|
|
patcherErr: errFake,
|
|
},
|
|
c: &test.MockClient{MockPatch: test.NewMockPatchFn(errFake)},
|
|
want: errors.Wrap(errFake, "cannot calculate patch by computing a three way diff"),
|
|
},
|
|
"PatchError": {
|
|
reason: "An error should be returned if patch failed",
|
|
args: args{
|
|
existing: existing,
|
|
desired: testDeploy,
|
|
},
|
|
c: &test.MockClient{MockPatch: test.NewMockPatchFn(errFake)},
|
|
want: errors.Wrap(errFake, "cannot patch object"),
|
|
},
|
|
"PatchingApplySuccessfully": {
|
|
reason: "No error should be returned if patch successfully",
|
|
args: args{
|
|
existing: existing,
|
|
desired: desired,
|
|
},
|
|
c: &test.MockClient{MockPatch: test.NewMockPatchFn(nil)},
|
|
},
|
|
}
|
|
|
|
for caseName, tc := range cases {
|
|
t.Run(caseName, func(t *testing.T) {
|
|
a := &APIApplicator{
|
|
creator: creatorFn(func(_ context.Context, _ *applyAction, _ client.Client, _ client.Object, _ ...ApplyOption) (client.Object, error) {
|
|
return tc.args.existing, tc.args.creatorErr
|
|
}),
|
|
patcher: patcherFn(func(c, m client.Object, a *applyAction) (client.Patch, error) {
|
|
return client.RawPatch(types.MergePatchType, []byte(`err`)), tc.args.patcherErr
|
|
}),
|
|
c: tc.c,
|
|
}
|
|
result := a.Apply(ctx, tc.args.desired, tc.args.ao...)
|
|
if diff := cmp.Diff(tc.want, result, test.EquateErrors()); diff != "" {
|
|
t.Errorf("\n%s\nApply(...): -want , +got \n%s\n", tc.reason, diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreator(t *testing.T) {
|
|
desired := &unstructured.Unstructured{}
|
|
desired.SetName("desired")
|
|
type args struct {
|
|
desired client.Object
|
|
ao []ApplyOption
|
|
}
|
|
type want struct {
|
|
existing client.Object
|
|
err error
|
|
}
|
|
|
|
cases := map[string]struct {
|
|
reason string
|
|
c client.Client
|
|
args args
|
|
want want
|
|
}{
|
|
"CannotCreateObjectWithoutName": {
|
|
reason: "An error should be returned if cannot create the object",
|
|
args: args{
|
|
desired: &testObject{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
GenerateName: "prefix",
|
|
},
|
|
},
|
|
},
|
|
c: &test.MockClient{MockCreate: test.NewMockCreateFn(errFake)},
|
|
want: want{
|
|
existing: nil,
|
|
err: errors.Wrap(errFake, "cannot create object"),
|
|
},
|
|
},
|
|
"CannotCreate": {
|
|
reason: "An error should be returned if cannot create the object",
|
|
c: &test.MockClient{
|
|
MockCreate: test.NewMockCreateFn(errFake),
|
|
MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, ""))},
|
|
args: args{
|
|
desired: desired,
|
|
},
|
|
want: want{
|
|
existing: nil,
|
|
err: errors.Wrap(errFake, "cannot create object"),
|
|
},
|
|
},
|
|
"CannotGetExisting": {
|
|
reason: "An error should be returned if cannot get the object",
|
|
c: &test.MockClient{
|
|
MockGet: test.NewMockGetFn(errFake)},
|
|
args: args{
|
|
desired: desired,
|
|
},
|
|
want: want{
|
|
existing: nil,
|
|
err: errors.Wrap(errFake, "cannot get object"),
|
|
},
|
|
},
|
|
"ApplyOptionErrorWhenCreatObjectWithoutName": {
|
|
reason: "An error should be returned if cannot apply ApplyOption",
|
|
args: args{
|
|
desired: &testObject{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
GenerateName: "prefix",
|
|
},
|
|
},
|
|
ao: []ApplyOption{
|
|
func(_ *applyAction, existing, desired client.Object) error {
|
|
return errFake
|
|
},
|
|
},
|
|
},
|
|
want: want{
|
|
existing: nil,
|
|
err: errors.Wrap(errFake, "cannot apply ApplyOption"),
|
|
},
|
|
},
|
|
"ApplyOptionErrorWhenCreatObject": {
|
|
reason: "An error should be returned if cannot apply ApplyOption",
|
|
c: &test.MockClient{MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, ""))},
|
|
args: args{
|
|
desired: desired,
|
|
ao: []ApplyOption{
|
|
func(_ *applyAction, existing, desired client.Object) error {
|
|
return errFake
|
|
},
|
|
},
|
|
},
|
|
want: want{
|
|
existing: nil,
|
|
err: errors.Wrap(errFake, "cannot apply ApplyOption"),
|
|
},
|
|
},
|
|
"CreateWithoutNameSuccessfully": {
|
|
reason: "No error and existing should be returned if create successfully",
|
|
c: &test.MockClient{MockCreate: test.NewMockCreateFn(nil)},
|
|
args: args{
|
|
desired: &testObject{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
GenerateName: "prefix",
|
|
},
|
|
},
|
|
},
|
|
want: want{
|
|
existing: nil,
|
|
err: nil,
|
|
},
|
|
},
|
|
"CreateSuccessfully": {
|
|
reason: "No error and existing should be returned if create successfully",
|
|
c: &test.MockClient{
|
|
MockCreate: test.NewMockCreateFn(nil),
|
|
MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, ""))},
|
|
args: args{
|
|
desired: desired,
|
|
},
|
|
want: want{
|
|
existing: nil,
|
|
err: nil,
|
|
},
|
|
},
|
|
"GetExistingSuccessfully": {
|
|
reason: "Existing object and no error should be returned",
|
|
c: &test.MockClient{
|
|
MockGet: test.NewMockGetFn(nil, func(obj client.Object) error {
|
|
o, _ := obj.(*unstructured.Unstructured)
|
|
*o = *desired
|
|
return nil
|
|
})},
|
|
args: args{
|
|
desired: desired,
|
|
},
|
|
want: want{
|
|
existing: desired,
|
|
err: nil,
|
|
},
|
|
},
|
|
}
|
|
|
|
for caseName, tc := range cases {
|
|
t.Run(caseName, func(t *testing.T) {
|
|
act := new(applyAction)
|
|
result, err := createOrGetExisting(ctx, act, tc.c, tc.args.desired, tc.args.ao...)
|
|
if diff := cmp.Diff(tc.want.existing, result); diff != "" {
|
|
t.Errorf("\n%s\ncreateOrGetExisting(...): -want , +got \n%s\n", tc.reason, diff)
|
|
}
|
|
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
|
t.Errorf("\n%s\ncreateOrGetExisting(...): -want error, +got error\n%s\n", tc.reason, diff)
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestMustBeControllableBy(t *testing.T) {
|
|
uid := types.UID("very-unique-string")
|
|
controller := true
|
|
|
|
cases := map[string]struct {
|
|
reason string
|
|
current client.Object
|
|
u types.UID
|
|
want error
|
|
}{
|
|
"NoExistingObject": {
|
|
reason: "No error should be returned if no existing object",
|
|
},
|
|
"Adoptable": {
|
|
reason: "A current object with no controller reference may be adopted and controlled",
|
|
u: uid,
|
|
current: &testObject{},
|
|
},
|
|
"ControlledBySuppliedUID": {
|
|
reason: "A current object that is already controlled by the supplied UID is controllable",
|
|
u: uid,
|
|
current: &testObject{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{
|
|
UID: uid,
|
|
Controller: &controller,
|
|
}}}},
|
|
},
|
|
"ControlledBySomeoneElse": {
|
|
reason: "A current object that is already controlled by a different UID is not controllable",
|
|
u: uid,
|
|
current: &testObject{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{
|
|
UID: types.UID("some-other-uid"),
|
|
Controller: &controller,
|
|
}}}},
|
|
want: errors.Errorf("existing object is not controlled by UID %q", uid),
|
|
},
|
|
"cross namespace resource": {
|
|
reason: "A cross namespace resource have a resourceTracker owner, skip check UID",
|
|
u: uid,
|
|
current: &testObject{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{
|
|
UID: uid,
|
|
Controller: &controller,
|
|
Kind: v1beta1.ResourceTrackerKind,
|
|
}}}},
|
|
},
|
|
}
|
|
|
|
for name, tc := range cases {
|
|
t.Run(name, func(t *testing.T) {
|
|
ao := MustBeControllableBy(tc.u)
|
|
act := new(applyAction)
|
|
err := ao(act, tc.current, nil)
|
|
if diff := cmp.Diff(tc.want, err, test.EquateErrors()); diff != "" {
|
|
t.Errorf("\n%s\nMustBeControllableBy(...)(...): -want error, +got error\n%s\n", tc.reason, diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMustBeControlledByApp(t *testing.T) {
|
|
app := &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "app"}}
|
|
ao := MustBeControlledByApp(app)
|
|
testCases := map[string]struct {
|
|
existing client.Object
|
|
hasError bool
|
|
}{
|
|
"no old app": {
|
|
existing: nil,
|
|
hasError: false,
|
|
},
|
|
"old app has no label": {
|
|
existing: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "-"}},
|
|
hasError: true,
|
|
},
|
|
"old app has no app label": {
|
|
existing: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{},
|
|
ResourceVersion: "-",
|
|
}},
|
|
hasError: true,
|
|
},
|
|
"old app has no app ns label": {
|
|
existing: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{oam.LabelAppName: "app"},
|
|
ResourceVersion: "-",
|
|
}},
|
|
hasError: true,
|
|
},
|
|
"old app has correct label": {
|
|
existing: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{oam.LabelAppName: "app", oam.LabelAppNamespace: "default"},
|
|
}},
|
|
hasError: false,
|
|
},
|
|
"old app has incorrect app label": {
|
|
existing: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{oam.LabelAppName: "a", oam.LabelAppNamespace: "default"},
|
|
}},
|
|
hasError: true,
|
|
},
|
|
"old app has incorrect ns label": {
|
|
existing: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{oam.LabelAppName: "app", oam.LabelAppNamespace: "ns"},
|
|
}},
|
|
hasError: true,
|
|
},
|
|
"old app has no resource version but with bad app key": {
|
|
existing: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{oam.LabelAppName: "app", oam.LabelAppNamespace: "ns"},
|
|
}},
|
|
hasError: true,
|
|
},
|
|
"old app has no resource version": {
|
|
existing: &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{},
|
|
}},
|
|
hasError: false,
|
|
},
|
|
}
|
|
for name, tc := range testCases {
|
|
t.Run(name, func(t *testing.T) {
|
|
r := require.New(t)
|
|
err := ao(&applyAction{}, tc.existing, nil)
|
|
if tc.hasError {
|
|
r.Error(err)
|
|
} else {
|
|
r.NoError(err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSharedByApp(t *testing.T) {
|
|
app := &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "app"}}
|
|
ao := SharedByApp(app)
|
|
testCases := map[string]struct {
|
|
existing client.Object
|
|
desired client.Object
|
|
output client.Object
|
|
hasError bool
|
|
expectIsShared bool
|
|
}{
|
|
"create new resource": {
|
|
existing: nil,
|
|
desired: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "ConfigMap",
|
|
}},
|
|
output: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"annotations": map[string]interface{}{oam.AnnotationAppSharedBy: "default/app"},
|
|
},
|
|
}},
|
|
},
|
|
"add sharer to existing resource": {
|
|
existing: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "ConfigMap",
|
|
}},
|
|
desired: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "ConfigMap",
|
|
}},
|
|
output: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"annotations": map[string]interface{}{oam.AnnotationAppSharedBy: "default/app"},
|
|
},
|
|
}},
|
|
},
|
|
"add sharer to existing sharing resource": {
|
|
existing: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{
|
|
oam.LabelAppName: "example",
|
|
oam.LabelAppNamespace: "default",
|
|
},
|
|
"annotations": map[string]interface{}{oam.AnnotationAppSharedBy: "x/y"},
|
|
},
|
|
"data": "x",
|
|
}},
|
|
desired: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "ConfigMap",
|
|
"data": "y",
|
|
}},
|
|
// Non-owner sharer: desired only gets the shared-by annotation added
|
|
// The actual resource content is NOT modified - the short-circuit in Apply()
|
|
// will patch only the annotation on the server
|
|
output: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"annotations": map[string]interface{}{oam.AnnotationAppSharedBy: "x/y,default/app"},
|
|
},
|
|
"data": "y",
|
|
}},
|
|
expectIsShared: true,
|
|
},
|
|
"add sharer to existing sharing resource owned by self": {
|
|
existing: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{
|
|
oam.LabelAppName: "app",
|
|
oam.LabelAppNamespace: "default",
|
|
},
|
|
"annotations": map[string]interface{}{oam.AnnotationAppSharedBy: "default/app,x/y"},
|
|
},
|
|
"data": "x",
|
|
}},
|
|
desired: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{
|
|
oam.LabelAppName: "app",
|
|
oam.LabelAppNamespace: "default",
|
|
},
|
|
"annotations": map[string]interface{}{oam.AnnotationAppSharedBy: "default/app"},
|
|
},
|
|
"data": "y",
|
|
}},
|
|
output: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{
|
|
oam.LabelAppName: "app",
|
|
oam.LabelAppNamespace: "default",
|
|
},
|
|
"annotations": map[string]interface{}{oam.AnnotationAppSharedBy: "default/app,x/y"},
|
|
},
|
|
"data": "y",
|
|
}},
|
|
},
|
|
"add sharer to existing non-sharing resource": {
|
|
existing: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{
|
|
oam.LabelAppName: "example",
|
|
oam.LabelAppNamespace: "default",
|
|
},
|
|
},
|
|
}},
|
|
desired: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "ConfigMap",
|
|
}},
|
|
hasError: true,
|
|
},
|
|
"non-owner sharer sets short-circuit flags": {
|
|
existing: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "Pod",
|
|
"metadata": map[string]interface{}{
|
|
"name": "test-pod",
|
|
"resourceVersion": "12345",
|
|
"labels": map[string]interface{}{
|
|
oam.LabelAppName: "app1",
|
|
oam.LabelAppNamespace: "default",
|
|
},
|
|
"annotations": map[string]interface{}{
|
|
oam.AnnotationAppSharedBy: "default/app1",
|
|
},
|
|
},
|
|
}},
|
|
desired: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "Pod",
|
|
"metadata": map[string]interface{}{
|
|
"name": "test-pod",
|
|
},
|
|
}},
|
|
// For non-owner sharers, desired only gets the shared-by annotation added
|
|
// The actual patching happens in Apply() via short-circuit
|
|
output: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "Pod",
|
|
"metadata": map[string]interface{}{
|
|
"name": "test-pod",
|
|
"annotations": map[string]interface{}{
|
|
oam.AnnotationAppSharedBy: "default/app1,default/app",
|
|
},
|
|
},
|
|
}},
|
|
// These flags are checked in the test loop
|
|
expectIsShared: true,
|
|
},
|
|
"non-owner sharer works without last-applied-configuration": {
|
|
existing: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "test-cm",
|
|
"labels": map[string]interface{}{
|
|
oam.LabelAppName: "app1",
|
|
oam.LabelAppNamespace: "default",
|
|
},
|
|
"annotations": map[string]interface{}{
|
|
oam.AnnotationAppSharedBy: "default/app1",
|
|
},
|
|
},
|
|
"data": map[string]interface{}{
|
|
"key": "value",
|
|
},
|
|
}},
|
|
desired: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "test-cm",
|
|
},
|
|
}},
|
|
// For non-owner sharers, desired only gets the shared-by annotation added
|
|
output: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "test-cm",
|
|
"annotations": map[string]interface{}{
|
|
oam.AnnotationAppSharedBy: "default/app1,default/app",
|
|
},
|
|
},
|
|
}},
|
|
expectIsShared: true,
|
|
},
|
|
}
|
|
for name, tc := range testCases {
|
|
t.Run(name, func(t *testing.T) {
|
|
r := require.New(t)
|
|
act := &applyAction{}
|
|
err := ao(act, tc.existing, tc.desired)
|
|
if tc.hasError {
|
|
r.Error(err)
|
|
} else {
|
|
r.NoError(err)
|
|
r.Equal(tc.output, tc.desired)
|
|
|
|
// Verify short-circuit flags for non-owner sharers
|
|
if tc.expectIsShared {
|
|
r.True(act.isShared, "isShared should be true for non-owner sharers")
|
|
r.False(act.updateAnnotation, "updateAnnotation should be false for non-owner sharers")
|
|
}
|
|
|
|
// Legacy check: When a resource is shared by another app, updateAnnotation should be false
|
|
if tc.existing != nil && tc.existing.GetAnnotations() != nil && tc.existing.GetAnnotations()[oam.AnnotationAppSharedBy] != "" {
|
|
existingController := GetControlledBy(tc.existing)
|
|
if existingController != "" && existingController != GetAppKey(app) {
|
|
r.False(act.updateAnnotation, "updateAnnotation should be false when sharing resource controlled by another app")
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFilterSpecialAnn(t *testing.T) {
|
|
var cm = &corev1.ConfigMap{}
|
|
var sc = &corev1.Secret{}
|
|
var dp = &appsv1.Deployment{}
|
|
var crd = &v1.CustomResourceDefinition{}
|
|
assert.Equal(t, false, trimLastAppliedConfigurationForSpecialResources(cm))
|
|
assert.Equal(t, false, trimLastAppliedConfigurationForSpecialResources(sc))
|
|
assert.Equal(t, false, trimLastAppliedConfigurationForSpecialResources(crd))
|
|
assert.Equal(t, true, trimLastAppliedConfigurationForSpecialResources(dp))
|
|
|
|
dp.Annotations = map[string]string{oam.AnnotationLastAppliedConfig: "-"}
|
|
assert.Equal(t, false, trimLastAppliedConfigurationForSpecialResources(dp))
|
|
dp.Annotations = map[string]string{oam.AnnotationLastAppliedConfig: "skip"}
|
|
assert.Equal(t, false, trimLastAppliedConfigurationForSpecialResources(dp))
|
|
dp.Annotations = map[string]string{oam.AnnotationLastAppliedConfig: "xxx"}
|
|
assert.Equal(t, true, trimLastAppliedConfigurationForSpecialResources(dp))
|
|
}
|