istio
161 строка · 4.9 Кб
1// Copyright Istio Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package helmreconciler
16
17import (
18"context"
19"errors"
20"fmt"
21"os"
22"reflect"
23"sync"
24"testing"
25
26kerrors "k8s.io/apimachinery/pkg/api/errors"
27metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
29"k8s.io/apimachinery/pkg/runtime/schema"
30"k8s.io/apimachinery/pkg/types"
31"sigs.k8s.io/controller-runtime/pkg/client"
32"sigs.k8s.io/controller-runtime/pkg/client/fake"
33"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
34
35v1alpha12 "istio.io/api/operator/v1alpha1"
36"istio.io/istio/operator/pkg/apis/istio/v1alpha1"
37"istio.io/istio/operator/pkg/object"
38)
39
40var interceptorFunc = interceptor.Funcs{Patch: func(
41ctx context.Context,
42clnt client.WithWatch,
43obj client.Object,
44patch client.Patch,
45opts ...client.PatchOption,
46) error {
47// Apply patches are supposed to upsert, but fake client fails if the object doesn't exist,
48// if an apply patch occurs for an object that doesn't yet exist, create it.
49if patch.Type() != types.ApplyPatchType {
50return clnt.Patch(ctx, obj, patch, opts...)
51}
52check, ok := obj.DeepCopyObject().(client.Object)
53if !ok {
54return errors.New("could not check for object in fake client")
55}
56if err := clnt.Get(ctx, client.ObjectKeyFromObject(obj), check); kerrors.IsNotFound(err) {
57if err := clnt.Create(ctx, check); err != nil {
58return fmt.Errorf("could not inject object creation for fake: %w", err)
59}
60} else if err != nil {
61return err
62}
63obj.SetResourceVersion(check.GetResourceVersion())
64return clnt.Update(ctx, obj)
65}}
66
67func TestHelmReconciler_ApplyObject(t *testing.T) {
68tests := []struct {
69name string
70currentState string
71input string
72want string
73wantErr bool
74}{
75{
76name: "creates if not present",
77input: "testdata/configmap.yaml",
78want: "testdata/configmap.yaml",
79},
80{
81name: "updates if present",
82currentState: "testdata/configmap.yaml",
83input: "testdata/configmap-changed.yaml",
84want: "testdata/configmap-changed.yaml",
85},
86// Test IstioOperator field removals
87{
88name: "creates if not present",
89input: "testdata/iop-test-gw-1.yaml",
90want: "testdata/iop-test-gw-1.yaml",
91},
92{
93name: "updates if present",
94currentState: "testdata/iop-test-gw-1.yaml",
95input: "testdata/iop-changed.yaml",
96want: "testdata/iop-changed.yaml",
97},
98}
99for _, tt := range tests {
100t.Run(tt.name, func(t *testing.T) {
101obj := loadData(t, tt.input)
102var k8sClient client.Client
103if tt.currentState != "" {
104k8sClient = fake.NewClientBuilder().
105WithRuntimeObjects(loadData(t, tt.currentState).
106UnstructuredObject()).WithInterceptorFuncs(interceptorFunc).Build()
107} else {
108// no current state provided, initialize fake client without runtime object
109k8sClient = fake.NewClientBuilder().WithInterceptorFuncs(interceptorFunc).Build()
110}
111
112cl := k8sClient
113h := &HelmReconciler{
114client: cl,
115opts: &Options{},
116iop: &v1alpha1.IstioOperator{
117ObjectMeta: metav1.ObjectMeta{
118Name: "test-operator",
119Namespace: "istio-operator-test",
120},
121Spec: &v1alpha12.IstioOperatorSpec{},
122},
123countLock: &sync.Mutex{},
124prunedKindSet: map[schema.GroupKind]struct{}{},
125}
126if err := h.ApplyObject(obj.UnstructuredObject()); (err != nil) != tt.wantErr {
127t.Errorf("HelmReconciler.ApplyObject() error = %v, wantErr %v", err, tt.wantErr)
128}
129
130manifest := loadData(t, tt.want)
131key := client.ObjectKeyFromObject(manifest.UnstructuredObject())
132got, want := obj.UnstructuredObject(), manifest.UnstructuredObject()
133
134if err := cl.Get(context.Background(), key, got); err != nil {
135t.Errorf("error validating manifest %v: %v", manifest.Hash(), err)
136}
137// remove resource version and annotations (last applied config) when we compare as we don't care
138unstructured.RemoveNestedField(got.Object, "metadata", "resourceVersion")
139unstructured.RemoveNestedField(got.Object, "metadata", "annotations")
140
141if !reflect.DeepEqual(want, got) {
142t.Errorf("wanted:\n%v\ngot:\n%v",
143object.NewK8sObject(want, nil, nil).YAMLDebugString(),
144object.NewK8sObject(got, nil, nil).YAMLDebugString(),
145)
146}
147})
148}
149}
150
151func loadData(t *testing.T, file string) *object.K8sObject {
152contents, err := os.ReadFile(file)
153if err != nil {
154t.Fatal(err)
155}
156obj, err := object.ParseYAMLToK8sObject(contents)
157if err != nil {
158t.Fatal(err)
159}
160return obj
161}
162