blob: 92aadfca08c9845d6db568cbc5742a21c1609900 [file] [log] [blame]
// Copyright Istio 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 helmreconciler
import (
"context"
"os"
"reflect"
"sync"
"testing"
)
import (
v1alpha12 "istio.io/api/operator/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
import (
"github.com/apache/dubbo-go-pixiu/operator/pkg/apis/istio/v1alpha1"
"github.com/apache/dubbo-go-pixiu/operator/pkg/object"
)
func TestHelmReconciler_ApplyObject(t *testing.T) {
tests := []struct {
name string
currentState string
input string
want string
wantErr bool
}{
{
name: "creates if not present",
input: "testdata/configmap.yaml",
want: "testdata/configmap.yaml",
},
{
name: "updates if present",
currentState: "testdata/configmap.yaml",
input: "testdata/configmap-changed.yaml",
want: "testdata/configmap-changed.yaml",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
obj := loadData(t, tt.input)
cl := &fakeClientWrapper{fake.NewClientBuilder().WithRuntimeObjects(loadData(t, tt.input).UnstructuredObject()).Build()}
h := &HelmReconciler{
client: cl,
opts: &Options{},
iop: &v1alpha1.IstioOperator{
ObjectMeta: v1.ObjectMeta{
Name: "test-operator",
Namespace: "istio-operator-test",
},
Spec: &v1alpha12.IstioOperatorSpec{},
},
countLock: &sync.Mutex{},
prunedKindSet: map[schema.GroupKind]struct{}{},
}
if err := h.ApplyObject(obj.UnstructuredObject(), false); (err != nil) != tt.wantErr {
t.Errorf("HelmReconciler.ApplyObject() error = %v, wantErr %v", err, tt.wantErr)
}
manifest := loadData(t, tt.want)
key := client.ObjectKeyFromObject(manifest.UnstructuredObject())
got, want := obj.UnstructuredObject(), manifest.UnstructuredObject()
if err := cl.Get(context.Background(), key, got); err != nil {
t.Errorf("error validating manifest %v: %v", manifest.Hash(), err)
}
// remove resource version and annotations (last applied config) when we compare as we don't care
unstructured.RemoveNestedField(got.Object, "metadata", "resourceVersion")
unstructured.RemoveNestedField(got.Object, "metadata", "annotations")
if !reflect.DeepEqual(want, got) {
t.Errorf("wanted:\n%v\ngot:\n%v",
object.NewK8sObject(want, nil, nil).YAMLDebugString(),
object.NewK8sObject(got, nil, nil).YAMLDebugString(),
)
}
})
}
}
type fakeClientWrapper struct {
client.Client
}
// Patch converts apply patches to merge patches because fakeclient does not support apply patch.
func (c *fakeClientWrapper) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
patch, opts = convertApplyToMergePatch(patch, opts...)
return c.Client.Patch(ctx, obj, patch, opts...)
}
func convertApplyToMergePatch(patch client.Patch, opts ...client.PatchOption) (client.Patch, []client.PatchOption) {
if patch.Type() == types.ApplyPatchType {
patch = client.Merge
patchOptions := make([]client.PatchOption, 0, len(opts))
for _, opt := range opts {
if opt == client.ForceOwnership {
continue
}
patchOptions = append(patchOptions, opt)
}
opts = patchOptions
}
return patch, opts
}
func loadData(t *testing.T, file string) *object.K8sObject {
contents, err := os.ReadFile(file)
if err != nil {
t.Fatal(err)
}
obj, err := object.ParseYAMLToK8sObject(contents)
if err != nil {
t.Fatal(err)
}
return obj
}