diff --git a/api/dataservices/settings/settings.go b/api/dataservices/settings/settings.go index 19e89ed33..cb991d00c 100644 --- a/api/dataservices/settings/settings.go +++ b/api/dataservices/settings/settings.go @@ -31,6 +31,13 @@ func NewService(connection portainer.Connection) (*Service, error) { }, nil } +func (service *Service) Tx(tx portainer.Transaction) ServiceTx { + return ServiceTx{ + service: service, + tx: tx, + } +} + // Settings retrieve the settings object. func (service *Service) Settings() (*portainer.Settings, error) { var settings portainer.Settings diff --git a/api/dataservices/settings/tx.go b/api/dataservices/settings/tx.go new file mode 100644 index 000000000..7e6899dec --- /dev/null +++ b/api/dataservices/settings/tx.go @@ -0,0 +1,31 @@ +package settings + +import ( + portainer "github.com/portainer/portainer/api" +) + +type ServiceTx struct { + service *Service + tx portainer.Transaction +} + +func (service ServiceTx) BucketName() string { + return BucketName +} + +// Settings retrieve the settings object. +func (service ServiceTx) Settings() (*portainer.Settings, error) { + var settings portainer.Settings + + err := service.tx.GetObject(BucketName, []byte(settingsKey), &settings) + if err != nil { + return nil, err + } + + return &settings, nil +} + +// UpdateSettings persists a Settings object. +func (service ServiceTx) UpdateSettings(settings *portainer.Settings) error { + return service.tx.UpdateObject(BucketName, []byte(settingsKey), settings) +} diff --git a/api/datastore/services_tx.go b/api/datastore/services_tx.go index e58f5953e..dcedecec7 100644 --- a/api/datastore/services_tx.go +++ b/api/datastore/services_tx.go @@ -44,7 +44,7 @@ func (tx *StoreTx) FDOProfile() dataservices.FDOProfileService { func (tx *StoreTx) HelmUserRepository() dataservices.HelmUserRepositoryService { return nil } func (tx *StoreTx) Registry() dataservices.RegistryService { - return nil + return tx.store.RegistryService.Tx(tx.tx) } func (tx *StoreTx) ResourceControl() dataservices.ResourceControlService { @@ -56,7 +56,10 @@ func (tx *StoreTx) Role() dataservices.RoleService { } func (tx *StoreTx) APIKeyRepository() dataservices.APIKeyRepository { return nil } -func (tx *StoreTx) Settings() dataservices.SettingsService { return nil } + +func (tx *StoreTx) Settings() dataservices.SettingsService { + return tx.store.SettingsService.Tx(tx.tx) +} func (tx *StoreTx) Snapshot() dataservices.SnapshotService { return tx.store.SnapshotService.Tx(tx.tx) diff --git a/api/http/handler/endpointedge/endpoint_edgejob_logs.go b/api/http/handler/endpointedge/endpoint_edgejob_logs.go index e752443d4..48d85bf05 100644 --- a/api/http/handler/endpointedge/endpoint_edgejob_logs.go +++ b/api/http/handler/endpointedge/endpoint_edgejob_logs.go @@ -1,6 +1,7 @@ package endpointedge import ( + "errors" "net/http" "strconv" @@ -8,7 +9,9 @@ import ( "github.com/portainer/libhttp/request" "github.com/portainer/libhttp/response" portainer "github.com/portainer/portainer/api" + "github.com/portainer/portainer/api/dataservices" "github.com/portainer/portainer/api/http/middlewares" + "github.com/portainer/portainer/pkg/featureflags" ) type logsPayload struct { @@ -53,14 +56,42 @@ func (handler *Handler) endpointEdgeJobsLogs(w http.ResponseWriter, r *http.Requ return httperror.BadRequest("Invalid request payload", err) } - edgeJob, err := handler.DataStore.EdgeJob().EdgeJob(portainer.EdgeJobID(edgeJobID)) - if handler.DataStore.IsErrObjectNotFound(err) { + if featureflags.IsEnabled(portainer.FeatureNoTx) { + err = handler.getEdgeJobLobs(handler.DataStore, endpoint.ID, portainer.EdgeJobID(edgeJobID), payload) + } else { + err = handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error { + return handler.getEdgeJobLobs(tx, endpoint.ID, portainer.EdgeJobID(edgeJobID), payload) + }) + } + + if err != nil { + var httpErr *httperror.HandlerError + if errors.As(err, &httpErr) { + return httpErr + } + + return httperror.InternalServerError("Unexpected error", err) + } + + return response.JSON(w, nil) +} + +func (handler *Handler) getEdgeJobLobs(tx dataservices.DataStoreTx, endpointID portainer.EndpointID, edgeJobID portainer.EdgeJobID, payload logsPayload) error { + endpoint, err := tx.Endpoint().Endpoint(endpointID) + if tx.IsErrObjectNotFound(err) { + return httperror.NotFound("Unable to find an environment with the specified identifier inside the database", err) + } else if err != nil { + return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err) + } + + edgeJob, err := tx.EdgeJob().EdgeJob(portainer.EdgeJobID(edgeJobID)) + if tx.IsErrObjectNotFound(err) { return httperror.NotFound("Unable to find an edge job with the specified identifier inside the database", err) } else if err != nil { return httperror.InternalServerError("Unable to find an edge job with the specified identifier inside the database", err) } - err = handler.FileService.StoreEdgeJobTaskLogFileFromBytes(strconv.Itoa(edgeJobID), strconv.Itoa(int(endpoint.ID)), []byte(payload.FileContent)) + err = handler.FileService.StoreEdgeJobTaskLogFileFromBytes(strconv.Itoa(int(edgeJobID)), strconv.Itoa(int(endpointID)), []byte(payload.FileContent)) if err != nil { return httperror.InternalServerError("Unable to save task log to the filesystem", err) } @@ -72,7 +103,7 @@ func (handler *Handler) endpointEdgeJobsLogs(w http.ResponseWriter, r *http.Requ edgeJob.Endpoints[endpoint.ID] = meta } - err = handler.DataStore.EdgeJob().UpdateEdgeJob(edgeJob.ID, edgeJob) + err = tx.EdgeJob().UpdateEdgeJob(edgeJob.ID, edgeJob) handler.ReverseTunnelService.AddEdgeJob(endpoint, edgeJob) @@ -80,5 +111,5 @@ func (handler *Handler) endpointEdgeJobsLogs(w http.ResponseWriter, r *http.Requ return httperror.InternalServerError("Unable to persist edge job changes to the database", err) } - return response.JSON(w, nil) + return nil } diff --git a/api/http/handler/endpointedge/endpoint_edgestatus_inspect.go b/api/http/handler/endpointedge/endpoint_edgestatus_inspect.go index 0c541ae62..76df0159b 100644 --- a/api/http/handler/endpointedge/endpoint_edgestatus_inspect.go +++ b/api/http/handler/endpointedge/endpoint_edgestatus_inspect.go @@ -17,7 +17,9 @@ import ( "github.com/portainer/libhttp/request" "github.com/portainer/libhttp/response" portainer "github.com/portainer/portainer/api" + "github.com/portainer/portainer/api/dataservices" "github.com/portainer/portainer/api/internal/edge/cache" + "github.com/portainer/portainer/pkg/featureflags" ) type stackStatusResponse struct { @@ -96,6 +98,34 @@ func (handler *Handler) endpointEdgeStatusInspect(w http.ResponseWriter, r *http return httperror.Forbidden("Permission denied to access environment", err) } + var statusResponse *endpointEdgeStatusInspectResponse + if featureflags.IsEnabled(portainer.FeatureNoTx) { + statusResponse, err = handler.inspectStatus(handler.DataStore, r, portainer.EndpointID(endpointID)) + } else { + err = handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error { + statusResponse, err = handler.inspectStatus(tx, r, portainer.EndpointID(endpointID)) + return err + }) + } + + if err != nil { + var httpErr *httperror.HandlerError + if errors.As(err, &httpErr) { + return httpErr + } + + return httperror.InternalServerError("Unexpected error", err) + } + + return cacheResponse(w, endpoint.ID, *statusResponse) +} + +func (handler *Handler) inspectStatus(tx dataservices.DataStoreTx, r *http.Request, endpointID portainer.EndpointID) (*endpointEdgeStatusInspectResponse, error) { + endpoint, err := tx.Endpoint().Endpoint(endpointID) + if err != nil { + return nil, err + } + if endpoint.EdgeID == "" { edgeIdentifier := r.Header.Get(portainer.PortainerAgentEdgeIDHeader) endpoint.EdgeID = edgeIdentifier @@ -103,7 +133,7 @@ func (handler *Handler) endpointEdgeStatusInspect(w http.ResponseWriter, r *http agentPlatform, agentPlatformErr := parseAgentPlatform(r) if agentPlatformErr != nil { - return httperror.BadRequest("agent platform header is not valid", err) + return nil, httperror.BadRequest("agent platform header is not valid", err) } endpoint.Type = agentPlatform @@ -112,21 +142,21 @@ func (handler *Handler) endpointEdgeStatusInspect(w http.ResponseWriter, r *http endpoint.LastCheckInDate = time.Now().Unix() - err = handler.DataStore.Endpoint().UpdateEndpoint(endpoint.ID, endpoint) + err = tx.Endpoint().UpdateEndpoint(endpoint.ID, endpoint) if err != nil { - return httperror.InternalServerError("Unable to Unable to persist environment changes inside the database", err) + return nil, httperror.InternalServerError("Unable to Unable to persist environment changes inside the database", err) } - err = handler.requestBouncer.TrustedEdgeEnvironmentAccess(endpoint) + err = handler.requestBouncer.TrustedEdgeEnvironmentAccess(tx, endpoint) if err != nil { - return httperror.Forbidden("Permission denied to access environment", err) + return nil, httperror.Forbidden("Permission denied to access environment", err) } checkinInterval := endpoint.EdgeCheckinInterval if endpoint.EdgeCheckinInterval == 0 { - settings, err := handler.DataStore.Settings().Settings() + settings, err := tx.Settings().Settings() if err != nil { - return httperror.InternalServerError("Unable to retrieve settings from the database", err) + return nil, httperror.InternalServerError("Unable to retrieve settings from the database", err) } checkinInterval = settings.EdgeAgentCheckinInterval } @@ -142,7 +172,7 @@ func (handler *Handler) endpointEdgeStatusInspect(w http.ResponseWriter, r *http schedules, handlerErr := handler.buildSchedules(endpoint.ID, tunnel) if handlerErr != nil { - return handlerErr + return nil, handlerErr } statusResponse.Schedules = schedules @@ -150,13 +180,13 @@ func (handler *Handler) endpointEdgeStatusInspect(w http.ResponseWriter, r *http handler.ReverseTunnelService.SetTunnelStatusToActive(endpoint.ID) } - edgeStacksStatus, handlerErr := handler.buildEdgeStacks(endpoint.ID) + edgeStacksStatus, handlerErr := handler.buildEdgeStacks(tx, endpoint.ID) if handlerErr != nil { - return handlerErr + return nil, handlerErr } statusResponse.Stacks = edgeStacksStatus - return cacheResponse(w, endpoint.ID, statusResponse) + return &statusResponse, nil } func parseAgentPlatform(r *http.Request) (portainer.EndpointType, error) { @@ -210,15 +240,15 @@ func (handler *Handler) buildSchedules(endpointID portainer.EndpointID, tunnel p return schedules, nil } -func (handler *Handler) buildEdgeStacks(endpointID portainer.EndpointID) ([]stackStatusResponse, *httperror.HandlerError) { - relation, err := handler.DataStore.EndpointRelation().EndpointRelation(endpointID) +func (handler *Handler) buildEdgeStacks(tx dataservices.DataStoreTx, endpointID portainer.EndpointID) ([]stackStatusResponse, *httperror.HandlerError) { + relation, err := tx.EndpointRelation().EndpointRelation(endpointID) if err != nil { return nil, httperror.InternalServerError("Unable to retrieve relation object from the database", err) } edgeStacksStatus := []stackStatusResponse{} for stackID := range relation.EdgeStacks { - version, ok := handler.DataStore.EdgeStack().EdgeStackVersion(stackID) + version, ok := tx.EdgeStack().EdgeStackVersion(stackID) if !ok { return nil, httperror.InternalServerError("Unable to retrieve edge stack from the database", err) } diff --git a/api/http/security/bouncer.go b/api/http/security/bouncer.go index 9855c8407..4a10f16f1 100644 --- a/api/http/security/bouncer.go +++ b/api/http/security/bouncer.go @@ -151,12 +151,12 @@ func (bouncer *RequestBouncer) AuthorizedEdgeEndpointOperation(r *http.Request, // TrustedEdgeEnvironmentAccess defines a security check for Edge environments, checks if // the request is coming from a trusted Edge environment -func (bouncer *RequestBouncer) TrustedEdgeEnvironmentAccess(endpoint *portainer.Endpoint) error { +func (bouncer *RequestBouncer) TrustedEdgeEnvironmentAccess(tx dataservices.DataStoreTx, endpoint *portainer.Endpoint) error { if endpoint.UserTrusted { return nil } - settings, err := bouncer.dataStore.Settings().Settings() + settings, err := tx.Settings().Settings() if err != nil { return errors.WithMessage(err, "could not retrieve the settings") }