@@ -22,6 +22,7 @@ import (
2222 apierrors "k8s.io/apimachinery/pkg/api/errors"
2323 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2424 "k8s.io/apimachinery/pkg/runtime/schema"
25+ "k8s.io/apimachinery/pkg/types"
2526 ctrlrt "sigs.k8s.io/controller-runtime"
2627 "sigs.k8s.io/controller-runtime/pkg/client"
2728 k8sctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -53,6 +54,7 @@ type adoptionReconciler struct {
5354// of an upstream controller-runtime.Manager
5455func (r * adoptionReconciler ) BindControllerManager (mgr ctrlrt.Manager ) error {
5556 r .kc = mgr .GetClient ()
57+ r .apiReader = mgr .GetAPIReader ()
5658 return ctrlrt .NewControllerManagedBy (
5759 mgr ,
5860 ).For (
@@ -130,7 +132,7 @@ func (r *adoptionReconciler) reconcile(req ctrlrt.Request) error {
130132 }
131133
132134 if res .DeletionTimestamp != nil {
133- return r .cleanup (ctx , * res )
135+ return r .cleanup (ctx , res )
134136 }
135137
136138 // Determine whether the reason is in a terminal state
@@ -212,31 +214,41 @@ func (r *adoptionReconciler) sync(
212214 targetDescriptor .MarkManaged (described )
213215 targetDescriptor .MarkAdopted (described )
214216
215- if err := r .kc .Create (ctx , described .RuntimeObject ()); err != nil {
216- return r .onError (ctx , desired , err )
217- }
218-
219- if err := r .kc .Status ().Update (ctx , described .RuntimeObject ()); err != nil {
220- return r .onError (ctx , desired , err )
217+ // Only create the described resource if it does not already exist
218+ // in k8s cluster.
219+ if err := r .apiReader .Get (ctx , types.NamespacedName {
220+ Namespace : described .MetaObject ().GetNamespace (),
221+ Name : described .MetaObject ().GetName (),
222+ }, described .RuntimeObject ()); err != nil {
223+ if apierrors .IsNotFound (err ) {
224+ // If Adopted AWS resource was not found in k8s, create it.
225+ if err := r .kc .Create (ctx , described .RuntimeObject ()); err != nil {
226+ return r .onError (ctx , desired , err )
227+ }
228+
229+ if err := r .kc .Status ().Update (ctx , described .RuntimeObject ()); err != nil {
230+ return r .onError (ctx , desired , err )
231+ }
232+ } else {
233+ // for any other error except NotFound, return error
234+ return r .onError (ctx , desired , err )
235+ }
221236 }
222237
223- if err := r .markManaged (ctx , * desired ); err != nil {
238+ if err := r .markManaged (ctx , desired ); err != nil {
224239 return r .onError (ctx , desired , err )
225240 }
226241
227- if err := r .onSuccess (ctx , desired ); err != nil {
228- // Don't attempt to patch conditions again, directly return err
229- return err
230- }
231-
232- return nil
242+ // Don't attempt to patch conditions again, directly return result of
243+ // 'r.onSuccess'
244+ return r .onSuccess (ctx , desired )
233245}
234246
235- // cleanup ensures that the supplied AWSResource's backing API resource is
236- // destroyed along with all child dependent resources
247+ // cleanup removes the finalizer from AdoptedResource so that k8s object can
248+ // be deleted.
237249func (r * adoptionReconciler ) cleanup (
238250 ctx context.Context ,
239- current ackv1alpha1.AdoptedResource ,
251+ current * ackv1alpha1.AdoptedResource ,
240252) error {
241253 if err := r .markUnmanaged (ctx , current ); err != nil {
242254 return err
@@ -252,7 +264,15 @@ func (r *adoptionReconciler) getAdoptedResource(
252264 req ctrlrt.Request ,
253265) (* ackv1alpha1.AdoptedResource , error ) {
254266 ro := & ackv1alpha1.AdoptedResource {}
255- if err := r .kc .Get (ctx , req .NamespacedName , ro ); err != nil {
267+ // Here we use k8s APIReader to read the k8s object by making the
268+ // direct call to k8s apiserver instead of using k8sClient.
269+ // The reason is that k8sClient uses a cache and sometimes k8sClient can
270+ // return stale copy of object.
271+ // It is okay to make direct call to k8s apiserver because we are only
272+ // making single read call for complete reconciler loop.
273+ // See following issue for more details:
274+ // https://github.com/aws-controllers-k8s/community/issues/894
275+ if err := r .apiReader .Get (ctx , req .NamespacedName , ro ); err != nil {
256276 return nil , err
257277 }
258278 return ro , nil
@@ -279,16 +299,17 @@ func (r *adoptionReconciler) onSuccess(
279299}
280300
281301// patchAdoptedCondition updates the adopted condition status of the adopted resource
302+ // The resource passed in the parameter gets updated with the conditions
282303func (r * adoptionReconciler ) patchAdoptedCondition (
283304 ctx context.Context ,
284305 res * ackv1alpha1.AdoptedResource ,
285306 err error ,
286307) error {
287- ko := res .DeepCopy ()
308+ base := res .DeepCopy ()
288309
289310 // Adopted condition
290311 var adoptedCondition * ackv1alpha1.Condition = nil
291- for _ , condition := range ko .Status .Conditions {
312+ for _ , condition := range res .Status .Conditions {
292313 if condition .Type == ackv1alpha1 .ConditionTypeAdopted {
293314 adoptedCondition = condition
294315 break
@@ -299,7 +320,7 @@ func (r *adoptionReconciler) patchAdoptedCondition(
299320 adoptedCondition = & ackv1alpha1.Condition {
300321 Type : ackv1alpha1 .ConditionTypeAdopted ,
301322 }
302- ko .Status .Conditions = append (ko .Status .Conditions , adoptedCondition )
323+ res .Status .Conditions = append (res .Status .Conditions , adoptedCondition )
303324 }
304325
305326 var errMessage string
@@ -312,11 +333,7 @@ func (r *adoptionReconciler) patchAdoptedCondition(
312333 adoptedCondition .Status = corev1 .ConditionTrue
313334 }
314335
315- return r .kc .Status ().Patch (
316- ctx ,
317- ko .DeepCopyObject (),
318- client .MergeFrom (res ),
319- )
336+ return r .patchStatus (ctx , res , base )
320337}
321338
322339// isAdopted returns true if the AdoptedResource is in a terminal adoption state
@@ -344,39 +361,27 @@ func (r *adoptionReconciler) getTargetResourceGroupKind(
344361}
345362
346363// markManaged places the supplied resource under the management of ACK.
364+ // It adds the finalizer string, patches the object in etcd and updates
365+ // the object 'res' in parameter with latest metadata.
347366func (r * adoptionReconciler ) markManaged (
348367 ctx context.Context ,
349- res ackv1alpha1.AdoptedResource ,
368+ res * ackv1alpha1.AdoptedResource ,
350369) error {
351- orig := res .DeepCopyObject ()
370+ base := res .DeepCopy ()
352371 k8sctrlutil .AddFinalizer (& res .ObjectMeta , finalizerString )
353- err := r .kc .Patch (
354- ctx ,
355- res .DeepCopyObject (),
356- client .MergeFrom (orig ),
357- )
358- if err != nil {
359- return err
360- }
361- return nil
372+ return r .patchMetadataAndSpec (ctx , res , base )
362373}
363374
364375// markUnmanaged removes the supplied resource from management by ACK.
376+ // It removes the finalizer string, patches the object in etcd and updates
377+ // the object 'res' in parameter with latest metadata.
365378func (r * adoptionReconciler ) markUnmanaged (
366379 ctx context.Context ,
367- res ackv1alpha1.AdoptedResource ,
380+ res * ackv1alpha1.AdoptedResource ,
368381) error {
369- orig := res .DeepCopyObject ()
382+ base := res .DeepCopy ()
370383 k8sctrlutil .RemoveFinalizer (& res .ObjectMeta , finalizerString )
371- err := r .kc .Patch (
372- ctx ,
373- res .DeepCopyObject (),
374- client .MergeFrom (orig ),
375- )
376- if err != nil {
377- return err
378- }
379- return nil
384+ return r .patchMetadataAndSpec (ctx , res , base )
380385}
381386
382387// handleReconcileError will handle errors from reconcile handlers, which
@@ -481,6 +486,45 @@ func (r *adoptionReconciler) getRegion(
481486 return ackv1alpha1 .AWSRegion (r .cfg .Region )
482487}
483488
489+ // patchMetadataAndSpec patches the Metadata and Spec for AdoptedResource into
490+ // k8s. The adopted resource 'res' also gets updated with content returned from
491+ // apiserver.
492+ // TODO(vijat@): Refactor this and use single 'patchMetadataAndSpec' method
493+ // for reconciler and adoptionReconciler
494+ func (r * adoptionReconciler ) patchMetadataAndSpec (
495+ ctx context.Context ,
496+ res * ackv1alpha1.AdoptedResource ,
497+ base * ackv1alpha1.AdoptedResource ,
498+ ) error {
499+ // k8s Client Patch call updates the status of original object with the
500+ // content returned from apiserver.
501+ // Keep a copy of status field to reset the status of 'res' after patch call
502+ resStatusCopy := res .DeepCopy ().Status
503+ err := r .kc .Patch (
504+ ctx ,
505+ res ,
506+ client .MergeFrom (base ),
507+ )
508+ res .Status = resStatusCopy
509+ return err
510+ }
511+
512+ // patchStatus patches the Status for AdoptedResource into k8s. The adopted
513+ // resource 'res' also gets updated with the content returned from apiserver.
514+ // TODO(vijat@): Refactor this and use single 'patchStatus' method
515+ // for reconciler and adoptionReconciler
516+ func (r * adoptionReconciler ) patchStatus (
517+ ctx context.Context ,
518+ res * ackv1alpha1.AdoptedResource ,
519+ base * ackv1alpha1.AdoptedResource ,
520+ ) error {
521+ return r .kc .Status ().Patch (
522+ ctx ,
523+ res ,
524+ client .MergeFrom (base ),
525+ )
526+ }
527+
484528// NewAdoptionReconciler returns a new adoptionReconciler object
485529func NewAdoptionReconciler (
486530 sc acktypes.ServiceController ,
0 commit comments