From d9df58e93a8d6a4dd8d1c72221da1d3ba961730b Mon Sep 17 00:00:00 2001 From: Prabhat Khera <91852476+prabhat-portainer@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:09:10 +1200 Subject: [PATCH] fix(pending-actions): clean pending actions for deleted environment [EE-6545] (#11598) --- api/dataservices/interface.go | 1 + .../pendingactions/pendingactions.go | 31 ++++++++++++++++++ api/datastore/migrate_data.go | 1 + .../migrator/migrate_dbversion111.go | 32 +++++++++++++++++++ api/datastore/migrator/migrator.go | 7 ++++ api/datastore/services_tx.go | 4 ++- .../test_data/output_24_to_latest.json | 2 +- api/http/handler/endpoints/endpoint_delete.go | 6 ++++ 8 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 api/datastore/migrator/migrate_dbversion111.go diff --git a/api/dataservices/interface.go b/api/dataservices/interface.go index 22e233ced..4d51fd66a 100644 --- a/api/dataservices/interface.go +++ b/api/dataservices/interface.go @@ -73,6 +73,7 @@ type ( PendingActionsService interface { BaseCRUD[portainer.PendingActions, portainer.PendingActionsID] GetNextIdentifier() int + DeleteByEndpointID(ID portainer.EndpointID) error } // EdgeStackService represents a service to manage Edge stacks diff --git a/api/dataservices/pendingactions/pendingactions.go b/api/dataservices/pendingactions/pendingactions.go index 5b55a8fd9..25b680899 100644 --- a/api/dataservices/pendingactions/pendingactions.go +++ b/api/dataservices/pendingactions/pendingactions.go @@ -1,10 +1,12 @@ package pendingactions import ( + "fmt" "time" portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/dataservices" + "github.com/rs/zerolog/log" ) const ( @@ -45,6 +47,12 @@ func (s Service) Update(ID portainer.PendingActionsID, config *portainer.Pending }) } +func (s Service) DeleteByEndpointID(ID portainer.EndpointID) error { + return s.Connection.UpdateTx(func(tx portainer.Transaction) error { + return s.Tx(tx).DeleteByEndpointID(ID) + }) +} + func (service *Service) Tx(tx portainer.Transaction) ServiceTx { return ServiceTx{ BaseDataServiceTx: dataservices.BaseDataServiceTx[portainer.PendingActions, portainer.PendingActionsID]{ @@ -68,6 +76,29 @@ func (s ServiceTx) Update(ID portainer.PendingActionsID, config *portainer.Pendi return s.BaseDataServiceTx.Update(ID, config) } +func (s ServiceTx) DeleteByEndpointID(ID portainer.EndpointID) error { + log.Debug().Int("endpointId", int(ID)).Msg("deleting pending actions for endpoint") + pendingActions, err := s.BaseDataServiceTx.ReadAll() + if err != nil { + return fmt.Errorf("failed to retrieve pending-actions for endpoint (%d): %w", ID, err) + } + + for _, pendingAction := range pendingActions { + if pendingAction.EndpointID == ID { + err := s.BaseDataServiceTx.Delete(pendingAction.ID) + if err != nil { + log.Debug().Int("endpointId", int(ID)).Msgf("failed to delete pending action: %v", err) + } + } + } + return nil +} + +// GetNextIdentifier returns the next identifier for a custom template. +func (service ServiceTx) GetNextIdentifier() int { + return service.Tx.GetNextIdentifier(BucketName) +} + // GetNextIdentifier returns the next identifier for a custom template. func (service *Service) GetNextIdentifier() int { return service.Connection.GetNextIdentifier(BucketName) diff --git a/api/datastore/migrate_data.go b/api/datastore/migrate_data.go index f430051e8..2bbd0db95 100644 --- a/api/datastore/migrate_data.go +++ b/api/datastore/migrate_data.go @@ -86,6 +86,7 @@ func (store *Store) newMigratorParameters(version *models.Version) *migrator.Mig EdgeStackService: store.EdgeStackService, EdgeJobService: store.EdgeJobService, TunnelServerService: store.TunnelServerService, + PendingActionsService: store.PendingActionsService, } } diff --git a/api/datastore/migrator/migrate_dbversion111.go b/api/datastore/migrator/migrate_dbversion111.go new file mode 100644 index 000000000..bd7c0d407 --- /dev/null +++ b/api/datastore/migrator/migrate_dbversion111.go @@ -0,0 +1,32 @@ +package migrator + +import ( + portainer "github.com/portainer/portainer/api" + "github.com/portainer/portainer/api/dataservices" + "github.com/rs/zerolog/log" +) + +func (migrator *Migrator) cleanPendingActionsForDeletedEndpointsForDB111() error { + log.Info().Msg("cleaning up pending actions for deleted endpoints") + + pendingActions, err := migrator.pendingActionsService.ReadAll() + if err != nil { + return err + } + + endpoints := make(map[portainer.EndpointID]struct{}) + for _, action := range pendingActions { + endpoints[action.EndpointID] = struct{}{} + } + + for endpointId := range endpoints { + _, err := migrator.endpointService.Endpoint(endpointId) + if dataservices.IsErrObjectNotFound(err) { + err := migrator.pendingActionsService.DeleteByEndpointID(endpointId) + if err != nil { + return err + } + } + } + return nil +} diff --git a/api/datastore/migrator/migrator.go b/api/datastore/migrator/migrator.go index beeb73078..c7b89de4f 100644 --- a/api/datastore/migrator/migrator.go +++ b/api/datastore/migrator/migrator.go @@ -14,6 +14,7 @@ import ( "github.com/portainer/portainer/api/dataservices/endpointrelation" "github.com/portainer/portainer/api/dataservices/extension" "github.com/portainer/portainer/api/dataservices/fdoprofile" + "github.com/portainer/portainer/api/dataservices/pendingactions" "github.com/portainer/portainer/api/dataservices/registry" "github.com/portainer/portainer/api/dataservices/resourcecontrol" "github.com/portainer/portainer/api/dataservices/role" @@ -58,6 +59,7 @@ type ( edgeStackService *edgestack.Service edgeJobService *edgejob.Service TunnelServerService *tunnelserver.Service + pendingActionsService *pendingactions.Service } // MigratorParameters represents the required parameters to create a new Migrator instance. @@ -85,6 +87,7 @@ type ( EdgeStackService *edgestack.Service EdgeJobService *edgejob.Service TunnelServerService *tunnelserver.Service + PendingActionsService *pendingactions.Service } ) @@ -114,6 +117,7 @@ func NewMigrator(parameters *MigratorParameters) *Migrator { edgeStackService: parameters.EdgeStackService, edgeJobService: parameters.EdgeJobService, TunnelServerService: parameters.TunnelServerService, + pendingActionsService: parameters.PendingActionsService, } migrator.initMigrations() @@ -232,6 +236,9 @@ func (m *Migrator) initMigrations() { m.updateAppTemplatesVersionForDB110, m.updateResourceOverCommitToDB110, ) + m.addMigrations("2.22", + m.cleanPendingActionsForDeletedEndpointsForDB111, + ) // Add new migrations below... // One function per migration, each versions migration funcs in the same file. diff --git a/api/datastore/services_tx.go b/api/datastore/services_tx.go index a7a40d6e4..0968d18cc 100644 --- a/api/datastore/services_tx.go +++ b/api/datastore/services_tx.go @@ -16,7 +16,9 @@ func (tx *StoreTx) IsErrObjectNotFound(err error) bool { func (tx *StoreTx) CustomTemplate() dataservices.CustomTemplateService { return nil } -func (tx *StoreTx) PendingActions() dataservices.PendingActionsService { return nil } +func (tx *StoreTx) PendingActions() dataservices.PendingActionsService { + return tx.store.PendingActionsService.Tx(tx.tx) +} func (tx *StoreTx) EdgeGroup() dataservices.EdgeGroupService { return tx.store.EdgeGroupService.Tx(tx.tx) diff --git a/api/datastore/test_data/output_24_to_latest.json b/api/datastore/test_data/output_24_to_latest.json index 2d443bd5b..65f856c5b 100644 --- a/api/datastore/test_data/output_24_to_latest.json +++ b/api/datastore/test_data/output_24_to_latest.json @@ -940,6 +940,6 @@ } ], "version": { - "VERSION": "{\"SchemaVersion\":\"2.22.0\",\"MigratorCount\":0,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}" + "VERSION": "{\"SchemaVersion\":\"2.22.0\",\"MigratorCount\":1,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}" } } \ No newline at end of file diff --git a/api/http/handler/endpoints/endpoint_delete.go b/api/http/handler/endpoints/endpoint_delete.go index 5a71faee1..bcfd77ecb 100644 --- a/api/http/handler/endpoints/endpoint_delete.go +++ b/api/http/handler/endpoints/endpoint_delete.go @@ -179,6 +179,12 @@ func (handler *Handler) deleteEndpoint(tx dataservices.DataStoreTx, endpointID p } } + // delete the pending actions + err = tx.PendingActions().DeleteByEndpointID(endpoint.ID) + if err != nil { + log.Warn().Err(err).Int("endpointId", int(endpoint.ID)).Msgf("Unable to delete pending actions") + } + err = tx.Endpoint().DeleteEndpoint(portainer.EndpointID(endpointID)) if err != nil { return httperror.InternalServerError("Unable to delete the environment from the database", err)