1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package testreconciler
16
17 import (
18 "context"
19 "fmt"
20 "log"
21 "regexp"
22 "testing"
23 "time"
24
25 dclcontroller "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/dcl"
26 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/iam/auditconfig"
27 partialpolicy "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/iam/partialpolicy"
28 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/iam/policy"
29 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/iam/policymember"
30 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/reconciliationinterval"
31 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/tf"
32 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/crd/crdgeneration"
33 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/conversion"
34 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/metadata"
35 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/schema/dclschemaloader"
36 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
37 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
38 testcontroller "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/controller"
39 testk8s "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/k8s"
40 testservicemappingloader "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/servicemappingloader"
41
42 mmdcl "github.com/GoogleCloudPlatform/declarative-resource-client-library/dcl"
43 tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
44 "golang.org/x/sync/semaphore"
45 apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
46 "k8s.io/apimachinery/pkg/api/errors"
47 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
48 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
49 "sigs.k8s.io/controller-runtime/pkg/event"
50 "sigs.k8s.io/controller-runtime/pkg/manager"
51 "sigs.k8s.io/controller-runtime/pkg/reconcile"
52 )
53
54 type ResourceCleanupPolicy string
55
56 const (
57
58 CleanupPolicyAlways ResourceCleanupPolicy = "Always"
59
60
61 CleanupPolicyOnSuccess ResourceCleanupPolicy = "OnSuccess"
62 )
63
64 var (
65 ExpectedSuccessfulReconcileResultFor = expectedSuccessfulReconcileResultFor
66 ExpectedUnsuccessfulReconcileResult = reconcile.Result{Requeue: false, RequeueAfter: 0 * time.Minute}
67 ExpectedRequeueReconcileStruct = reconcile.Result{Requeue: true}
68 )
69
70 type TestReconciler struct {
71 mgr manager.Manager
72 t *testing.T
73 provider *tfschema.Provider
74 smLoader *servicemappingloader.ServiceMappingLoader
75 dclConfig *mmdcl.Config
76 dclConverter *conversion.Converter
77 }
78
79
80 func New(t *testing.T, mgr manager.Manager, provider *tfschema.Provider) *TestReconciler {
81 return NewForDCLAndTFTestReconciler(t, mgr, provider, nil)
82 }
83
84 func NewForDCLAndTFTestReconciler(t *testing.T, mgr manager.Manager, provider *tfschema.Provider, dclConfig *mmdcl.Config) *TestReconciler {
85 smLoader := testservicemappingloader.New(t)
86 dclSchemaLoader, err := dclschemaloader.New()
87 if err != nil {
88 log.Fatalf("error creating a DCL schema loader: %v", err)
89 }
90 serviceMetaLoader := metadata.New()
91 dclConverter := conversion.New(dclSchemaLoader, serviceMetaLoader)
92 return &TestReconciler{
93 mgr: mgr,
94 t: t,
95 provider: provider,
96 smLoader: smLoader,
97 dclConverter: dclConverter,
98 dclConfig: dclConfig,
99 }
100 }
101
102 func (r *TestReconciler) ReconcileIfManagedByKCC(unstruct *unstructured.Unstructured, expectedResult reconcile.Result, expectedErrorRegexp *regexp.Regexp) {
103 if k8s.IsManagedByKCC(unstruct.GroupVersionKind()) {
104 r.Reconcile(unstruct, expectedResult, expectedErrorRegexp)
105 } else {
106
107
108 log.Printf("%v %v/%v is not managed by KCC; skipping reconciliation",
109 unstruct.GetKind(), unstruct.GetNamespace(), unstruct.GetName())
110 }
111 }
112
113 func (r *TestReconciler) Reconcile(unstruct *unstructured.Unstructured, expectedResult reconcile.Result, expectedErrorRegex *regexp.Regexp) {
114 r.t.Helper()
115 om := metav1.ObjectMeta{
116 Name: unstruct.GetName(),
117 Namespace: unstruct.GetNamespace(),
118 }
119 r.ReconcileObjectMeta(om, unstruct.GetKind(), expectedResult, expectedErrorRegex)
120 }
121
122 func (r *TestReconciler) ReconcileObjectMeta(om metav1.ObjectMeta, kind string, expectedResult reconcile.Result, expectedErrorRegex *regexp.Regexp) {
123 r.t.Helper()
124 reconciler := r.NewReconcilerForKind(kind)
125 testcontroller.RunReconcilerAssertResults(r.t, reconciler, om, expectedResult, expectedErrorRegex)
126 }
127
128
129 func (r *TestReconciler) CreateAndReconcile(unstructs []*unstructured.Unstructured, cleanupPolicy ResourceCleanupPolicy) func() {
130 r.t.Helper()
131 cleanupFuncs := make([]func(), 0, len(unstructs))
132 for _, u := range unstructs {
133 if err := r.mgr.GetClient().Create(context.TODO(), u); err != nil {
134 r.t.Fatalf("error creating resource '%v': %v", u.GetKind(), err)
135 }
136 cleanupFuncs = append(cleanupFuncs, r.BuildCleanupFunc(u, cleanupPolicy))
137 r.ReconcileIfManagedByKCC(u, ExpectedSuccessfulReconcileResultFor(r, u), nil)
138 }
139 return func() {
140 for i := len(cleanupFuncs) - 1; i >= 0; i-- {
141 cleanupFuncs[i]()
142 }
143 }
144 }
145
146 func (r *TestReconciler) BuildCleanupFunc(unstruct *unstructured.Unstructured, cleanupPolicy ResourceCleanupPolicy) func() {
147 r.t.Helper()
148 return func() {
149 switch cleanupPolicy {
150 case CleanupPolicyAlways:
151 break
152 case CleanupPolicyOnSuccess:
153 if r.t.Failed() {
154 log.Printf("skipping cleanup of %v: %v/%v\n", unstruct.GetKind(), unstruct.GetNamespace(), unstruct.GetName())
155 return
156 }
157 default:
158 panic(fmt.Errorf("unknown cleanup policy: %v", cleanupPolicy))
159 }
160 log.Printf("Deleting %v: %v/%v\n", unstruct.GetKind(), unstruct.GetNamespace(), unstruct.GetName())
161 testk8s.RemoveDeletionDefenderFinalizerForUnstructured(r.t, unstruct, r.mgr.GetClient())
162 err := r.mgr.GetClient().Delete(context.TODO(), unstruct)
163 if err != nil {
164 if errors.IsNotFound(err) {
165 log.Printf("Resource already gone; no deletion required.")
166 return
167 }
168 r.t.Errorf("error deleting %v: %v", unstruct, err)
169 }
170 r.ReconcileIfManagedByKCC(unstruct, ExpectedSuccessfulReconcileResultFor(r, unstruct), nil)
171 }
172 }
173
174 func (r *TestReconciler) NewReconcilerForKind(kind string) reconcile.Reconciler {
175 r.t.Helper()
176 var reconciler reconcile.Reconciler
177 var err error
178
179
180
181
182
183
184
185 var immediateReconcileRequests chan event.GenericEvent = nil
186 var resourceWatcherRoutines *semaphore.Weighted = nil
187
188 switch kind {
189 case "IAMPolicy":
190 reconciler, err = policy.NewReconciler(r.mgr, r.provider, r.smLoader, r.dclConverter, r.dclConfig, immediateReconcileRequests, resourceWatcherRoutines)
191 case "IAMPartialPolicy":
192 reconciler, err = partialpolicy.NewReconciler(r.mgr, r.provider, r.smLoader, r.dclConverter, r.dclConfig, immediateReconcileRequests, resourceWatcherRoutines)
193 case "IAMPolicyMember":
194 reconciler, err = policymember.NewReconciler(r.mgr, r.provider, r.smLoader, r.dclConverter, r.dclConfig, immediateReconcileRequests, resourceWatcherRoutines)
195 case "IAMAuditConfig":
196 reconciler, err = auditconfig.NewReconciler(r.mgr, r.provider, r.smLoader, r.dclConverter, r.dclConfig, immediateReconcileRequests, resourceWatcherRoutines)
197 default:
198 crd := testcontroller.GetCRDForKind(r.t, r.mgr.GetClient(), kind)
199 reconciler, err = r.newReconcilerForCRD(crd)
200 }
201 if err != nil {
202 r.t.Fatalf("error creating reconciler: %v", err)
203 }
204 return reconciler
205 }
206
207 func (r *TestReconciler) newReconcilerForCRD(crd *apiextensions.CustomResourceDefinition) (reconcile.Reconciler, error) {
208 if crd.GetLabels()[crdgeneration.ManagedByKCCLabel] == "true" {
209
210
211
212
213
214
215
216 var immediateReconcileRequests chan event.GenericEvent = nil
217 var resourceWatcherRoutines *semaphore.Weighted = nil
218
219 if crd.GetLabels()[crdgeneration.TF2CRDLabel] == "true" {
220 return tf.NewReconciler(r.mgr, crd, r.provider, r.smLoader, immediateReconcileRequests, resourceWatcherRoutines)
221 }
222 if crd.GetLabels()[k8s.DCL2CRDLabel] == "true" {
223 return dclcontroller.NewReconciler(r.mgr, crd, r.dclConverter, r.dclConfig, r.smLoader, immediateReconcileRequests, resourceWatcherRoutines)
224 }
225 }
226 return nil, fmt.Errorf("CRD format not recognized")
227 }
228
229 func expectedSuccessfulReconcileResultFor(r *TestReconciler, u *unstructured.Unstructured) reconcile.Result {
230 if val, ok := k8s.GetAnnotation(k8s.ReconcileIntervalInSecondsAnnotation, u); ok {
231 reconcileInterval, err := reconciliationinterval.MeanReconcileReenqueuePeriodFromAnnotation(val)
232 if err != nil {
233 return reconcile.Result{}
234 }
235 return reconcile.Result{RequeueAfter: reconcileInterval}
236 }
237 return reconcile.Result{RequeueAfter: reconciliationinterval.MeanReconcileReenqueuePeriod(u.GroupVersionKind(), r.smLoader, r.dclConverter.MetadataLoader)}
238 }
239
View as plain text