@@ -7,12 +7,15 @@ package controllers
77
88import (
99 "context"
10+ "errors"
1011 "fmt"
11- "time"
1212
13+ "github.com/fluxcd/pkg/apis/meta"
14+ "github.com/fluxcd/pkg/runtime/conditions"
1315 "github.com/fluxcd/pkg/runtime/patch"
1416 corev1 "k8s.io/api/core/v1"
1517 apierrors "k8s.io/apimachinery/pkg/api/errors"
18+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1619 "k8s.io/apimachinery/pkg/runtime"
1720 "k8s.io/apimachinery/pkg/types"
1821 ctrl "sigs.k8s.io/controller-runtime"
@@ -44,71 +47,139 @@ type GitSyncReconciler struct {
4447// Reconcile is part of the main kubernetes reconciliation loop which aims to
4548// move the current state of the cluster closer to the desired state.
4649func (r * GitSyncReconciler ) Reconcile (ctx context.Context , req ctrl.Request ) (ctrl.Result , error ) {
50+ var (
51+ result ctrl.Result
52+ retErr error
53+ )
54+
4755 log := log .FromContext (ctx )
4856 log .V (4 ).Info ("starting reconcile loop for snapshot" )
49- gitSync := & v1alpha1.GitSync {}
50- if err := r .Get (ctx , req .NamespacedName , gitSync ); err != nil {
57+ obj := & v1alpha1.GitSync {}
58+ if err := r .Get (ctx , req .NamespacedName , obj ); err != nil {
5159 if apierrors .IsNotFound (err ) {
5260 return ctrl.Result {}, nil
5361 }
5462 return ctrl.Result {}, fmt .Errorf ("failed to get git sync object: %w" , err )
5563 }
56- log .V (4 ).Info ("found reconciling object" , "gitSync" , gitSync )
64+ log .V (4 ).Info ("found reconciling object" , "gitSync" , obj )
65+
66+ // The replication controller doesn't need a shouldReconcile, because it should always reconcile,
67+ // that is its purpose.
68+ patchHelper , err := patch .NewHelper (obj , r .Client )
69+ if err != nil {
70+ retErr = errors .Join (retErr , err )
71+ conditions .MarkFalse (obj , meta .ReadyCondition , v1alpha1 .PatchFailedReason , err .Error ())
72+
73+ return ctrl.Result {}, retErr
74+ }
75+
76+ // Always attempt to patch the object and status after each reconciliation.
77+ defer func () {
78+ // Patching has not been set up, or the controller errored earlier.
79+ if patchHelper == nil {
80+ return
81+ }
5782
58- if gitSync .Status .Digest != "" {
59- log .Info ("GitSync object already synced; status contains digest information" , "digest" , gitSync .Status .Digest )
83+ if condition := conditions .Get (obj , meta .StalledCondition ); condition != nil && condition .Status == metav1 .ConditionTrue {
84+ conditions .Delete (obj , meta .ReconcilingCondition )
85+ }
86+
87+ // Check if it's a successful reconciliation.
88+ // We don't set Requeue in case of error, so we can safely check for Requeue.
89+ if result .RequeueAfter == obj .GetRequeueAfter () && ! result .Requeue && retErr == nil {
90+ // Remove the reconciling condition if it's set.
91+ conditions .Delete (obj , meta .ReconcilingCondition )
92+
93+ // Set the return err as the ready failure message if the resource is not ready, but also not reconciling or stalled.
94+ if ready := conditions .Get (obj , meta .ReadyCondition ); ready != nil && ready .Status == metav1 .ConditionFalse && ! conditions .IsStalled (obj ) {
95+ retErr = errors .New (conditions .GetMessage (obj , meta .ReadyCondition ))
96+ }
97+ }
98+
99+ // If still reconciling then reconciliation did not succeed, set to ProgressingWithRetry to
100+ // indicate that reconciliation will be retried.
101+ if conditions .IsReconciling (obj ) {
102+ reconciling := conditions .Get (obj , meta .ReconcilingCondition )
103+ reconciling .Reason = meta .ProgressingWithRetryReason
104+ conditions .Set (obj , reconciling )
105+ }
106+
107+ // If not reconciling or stalled than mark Ready=True
108+ if ! conditions .IsReconciling (obj ) &&
109+ ! conditions .IsStalled (obj ) &&
110+ retErr == nil {
111+ conditions .MarkTrue (obj , meta .ReadyCondition , meta .SucceededReason , "Reconciliation success" )
112+ }
113+
114+ // Set status observed generation option if the component is stalled or ready.
115+ if conditions .IsStalled (obj ) || conditions .IsReady (obj ) {
116+ obj .Status .ObservedGeneration = obj .Generation
117+ }
118+
119+ // Update the object.
120+ if err := patchHelper .Patch (ctx , obj ); err != nil {
121+ retErr = errors .Join (retErr , err )
122+ }
123+ }()
124+
125+ // it's important that this happens here so any residual status condition can be overwritten / set.
126+ if obj .Status .Digest != "" {
127+ log .Info ("GitSync object already synced; status contains digest information" , "digest" , obj .Status .Digest )
60128 return ctrl.Result {}, nil
61129 }
62130
63131 snapshot := & ocmv1.Snapshot {}
64132 if err := r .Get (ctx , types.NamespacedName {
65- Namespace : gitSync .Spec .SnapshotRef .Namespace ,
66- Name : gitSync .Spec .SnapshotRef .Name ,
133+ Namespace : obj .Spec .SnapshotRef .Namespace ,
134+ Name : obj .Spec .SnapshotRef .Name ,
67135 }, snapshot ); err != nil {
68- return ctrl.Result {}, fmt .Errorf ("failed to find snapshot: %w" , err )
136+ retErr = fmt .Errorf ("failed to find snapshot: %w" , err )
137+ conditions .MarkFalse (obj , meta .ReadyCondition , v1alpha1 .SnapshotGetFailedReason , retErr .Error ())
138+
139+ return ctrl.Result {}, retErr
69140 }
141+
70142 authSecret := & corev1.Secret {}
71143 if err := r .Get (ctx , types.NamespacedName {
72- Namespace : gitSync .Spec .AuthRef .Namespace ,
73- Name : gitSync .Spec .AuthRef .Name ,
144+ Namespace : obj .Spec .AuthRef .Namespace ,
145+ Name : obj .Spec .AuthRef .Name ,
74146 }, authSecret ); err != nil {
75- return ctrl.Result {}, fmt .Errorf ("failed to find authentication secret: %w" , err )
147+ retErr = fmt .Errorf ("failed to find authentication secret: %w" , err )
148+ conditions .MarkFalse (obj , meta .ReadyCondition , v1alpha1 .CredentialsNotFoundReason , retErr .Error ())
149+
150+ return ctrl.Result {}, retErr
76151 }
77152
78153 // trim any trailing `/` and then just add.
79154 log .V (4 ).Info ("crafting artifact URL to download from" , "url" , snapshot .Status .RepositoryURL )
80155 opts := & providers.PushOptions {
81- URL : gitSync .Spec .URL ,
82- Message : gitSync .Spec .CommitTemplate .Message ,
83- Name : gitSync .Spec .CommitTemplate .Name ,
84- Email : gitSync .Spec .CommitTemplate .Email ,
156+ URL : obj .Spec .URL ,
157+ Message : obj .Spec .CommitTemplate .Message ,
158+ Name : obj .Spec .CommitTemplate .Name ,
159+ Email : obj .Spec .CommitTemplate .Email ,
85160 Snapshot : snapshot ,
86- Branch : gitSync .Spec .Branch ,
87- SubPath : gitSync .Spec .SubPath ,
161+ Branch : obj .Spec .Branch ,
162+ SubPath : obj .Spec .SubPath ,
88163 }
164+
89165 r .parseAuthSecret (authSecret , opts )
90166
91167 digest , err := r .Git .Push (ctx , opts )
92168 if err != nil {
93- return ctrl.Result {}, fmt .Errorf ("failed to push to git repository: %w" , err )
94- }
95- // Initialize the patch helper.
96- patchHelper , err := patch .NewHelper (gitSync , r .Client )
97- if err != nil {
98- return ctrl.Result {
99- RequeueAfter : 1 * time .Minute ,
100- }, fmt .Errorf ("failed to create patch helper: %w" , err )
101- }
169+ retErr = fmt .Errorf ("failed to push to git repository: %w" , err )
170+ conditions .MarkFalse (obj , meta .ReadyCondition , v1alpha1 .GitRepositoryPushFailedReason , retErr .Error ())
102171
103- gitSync .Status .Digest = digest
104- if err := patchHelper .Patch (ctx , gitSync ); err != nil {
105- return ctrl.Result {
106- RequeueAfter : 1 * time .Minute ,
107- }, fmt .Errorf ("failed to patch git sync object: %w" , err )
172+ return ctrl.Result {}, retErr
108173 }
109- log .V (4 ).Info ("patch successful" )
110174
111- return ctrl.Result {}, nil
175+ obj .Status .Digest = digest
176+
177+ // Remove any stale Ready condition, most likely False, set above. Its value
178+ // is derived from the overall result of the reconciliation in the deferred
179+ // block at the very end.
180+ conditions .Delete (obj , meta .ReadyCondition )
181+
182+ return result , retErr
112183}
113184
114185// SetupWithManager sets up the controller with the Manager.
0 commit comments