Skip to content

Commit 5445f2a

Browse files
authored
Merge pull request #116 from weaveworks/bug-when-existing-resources-partial-apply
Fix partial-apply resources bug
2 parents 2989e4a + f24f6ca commit 5445f2a

File tree

5 files changed

+131
-79
lines changed

5 files changed

+131
-79
lines changed

api/v1alpha1/conditions.go

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,15 @@ const (
1717
)
1818

1919
// SetGitOpsSetReadiness sets the ready condition with the given status, reason and message.
20-
func SetGitOpsSetReadiness(set *GitOpsSet, status metav1.ConditionStatus, reason, message string) {
20+
func SetGitOpsSetReadiness(set *GitOpsSet, inventory *ResourceInventory, status metav1.ConditionStatus, reason, message string) {
21+
if inventory != nil {
22+
set.Status.Inventory = inventory
23+
24+
if len(inventory.Entries) == 0 {
25+
set.Status.Inventory = nil
26+
}
27+
}
28+
2129
set.Status.ObservedGeneration = set.ObjectMeta.Generation
2230
newCondition := metav1.Condition{
2331
Type: meta.ReadyCondition,
@@ -28,18 +36,6 @@ func SetGitOpsSetReadiness(set *GitOpsSet, status metav1.ConditionStatus, reason
2836
apimeta.SetStatusCondition(&set.Status.Conditions, newCondition)
2937
}
3038

31-
// SetReadyWithInventory updates the GitOpsSet to reflect the new readiness and
32-
// store the current inventory.
33-
func SetReadyWithInventory(set *GitOpsSet, inventory *ResourceInventory, reason, message string) {
34-
set.Status.Inventory = inventory
35-
36-
if len(inventory.Entries) == 0 {
37-
set.Status.Inventory = nil
38-
}
39-
40-
SetGitOpsSetReadiness(set, metav1.ConditionTrue, reason, message)
41-
}
42-
4339
// GetGitOpsSetReadiness returns the readiness condition of the GitOpsSet.
4440
func GetGitOpsSetReadiness(set *GitOpsSet) metav1.ConditionStatus {
4541
return apimeta.FindStatusCondition(set.Status.Conditions, meta.ReadyCondition).Status

controllers/gitopsset_controller.go

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const (
5252
)
5353

5454
type eventRecorder interface {
55-
Event(object runtime.Object, eventtype, reason, message string)
55+
Event(object runtime.Object, eventType, reason, message string)
5656
}
5757

5858
// GitOpsSetReconciler reconciles a GitOpsSet object
@@ -76,12 +76,12 @@ func (r *GitOpsSetReconciler) event(obj *templatesv1.GitOpsSet, severity, msg st
7676
reason = severity
7777
}
7878

79-
eventtype := "Normal"
79+
eventType := "Normal"
8080
if severity == eventv1.EventSeverityError {
81-
eventtype = "Error"
81+
eventType = "Error"
8282
}
8383

84-
r.EventRecorder.Event(obj, eventtype, reason, msg)
84+
r.EventRecorder.Event(obj, eventType, reason, msg)
8585
}
8686

8787
//+kubebuilder:rbac:groups=templates.weave.works,resources=gitopssets,verbs=get;list;watch;create;update;patch;delete
@@ -167,15 +167,16 @@ func (r *GitOpsSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
167167
if err != nil {
168168
// We can return here because when the resource artifact is updated, this
169169
// will trigger a reconciliation.
170+
170171
if errors.As(err, &generators.NoArtifactError{}) {
171-
templatesv1.SetGitOpsSetReadiness(&gitOpsSet, metav1.ConditionFalse, templatesv1.ReconciliationFailedReason, "waiting for artifact")
172+
templatesv1.SetGitOpsSetReadiness(&gitOpsSet, inventory, metav1.ConditionFalse, templatesv1.ReconciliationFailedReason, "waiting for artifact")
172173
if err := r.patchStatus(ctx, req, gitOpsSet.Status); err != nil {
173174
logger.Error(err, "failed to reconcile")
174175
}
175176
return ctrl.Result{}, nil
176177
}
177178

178-
templatesv1.SetGitOpsSetReadiness(&gitOpsSet, metav1.ConditionFalse, templatesv1.ReconciliationFailedReason, err.Error())
179+
templatesv1.SetGitOpsSetReadiness(&gitOpsSet, inventory, metav1.ConditionFalse, templatesv1.ReconciliationFailedReason, err.Error())
179180
if err := r.patchStatus(ctx, req, gitOpsSet.Status); err != nil {
180181
logger.Error(err, "failed to reconcile")
181182
}
@@ -186,14 +187,15 @@ func (r *GitOpsSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
186187
}
187188

188189
if inventory != nil {
189-
templatesv1.SetReadyWithInventory(&gitOpsSet, inventory, templatesv1.ReconciliationSucceededReason,
190+
templatesv1.SetGitOpsSetReadiness(&gitOpsSet, inventory, metav1.ConditionTrue, templatesv1.ReconciliationSucceededReason,
190191
fmt.Sprintf("%d resources created", len(inventory.Entries)))
191192

192193
if err := r.patchStatus(ctx, req, gitOpsSet.Status); err != nil {
193-
templatesv1.SetGitOpsSetReadiness(&gitOpsSet, metav1.ConditionFalse, templatesv1.ReconciliationFailedReason, err.Error())
194194
logger.Error(err, "failed to reconcile")
195+
templatesv1.SetGitOpsSetReadiness(&gitOpsSet, inventory, metav1.ConditionFalse, templatesv1.ReconciliationFailedReason, err.Error())
195196
msg := "Status and inventory update failed after reconciliation"
196197
r.event(&gitOpsSet, eventv1.EventSeverityError, msg)
198+
197199
return ctrl.Result{}, fmt.Errorf("failed to update status and inventory: %w", err)
198200
}
199201
}
@@ -210,12 +212,12 @@ func (r *GitOpsSetReconciler) reconcileResources(ctx context.Context, k8sClient
210212

211213
inventory, err := r.renderAndReconcile(ctx, logger, k8sClient, gitOpsSet, instantiatedGenerators)
212214
if err != nil {
213-
return nil, generators.NoRequeueInterval, err
215+
return inventory, generators.NoRequeueInterval, err
214216
}
215217

216218
requeueAfter, err := calculateInterval(gitOpsSet, instantiatedGenerators)
217219
if err != nil {
218-
return nil, generators.NoRequeueInterval, fmt.Errorf("failed to calculate requeue interval: %w", err)
220+
return inventory, generators.NoRequeueInterval, fmt.Errorf("failed to calculate requeue interval: %w", err)
219221
}
220222

221223
return inventory, requeueAfter, nil
@@ -228,6 +230,8 @@ func (r *GitOpsSetReconciler) renderAndReconcile(ctx context.Context, logger log
228230
}
229231
logger.Info("rendered templates", "resourceCount", len(resources))
230232

233+
var inventoryErr error
234+
231235
existingEntries := sets.New[templatesv1.ResourceRef]()
232236
if gitOpsSet.Status.Inventory != nil {
233237
existingEntries.Insert(gitOpsSet.Status.Inventory.Entries...)
@@ -237,52 +241,65 @@ func (r *GitOpsSetReconciler) renderAndReconcile(ctx context.Context, logger log
237241
for _, newResource := range resources {
238242
ref, err := templatesv1.ResourceRefFromObject(newResource)
239243
if err != nil {
240-
return nil, fmt.Errorf("failed to update inventory: %w", err)
244+
inventoryErr = errors.Join(inventoryErr, fmt.Errorf("failed to update inventory: %w", err))
245+
continue
241246
}
242-
entries.Insert(ref)
243247

244248
if existingEntries.Has(ref) {
245249
existing, err := unstructuredFromResourceRef(ref)
246250
if err != nil {
247-
return nil, fmt.Errorf("failed to convert resource for update: %w", err)
251+
inventoryErr = errors.Join(inventoryErr, fmt.Errorf("failed to convert resource for update: %w", err))
252+
continue
248253
}
254+
// We can add the entry because we know it exists
255+
entries.Insert(ref)
249256
err = k8sClient.Get(ctx, client.ObjectKeyFromObject(newResource), existing)
250257
if err == nil {
251258
newResource = copyUnstructuredContent(existing, newResource)
252259
if err := k8sClient.Patch(ctx, newResource, client.MergeFrom(existing)); err != nil {
253-
return nil, fmt.Errorf("failed to update Resource: %w", err)
260+
inventoryErr = errors.Join(inventoryErr, fmt.Errorf("failed to update Resource: %w", err))
254261
}
255262
continue
256263
}
257264

258265
if !apierrors.IsNotFound(err) {
259-
return nil, fmt.Errorf("failed to load existing Resource: %w", err)
266+
inventoryErr = errors.Join(inventoryErr, fmt.Errorf("failed to load existing Resource: %w", err))
267+
continue
260268
}
261269
}
262270

263271
if err := logResourceMessage(logger, "creating new resource", newResource); err != nil {
264-
return nil, err
272+
inventoryErr = errors.Join(inventoryErr, err)
273+
continue
265274
}
266275

267276
if err := k8sClient.Create(ctx, newResource); err != nil {
268-
return nil, fmt.Errorf("failed to create Resource: %w", err)
277+
inventoryErr = errors.Join(inventoryErr, fmt.Errorf("failed to create Resource: %w", err))
278+
if apierrors.IsAlreadyExists(err) {
279+
if err := logResourceMessage(logger, "resource already exists", newResource); err != nil {
280+
inventoryErr = errors.Join(inventoryErr, err)
281+
}
282+
}
283+
continue
269284
}
285+
286+
entries.Insert(ref)
270287
}
271288

272289
if gitOpsSet.Status.Inventory == nil {
273290
return &templatesv1.ResourceInventory{Entries: entries.SortedList(func(x, y templatesv1.ResourceRef) bool {
274291
return x.ID < y.ID
275-
})}, nil
292+
})}, inventoryErr
276293

277294
}
278295
objectsToRemove := existingEntries.Difference(entries)
279296
if err := r.removeResourceRefs(ctx, k8sClient, objectsToRemove.List()); err != nil {
280-
return nil, err
297+
inventoryErr = errors.Join(inventoryErr, err)
281298
}
282299

283300
return &templatesv1.ResourceInventory{Entries: entries.SortedList(func(x, y templatesv1.ResourceRef) bool {
284301
return x.ID < y.ID
285-
})}, nil
302+
})}, inventoryErr
286303
}
287304

288305
func (r *GitOpsSetReconciler) patchStatus(ctx context.Context, req ctrl.Request, newStatus templatesv1.GitOpsSetStatus) error {

controllers/gitopsset_controller_test.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -653,9 +653,7 @@ func TestReconciliation(t *testing.T) {
653653
_, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(gs)})
654654
test.AssertErrorMatch(t, `update Resource: kustomizations.* is forbidden: User "system:serviceaccount:default:test-sa"`, err)
655655

656-
_, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(gs)})
657-
test.AssertErrorMatch(t, `update Resource: kustomizations.* is forbidden: User "system:serviceaccount:default:test-sa"`, err)
658-
656+
// Switch the permissions to allow updating.
659657
var role rbacv1.Role
660658
if err := k8sClient.Get(ctx, client.ObjectKey{Name: "test-role", Namespace: "default"}, &role); err != nil {
661659
t.Fatal(err)

pkg/cmd/command.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,12 @@ func outputResources(out io.Writer, resources []*unstructured.Unstructured) erro
9595
func marshalOutput(out io.Writer, obj runtime.Object) error {
9696
data, err := yaml.Marshal(obj)
9797
if err != nil {
98-
return fmt.Errorf("failed to marshal data: %v", err)
98+
return fmt.Errorf("failed to marshal data: %w", err)
9999
}
100100

101101
_, err = fmt.Fprintf(out, "%s", data)
102102
if err != nil {
103-
return fmt.Errorf("failed to write data: %v", err)
103+
return fmt.Errorf("failed to write data: %w", err)
104104
}
105105

106106
return nil

0 commit comments

Comments
 (0)