@@ -13,6 +13,7 @@ import (
1313 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1414 "k8s.io/apimachinery/pkg/util/intstr"
1515 "k8s.io/client-go/kubernetes"
16+ "k8s.io/client-go/util/retry"
1617)
1718
1819const (
@@ -192,6 +193,27 @@ func (n *PDBController) generateDesiredPDBs(resources []kubeResource, managedPDB
192193 return desiredPDBs
193194}
194195
196+ // mergeActualAndDesiredPDB takes the current definition of a PDB as it is in cluster and a PDB
197+ // with our desired configurations and does a merge between them. We also return a boolean to tell the
198+ // caller if any change actually had to be made to achieve our desired state or not
199+ func mergeActualAndDesiredPDB (managedPDB , desiredPDB pv1.PodDisruptionBudget ) (pv1.PodDisruptionBudget , bool ) {
200+
201+ needsUpdate := false
202+
203+ // check if PDBs are equal an only update if not
204+ if ! equality .Semantic .DeepEqual (managedPDB .Spec , desiredPDB .Spec ) ||
205+ ! equality .Semantic .DeepEqual (managedPDB .Labels , desiredPDB .Labels ) ||
206+ ! equality .Semantic .DeepEqual (managedPDB .Annotations , desiredPDB .Annotations ) {
207+ managedPDB .Annotations = desiredPDB .Annotations
208+ managedPDB .Labels = desiredPDB .Labels
209+ managedPDB .Spec = desiredPDB .Spec
210+
211+ needsUpdate = true
212+ }
213+
214+ return managedPDB , needsUpdate
215+ }
216+
195217func (n * PDBController ) reconcilePDBs (ctx context.Context , desiredPDBs , managedPDBs map [string ]pv1.PodDisruptionBudget ) {
196218 for key , managedPDB := range managedPDBs {
197219 desiredPDB , ok := desiredPDBs [key ]
@@ -215,14 +237,36 @@ func (n *PDBController) reconcilePDBs(ctx context.Context, desiredPDBs, managedP
215237 }
216238
217239 // check if PDBs are equal an only update if not
218- if ! equality .Semantic .DeepEqual (managedPDB .Spec , desiredPDB .Spec ) ||
219- ! equality .Semantic .DeepEqual (managedPDB .Labels , desiredPDB .Labels ) ||
220- ! equality .Semantic .DeepEqual (managedPDB .Annotations , desiredPDB .Annotations ) {
221- managedPDB .Annotations = desiredPDB .Annotations
222- managedPDB .Labels = desiredPDB .Labels
223- managedPDB .Spec = desiredPDB .Spec
224-
225- _ , err := n .PolicyV1 ().PodDisruptionBudgets (managedPDB .Namespace ).Update (ctx , & managedPDB , metav1.UpdateOptions {})
240+ updatedPDB , needsUpdate := mergeActualAndDesiredPDB (managedPDB , desiredPDB )
241+ if needsUpdate {
242+ err := retry .RetryOnConflict (retry .DefaultRetry , func () error {
243+ // Technically the updatedPDB and managedPDB namespace should never be different
244+ // but just to be **certain** we're updating the correct namespace we'll just use
245+ // the one that was given to us and not potentially modified
246+ _ , err := n .PolicyV1 ().PodDisruptionBudgets (managedPDB .Namespace ).Update (ctx , & updatedPDB , metav1.UpdateOptions {})
247+
248+ // If the update failed that likely means that our definition of what was on the cluster
249+ // has become out of date. To resolve this we'll need to get a more up to date copy of
250+ // the object we're attempting to modify
251+ if err != nil {
252+ currentPDB , err := n .PolicyV1 ().PodDisruptionBudgets (managedPDB .Namespace ).Get (ctx , managedPDB .Name , metav1.GetOptions {})
253+
254+ // This err is locally scoped to this if block and will not cause our `RetryOnConflict`
255+ // to pass if it is nil. If we're in this block then we will get another Retry
256+ if err != nil {
257+ return err
258+ }
259+
260+ updatedPDB , _ = mergeActualAndDesiredPDB (
261+ * currentPDB ,
262+ desiredPDB ,
263+ )
264+ }
265+
266+ // If this err != nil then the current block will be re-run by `RetryOnConflict`
267+ // on an exponential backoff schedule to see if we can fix the problem by trying again
268+ return err
269+ })
226270 if err != nil {
227271 log .Errorf ("Failed to update PDB: %v" , err )
228272 continue
0 commit comments