Skip to content

Commit 20de234

Browse files
committed
revert updates to repository
Signed-off-by: Chetan Banavikalmutt <[email protected]> Assisted-by: Cursor
1 parent d03ef99 commit 20de234

File tree

13 files changed

+453
-204
lines changed

13 files changed

+453
-204
lines changed

agent/inbound.go

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -528,23 +528,24 @@ func (a *Agent) deleteApplication(app *v1alpha1.Application) error {
528528

529529
logCtx.Infof("Deleting application")
530530

531+
if a.mode == types.AgentModeManaged {
532+
a.sourceCache.Application.Delete(app.UID)
533+
}
534+
531535
deletionPropagation := backend.DeletePropagationBackground
532536
err := a.appManager.Delete(a.context, a.namespace, app, &deletionPropagation)
533537
if err != nil {
534538
if apierrors.IsNotFound(err) {
535539
logCtx.Debug("application is not found, perhaps it is already deleted")
536-
if a.mode == types.AgentModeManaged {
537-
a.sourceCache.Application.Delete(app.UID)
538-
}
539540
return nil
540541
}
542+
// Restore the cache if the deletion fails
543+
if a.mode == types.AgentModeManaged {
544+
a.sourceCache.Application.Set(app.UID, app.Spec)
545+
}
541546
return err
542547
}
543548

544-
if a.mode == types.AgentModeManaged {
545-
a.sourceCache.Application.Delete(app.UID)
546-
}
547-
548549
err = a.appManager.Unmanage(app.QualifiedName())
549550
if err != nil {
550551
log().Warnf("Could not unmanage app %s: %v", app.QualifiedName(), err)
@@ -635,6 +636,8 @@ func (a *Agent) deleteAppProject(project *v1alpha1.AppProject) error {
635636

636637
logCtx.Infof("Deleting appProject")
637638

639+
a.sourceCache.AppProject.Delete(project.UID)
640+
638641
deletionPropagation := backend.DeletePropagationBackground
639642
err := a.projectManager.Delete(a.context, project, &deletionPropagation)
640643
if err != nil {
@@ -643,11 +646,11 @@ func (a *Agent) deleteAppProject(project *v1alpha1.AppProject) error {
643646
a.sourceCache.AppProject.Delete(project.UID)
644647
return nil
645648
}
649+
// Restore the cache if the deletion fails
650+
a.sourceCache.AppProject.Set(project.UID, project.Spec)
646651
return err
647652
}
648653

649-
a.sourceCache.AppProject.Delete(project.UID)
650-
651654
err = a.projectManager.Unmanage(project.Name)
652655
if err != nil {
653656
log().Warnf("Could not unmanage appProject %s: %v", project.Name, err)
@@ -682,6 +685,8 @@ func (a *Agent) createRepository(incoming *corev1.Secret) (*corev1.Secret, error
682685
incoming.Annotations = make(map[string]string)
683686
}
684687

688+
a.sourceCache.Repository.Set(incoming.UID, incoming.Data)
689+
685690
// Get rid of some fields that we do not want to have on the repository as we start fresh.
686691
delete(incoming.Annotations, "kubectl.kubernetes.io/last-applied-configuration")
687692

@@ -716,6 +721,8 @@ func (a *Agent) updateRepository(incoming *corev1.Secret) (*corev1.Secret, error
716721

717722
logCtx.Infof("Updating repository")
718723

724+
a.sourceCache.Repository.Set(incoming.UID, incoming.Data)
725+
719726
return a.repoManager.UpdateManagedRepository(a.context, incoming)
720727
}
721728

@@ -732,13 +739,17 @@ func (a *Agent) deleteRepository(repo *corev1.Secret) error {
732739

733740
logCtx.Infof("Deleting repository")
734741

742+
a.sourceCache.Repository.Delete(repo.UID)
743+
735744
deletionPropagation := backend.DeletePropagationBackground
736745
err := a.repoManager.Delete(a.context, repo.Name, repo.Namespace, &deletionPropagation)
737746
if err != nil {
738747
if apierrors.IsNotFound(err) {
739748
logCtx.Debug("repository is not found, perhaps it is already deleted")
740749
return nil
741750
}
751+
// Restore the cache if the deletion fails
752+
a.sourceCache.Repository.Set(repo.UID, repo.Data)
742753
return err
743754
}
744755

agent/outbound.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ import (
1919

2020
"github.com/argoproj-labs/argocd-agent/internal/event"
2121
"github.com/argoproj-labs/argocd-agent/internal/logging/logfields"
22+
"github.com/argoproj-labs/argocd-agent/internal/manager"
2223
"github.com/argoproj-labs/argocd-agent/internal/resources"
2324
"github.com/argoproj-labs/argocd-agent/pkg/types"
2425
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
2526
cacheutil "github.com/argoproj/argo-cd/v3/util/cache"
2627
"github.com/sirupsen/logrus"
2728
corev1 "k8s.io/api/core/v1"
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2830
)
2931

3032
// addAppCreationToQueue processes a new application event originating from the
@@ -116,6 +118,13 @@ func (a *Agent) addAppDeletionToQueue(app *v1alpha1.Application) {
116118
logCtx := log().WithField(logfields.Event, "DeleteApp").WithField(logfields.Application, app.QualifiedName())
117119
logCtx.Debugf("Delete app event")
118120

121+
if isResourceFromPrincipal(app) {
122+
if manager.RevertUserInitiatedDeletion(a.context, app, a.sourceCache.Application, a.appManager, logCtx) {
123+
logCtx.Trace("Deleted app is recreated")
124+
return
125+
}
126+
}
127+
119128
a.resources.Remove(resources.NewResourceKeyFromApp(app))
120129

121130
if !a.appManager.IsManaged(app.QualifiedName()) {
@@ -248,6 +257,13 @@ func (a *Agent) addAppProjectDeletionToQueue(appProject *v1alpha1.AppProject) {
248257

249258
logCtx.Debugf("Delete appProject event")
250259

260+
if isResourceFromPrincipal(appProject) {
261+
if manager.RevertUserInitiatedDeletion(a.context, appProject, a.sourceCache.AppProject, a.projectManager, logCtx) {
262+
logCtx.Trace("Deleted appProject is recreated")
263+
return
264+
}
265+
}
266+
251267
a.resources.Remove(resources.NewResourceKeyFromAppProject(appProject))
252268

253269
// Only send the deletion event when we're in autonomous mode
@@ -347,6 +363,11 @@ func (a *Agent) handleRepositoryUpdate(old, new *corev1.Secret) {
347363
a.watchLock.Lock()
348364
defer a.watchLock.Unlock()
349365

366+
if a.repoManager.RevertRepositoryChanges(a.context, new, a.sourceCache.Repository) {
367+
logCtx.Debugf("Modifications done to repository are reverted")
368+
return
369+
}
370+
350371
if a.mode.IsAutonomous() {
351372
logCtx.Debugf("Skipping repository event because the agent is not in managed mode")
352373
return
@@ -371,6 +392,11 @@ func (a *Agent) handleRepositoryDeletion(repo *corev1.Secret) {
371392

372393
logCtx.Debugf("Delete repository event")
373394

395+
if manager.RevertUserInitiatedDeletion(a.context, repo, a.sourceCache.Repository, a.repoManager, logCtx) {
396+
logCtx.Trace("Deleted repository is recreated")
397+
return
398+
}
399+
374400
if a.mode.IsAutonomous() {
375401
logCtx.Debugf("Skipping repository event because the agent is not in managed mode")
376402
return
@@ -388,3 +414,14 @@ func (a *Agent) handleRepositoryDeletion(repo *corev1.Secret) {
388414
return
389415
}
390416
}
417+
418+
// isResourceFromPrincipal checks if a Kubernetes resource was created by the principal
419+
// by examining if it has the source UID annotation.
420+
func isResourceFromPrincipal(resource metav1.Object) bool {
421+
annotations := resource.GetAnnotations()
422+
if annotations == nil {
423+
return false
424+
}
425+
_, ok := annotations[manager.SourceUIDAnnotation]
426+
return ok
427+
}

internal/cache/resource_cache.go

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,15 @@ func (c *ResourceCache[T]) Get(sourceUID types.UID) (T, bool) {
7777
return item, ok
7878
}
7979

80+
// Contains checks if a resource is in the cache
81+
func (c *ResourceCache[T]) Contains(sourceUID types.UID) bool {
82+
c.lock.RLock()
83+
defer c.lock.RUnlock()
84+
85+
_, ok := c.items[sourceUID]
86+
return ok
87+
}
88+
8089
// Delete removes a resource from the cache
8190
func (c *ResourceCache[T]) Delete(sourceUID types.UID) {
8291
c.lock.Lock()
@@ -103,27 +112,3 @@ func (c *ResourceCache[T]) Len() int {
103112
defer c.lock.RUnlock()
104113
return len(c.items)
105114
}
106-
107-
// Keys returns all keys in the cache
108-
func (c *ResourceCache[T]) Keys() []types.UID {
109-
c.lock.RLock()
110-
defer c.lock.RUnlock()
111-
112-
keys := make([]types.UID, 0, len(c.items))
113-
for key := range c.items {
114-
keys = append(keys, key)
115-
}
116-
return keys
117-
}
118-
119-
// ForEach iterates over all items in the cache
120-
func (c *ResourceCache[T]) ForEach(fn func(types.UID, T) bool) {
121-
c.lock.RLock()
122-
defer c.lock.RUnlock()
123-
124-
for key, value := range c.items {
125-
if !fn(key, value) {
126-
break
127-
}
128-
}
129-
}

internal/manager/appproject/appproject.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,8 +392,6 @@ func (m *AppProjectManager) RevertAppProjectChanges(ctx context.Context, project
392392
}
393393
return true
394394
} else {
395-
logCtx.Trace(cachedSpec)
396-
logCtx.Trace(project.Spec)
397395
logCtx.Debugf("AppProject %s is already in sync with source cache", project.Name)
398396
}
399397
} else {

internal/manager/manager.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,14 @@
1515
package manager
1616

1717
import (
18+
"context"
1819
"fmt"
1920
"sync"
21+
22+
"github.com/sirupsen/logrus"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
"k8s.io/apimachinery/pkg/runtime"
25+
"k8s.io/apimachinery/pkg/types"
2026
)
2127

2228
type ManagerRole int
@@ -187,3 +193,59 @@ func (o *ObservedResources) Len() int {
187193
defer o.mu.RUnlock()
188194
return len(o.observed)
189195
}
196+
197+
type kubeResource interface {
198+
runtime.Object
199+
metav1.Object
200+
}
201+
202+
type resourceCache[R kubeResource] interface {
203+
Contains(uid types.UID) bool
204+
}
205+
206+
type resourceManager[R kubeResource] interface {
207+
Create(ctx context.Context, obj R) (R, error)
208+
}
209+
210+
// RevertUserInitiatedDeletion detects if a resource deletion was unauthorized and recreates the resource.
211+
// Returns true if the resource was recreated, false otherwise.
212+
func RevertUserInitiatedDeletion[R kubeResource](ctx context.Context,
213+
outbound R,
214+
resCache resourceCache[R],
215+
mgr resourceManager[R],
216+
logCtx *logrus.Entry,
217+
) bool {
218+
219+
logCtx = logCtx.WithFields(logrus.Fields{
220+
"resource": outbound.GetName(),
221+
"kind": outbound.GetObjectKind().GroupVersionKind().Kind,
222+
})
223+
224+
sourceUID, exists := outbound.GetAnnotations()[SourceUIDAnnotation]
225+
if !exists {
226+
logCtx.Errorf("Source UID annotation not found for resource")
227+
return false
228+
}
229+
230+
// If the resource is not in the cache, it means it was deleted by an incoming delete event.
231+
// So no need to recreate it.
232+
if !resCache.Contains(types.UID(sourceUID)) {
233+
logCtx.Debugf("Expected deletion detected - allowing it to proceed")
234+
return false
235+
}
236+
237+
logCtx.Warnf("Unauthorized deletion detected - recreating")
238+
// This is an unauthorized deletion (user-initiated), recreate the resource
239+
resource := outbound.DeepCopyObject().(R)
240+
resource.SetResourceVersion("")
241+
resource.SetDeletionTimestamp(nil)
242+
resource.SetUID(types.UID(sourceUID))
243+
_, err := mgr.Create(ctx, resource)
244+
if err != nil {
245+
logCtx.WithError(err).Error("failed to recreate resource after unauthorized deletion")
246+
} else {
247+
logCtx.Infof("Recreated resource after unauthorized deletion")
248+
}
249+
250+
return true
251+
}

0 commit comments

Comments
 (0)