From 95ac2cc4c3cb9c9dc80d46818b354d8d80daf542 Mon Sep 17 00:00:00 2001 From: andres-portainer <91705312+andres-portainer@users.noreply.github.com> Date: Mon, 20 Feb 2023 16:11:18 -0300 Subject: [PATCH] chore(edge): add transaction support for common objects EE-5079 (#8522) --- api/dataservices/edgejob/edgejob.go | 13 +- api/dataservices/edgejob/tx.go | 84 +++++++++ api/dataservices/edgestack/edgestack.go | 13 +- api/dataservices/edgestack/tx.go | 131 +++++++++++++++ .../endpointgroup/endpointgroup.go | 7 + api/dataservices/endpointgroup/tx.go | 76 +++++++++ .../endpointrelation/endpointrelation.go | 13 +- api/dataservices/endpointrelation/tx.go | 159 ++++++++++++++++++ api/dataservices/snapshot/snapshot.go | 7 + api/dataservices/snapshot/tx.go | 63 +++++++ api/dataservices/tag/tag.go | 7 + api/dataservices/tag/tx.go | 82 +++++++++ api/datastore/services_tx.go | 47 ++++-- 13 files changed, 676 insertions(+), 26 deletions(-) create mode 100644 api/dataservices/edgejob/tx.go create mode 100644 api/dataservices/edgestack/tx.go create mode 100644 api/dataservices/endpointgroup/tx.go create mode 100644 api/dataservices/endpointrelation/tx.go create mode 100644 api/dataservices/snapshot/tx.go create mode 100644 api/dataservices/tag/tx.go diff --git a/api/dataservices/edgejob/edgejob.go b/api/dataservices/edgejob/edgejob.go index 48c085fa9..98eb6d1c7 100644 --- a/api/dataservices/edgejob/edgejob.go +++ b/api/dataservices/edgejob/edgejob.go @@ -8,10 +8,8 @@ import ( "github.com/rs/zerolog/log" ) -const ( - // BucketName represents the name of the bucket where this service stores data. - BucketName = "edgejobs" -) +// BucketName represents the name of the bucket where this service stores data. +const BucketName = "edgejobs" // Service represents a service for managing edge jobs data. type Service struct { @@ -34,6 +32,13 @@ func NewService(connection portainer.Connection) (*Service, error) { }, nil } +func (service *Service) Tx(tx portainer.Transaction) ServiceTx { + return ServiceTx{ + service: service, + tx: tx, + } +} + // EdgeJobs returns a list of Edge jobs func (service *Service) EdgeJobs() ([]portainer.EdgeJob, error) { var edgeJobs = make([]portainer.EdgeJob, 0) diff --git a/api/dataservices/edgejob/tx.go b/api/dataservices/edgejob/tx.go new file mode 100644 index 000000000..91c704bd9 --- /dev/null +++ b/api/dataservices/edgejob/tx.go @@ -0,0 +1,84 @@ +package edgejob + +import ( + "errors" + "fmt" + + portainer "github.com/portainer/portainer/api" + + "github.com/rs/zerolog/log" +) + +type ServiceTx struct { + service *Service + tx portainer.Transaction +} + +func (service ServiceTx) BucketName() string { + return BucketName +} + +// EdgeJobs returns a list of Edge jobs +func (service ServiceTx) EdgeJobs() ([]portainer.EdgeJob, error) { + var edgeJobs = make([]portainer.EdgeJob, 0) + + err := service.tx.GetAll( + BucketName, + &portainer.EdgeJob{}, + func(obj interface{}) (interface{}, error) { + job, ok := obj.(*portainer.EdgeJob) + if !ok { + log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeJob object") + return nil, fmt.Errorf("failed to convert to EdgeJob object: %s", obj) + } + + edgeJobs = append(edgeJobs, *job) + + return &portainer.EdgeJob{}, nil + }) + + return edgeJobs, err +} + +// EdgeJob returns an Edge job by ID +func (service ServiceTx) EdgeJob(ID portainer.EdgeJobID) (*portainer.EdgeJob, error) { + var edgeJob portainer.EdgeJob + identifier := service.service.connection.ConvertToKey(int(ID)) + + err := service.tx.GetObject(BucketName, identifier, &edgeJob) + if err != nil { + return nil, err + } + + return &edgeJob, nil +} + +// Create creates a new EdgeJob +func (service ServiceTx) Create(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error { + edgeJob.ID = ID + + return service.tx.CreateObjectWithId(BucketName, int(edgeJob.ID), edgeJob) +} + +// UpdateEdgeJob updates an edge job +func (service ServiceTx) UpdateEdgeJob(ID portainer.EdgeJobID, edgeJob *portainer.EdgeJob) error { + identifier := service.service.connection.ConvertToKey(int(ID)) + return service.tx.UpdateObject(BucketName, identifier, edgeJob) +} + +// UpdateEdgeJobFunc is a no-op inside a transaction. +func (service ServiceTx) UpdateEdgeJobFunc(ID portainer.EdgeJobID, updateFunc func(edgeJob *portainer.EdgeJob)) error { + return errors.New("cannot be called inside a transaction") +} + +// DeleteEdgeJob deletes an Edge job +func (service ServiceTx) DeleteEdgeJob(ID portainer.EdgeJobID) error { + identifier := service.service.connection.ConvertToKey(int(ID)) + + return service.tx.DeleteObject(BucketName, identifier) +} + +// GetNextIdentifier returns the next identifier for an environment(endpoint). +func (service ServiceTx) GetNextIdentifier() int { + return service.tx.GetNextIdentifier(BucketName) +} diff --git a/api/dataservices/edgestack/edgestack.go b/api/dataservices/edgestack/edgestack.go index fb434f388..c4a66c51b 100644 --- a/api/dataservices/edgestack/edgestack.go +++ b/api/dataservices/edgestack/edgestack.go @@ -9,10 +9,8 @@ import ( "github.com/rs/zerolog/log" ) -const ( - // BucketName represents the name of the bucket where this service stores data. - BucketName = "edge_stack" -) +// BucketName represents the name of the bucket where this service stores data. +const BucketName = "edge_stack" // Service represents a service for managing Edge stack data. type Service struct { @@ -55,6 +53,13 @@ func NewService(connection portainer.Connection, cacheInvalidationFn func(portai return s, nil } +func (service *Service) Tx(tx portainer.Transaction) ServiceTx { + return ServiceTx{ + service: service, + tx: tx, + } +} + // EdgeStacks returns an array containing all edge stacks func (service *Service) EdgeStacks() ([]portainer.EdgeStack, error) { var stacks = make([]portainer.EdgeStack, 0) diff --git a/api/dataservices/edgestack/tx.go b/api/dataservices/edgestack/tx.go new file mode 100644 index 000000000..53640eea1 --- /dev/null +++ b/api/dataservices/edgestack/tx.go @@ -0,0 +1,131 @@ +package edgestack + +import ( + "errors" + "fmt" + + portainer "github.com/portainer/portainer/api" + + "github.com/rs/zerolog/log" +) + +type ServiceTx struct { + service *Service + tx portainer.Transaction +} + +func (service ServiceTx) BucketName() string { + return BucketName +} + +// EdgeStacks returns an array containing all edge stacks +func (service ServiceTx) EdgeStacks() ([]portainer.EdgeStack, error) { + var stacks = make([]portainer.EdgeStack, 0) + + err := service.tx.GetAll( + BucketName, + &portainer.EdgeStack{}, + func(obj interface{}) (interface{}, error) { + stack, ok := obj.(*portainer.EdgeStack) + if !ok { + log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EdgeStack object") + return nil, fmt.Errorf("Failed to convert to EdgeStack object: %s", obj) + } + + stacks = append(stacks, *stack) + + return &portainer.EdgeStack{}, nil + }) + + return stacks, err +} + +// EdgeStack returns an Edge stack by ID. +func (service ServiceTx) EdgeStack(ID portainer.EdgeStackID) (*portainer.EdgeStack, error) { + var stack portainer.EdgeStack + identifier := service.service.connection.ConvertToKey(int(ID)) + + err := service.tx.GetObject(BucketName, identifier, &stack) + if err != nil { + return nil, err + } + + return &stack, nil +} + +// EdgeStackVersion returns the version of the given edge stack ID directly from an in-memory index +func (service ServiceTx) EdgeStackVersion(ID portainer.EdgeStackID) (int, bool) { + service.service.mu.RLock() + v, ok := service.service.idxVersion[ID] + service.service.mu.RUnlock() + + return v, ok +} + +// CreateEdgeStack saves an Edge stack object to db. +func (service ServiceTx) Create(id portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error { + edgeStack.ID = id + + err := service.tx.CreateObjectWithId( + BucketName, + int(edgeStack.ID), + edgeStack, + ) + if err != nil { + return err + } + + service.service.mu.Lock() + service.service.idxVersion[id] = edgeStack.Version + service.service.cacheInvalidationFn(id) + service.service.mu.Unlock() + + return nil +} + +// UpdateEdgeStack updates an Edge stack. +func (service ServiceTx) UpdateEdgeStack(ID portainer.EdgeStackID, edgeStack *portainer.EdgeStack) error { + service.service.mu.Lock() + defer service.service.mu.Unlock() + + identifier := service.service.connection.ConvertToKey(int(ID)) + + err := service.tx.UpdateObject(BucketName, identifier, edgeStack) + if err != nil { + return err + } + + service.service.idxVersion[ID] = edgeStack.Version + service.service.cacheInvalidationFn(ID) + + return nil +} + +// UpdateEdgeStackFunc is a no-op inside a transaction. +func (service ServiceTx) UpdateEdgeStackFunc(ID portainer.EdgeStackID, updateFunc func(edgeStack *portainer.EdgeStack)) error { + return errors.New("cannot be called inside a transaction") +} + +// DeleteEdgeStack deletes an Edge stack. +func (service ServiceTx) DeleteEdgeStack(ID portainer.EdgeStackID) error { + service.service.mu.Lock() + defer service.service.mu.Unlock() + + identifier := service.service.connection.ConvertToKey(int(ID)) + + err := service.tx.DeleteObject(BucketName, identifier) + if err != nil { + return err + } + + delete(service.service.idxVersion, ID) + + service.service.cacheInvalidationFn(ID) + + return nil +} + +// GetNextIdentifier returns the next identifier for an environment(endpoint). +func (service ServiceTx) GetNextIdentifier() int { + return service.tx.GetNextIdentifier(BucketName) +} diff --git a/api/dataservices/endpointgroup/endpointgroup.go b/api/dataservices/endpointgroup/endpointgroup.go index 6b4c4692d..4b62488ed 100644 --- a/api/dataservices/endpointgroup/endpointgroup.go +++ b/api/dataservices/endpointgroup/endpointgroup.go @@ -34,6 +34,13 @@ func NewService(connection portainer.Connection) (*Service, error) { }, nil } +func (service *Service) Tx(tx portainer.Transaction) ServiceTx { + return ServiceTx{ + service: service, + tx: tx, + } +} + // EndpointGroup returns an environment(endpoint) group by ID. func (service *Service) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.EndpointGroup, error) { var endpointGroup portainer.EndpointGroup diff --git a/api/dataservices/endpointgroup/tx.go b/api/dataservices/endpointgroup/tx.go new file mode 100644 index 000000000..b095c7646 --- /dev/null +++ b/api/dataservices/endpointgroup/tx.go @@ -0,0 +1,76 @@ +package endpointgroup + +import ( + "fmt" + + portainer "github.com/portainer/portainer/api" + + "github.com/rs/zerolog/log" +) + +type ServiceTx struct { + service *Service + tx portainer.Transaction +} + +func (service ServiceTx) BucketName() string { + return BucketName +} + +// EndpointGroup returns an environment(endpoint) group by ID. +func (service ServiceTx) EndpointGroup(ID portainer.EndpointGroupID) (*portainer.EndpointGroup, error) { + var endpointGroup portainer.EndpointGroup + identifier := service.service.connection.ConvertToKey(int(ID)) + + err := service.tx.GetObject(BucketName, identifier, &endpointGroup) + if err != nil { + return nil, err + } + + return &endpointGroup, nil +} + +// UpdateEndpointGroup updates an environment(endpoint) group. +func (service ServiceTx) UpdateEndpointGroup(ID portainer.EndpointGroupID, endpointGroup *portainer.EndpointGroup) error { + identifier := service.service.connection.ConvertToKey(int(ID)) + return service.tx.UpdateObject(BucketName, identifier, endpointGroup) +} + +// DeleteEndpointGroup deletes an environment(endpoint) group. +func (service ServiceTx) DeleteEndpointGroup(ID portainer.EndpointGroupID) error { + identifier := service.service.connection.ConvertToKey(int(ID)) + return service.tx.DeleteObject(BucketName, identifier) +} + +// EndpointGroups return an array containing all the environment(endpoint) groups. +func (service ServiceTx) EndpointGroups() ([]portainer.EndpointGroup, error) { + var endpointGroups = make([]portainer.EndpointGroup, 0) + + err := service.tx.GetAll( + BucketName, + &portainer.EndpointGroup{}, + func(obj interface{}) (interface{}, error) { + endpointGroup, ok := obj.(*portainer.EndpointGroup) + if !ok { + log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EndpointGroup object") + return nil, fmt.Errorf("failed to convert to EndpointGroup object: %s", obj) + } + + endpointGroups = append(endpointGroups, *endpointGroup) + + return &portainer.EndpointGroup{}, nil + }) + + return endpointGroups, err +} + +// CreateEndpointGroup assign an ID to a new environment(endpoint) group and saves it. +func (service ServiceTx) Create(endpointGroup *portainer.EndpointGroup) error { + return service.tx.CreateObject( + BucketName, + func(id uint64) (int, interface{}) { + endpointGroup.ID = portainer.EndpointGroupID(id) + return int(endpointGroup.ID), endpointGroup + }, + ) +} diff --git a/api/dataservices/endpointrelation/endpointrelation.go b/api/dataservices/endpointrelation/endpointrelation.go index eb7089e2b..b8ecfaa4b 100644 --- a/api/dataservices/endpointrelation/endpointrelation.go +++ b/api/dataservices/endpointrelation/endpointrelation.go @@ -9,10 +9,8 @@ import ( "github.com/rs/zerolog/log" ) -const ( - // BucketName represents the name of the bucket where this service stores data. - BucketName = "endpoint_relations" -) +// BucketName represents the name of the bucket where this service stores data. +const BucketName = "endpoint_relations" // Service represents a service for managing environment(endpoint) relation data. type Service struct { @@ -40,6 +38,13 @@ func NewService(connection portainer.Connection) (*Service, error) { }, nil } +func (service *Service) Tx(tx portainer.Transaction) ServiceTx { + return ServiceTx{ + service: service, + tx: tx, + } +} + // EndpointRelations returns an array of all EndpointRelations func (service *Service) EndpointRelations() ([]portainer.EndpointRelation, error) { var all = make([]portainer.EndpointRelation, 0) diff --git a/api/dataservices/endpointrelation/tx.go b/api/dataservices/endpointrelation/tx.go new file mode 100644 index 000000000..1da9f6b92 --- /dev/null +++ b/api/dataservices/endpointrelation/tx.go @@ -0,0 +1,159 @@ +package endpointrelation + +import ( + "fmt" + + portainer "github.com/portainer/portainer/api" + "github.com/portainer/portainer/api/internal/edge/cache" + + "github.com/rs/zerolog/log" +) + +type ServiceTx struct { + service *Service + tx portainer.Transaction +} + +func (service ServiceTx) BucketName() string { + return BucketName +} + +// EndpointRelations returns an array of all EndpointRelations +func (service ServiceTx) EndpointRelations() ([]portainer.EndpointRelation, error) { + var all = make([]portainer.EndpointRelation, 0) + + err := service.tx.GetAll( + BucketName, + &portainer.EndpointRelation{}, + func(obj interface{}) (interface{}, error) { + r, ok := obj.(*portainer.EndpointRelation) + if !ok { + log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to EndpointRelation object") + return nil, fmt.Errorf("failed to convert to EndpointRelation object: %s", obj) + } + + all = append(all, *r) + + return &portainer.EndpointRelation{}, nil + }) + + return all, err +} + +// EndpointRelation returns an Environment(Endpoint) relation object by EndpointID +func (service ServiceTx) EndpointRelation(endpointID portainer.EndpointID) (*portainer.EndpointRelation, error) { + var endpointRelation portainer.EndpointRelation + identifier := service.service.connection.ConvertToKey(int(endpointID)) + + err := service.tx.GetObject(BucketName, identifier, &endpointRelation) + if err != nil { + return nil, err + } + + return &endpointRelation, nil +} + +// CreateEndpointRelation saves endpointRelation +func (service ServiceTx) Create(endpointRelation *portainer.EndpointRelation) error { + err := service.tx.CreateObjectWithId(BucketName, int(endpointRelation.EndpointID), endpointRelation) + cache.Del(endpointRelation.EndpointID) + + return err +} + +// UpdateEndpointRelation updates an Environment(Endpoint) relation object +func (service ServiceTx) UpdateEndpointRelation(endpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error { + previousRelationState, _ := service.EndpointRelation(endpointID) + + identifier := service.service.connection.ConvertToKey(int(endpointID)) + err := service.tx.UpdateObject(BucketName, identifier, endpointRelation) + cache.Del(endpointID) + if err != nil { + return err + } + + updatedRelationState, _ := service.EndpointRelation(endpointID) + + service.updateEdgeStacksAfterRelationChange(previousRelationState, updatedRelationState) + + return nil +} + +// DeleteEndpointRelation deletes an Environment(Endpoint) relation object +func (service ServiceTx) DeleteEndpointRelation(endpointID portainer.EndpointID) error { + deletedRelation, _ := service.EndpointRelation(endpointID) + + identifier := service.service.connection.ConvertToKey(int(endpointID)) + err := service.tx.DeleteObject(BucketName, identifier) + cache.Del(endpointID) + if err != nil { + return err + } + + service.updateEdgeStacksAfterRelationChange(deletedRelation, nil) + + return nil +} + +func (service ServiceTx) InvalidateEdgeCacheForEdgeStack(edgeStackID portainer.EdgeStackID) { + rels, err := service.EndpointRelations() + if err != nil { + log.Error().Err(err).Msg("cannot retrieve endpoint relations") + return + } + + for _, rel := range rels { + for id := range rel.EdgeStacks { + if edgeStackID == id { + cache.Del(rel.EndpointID) + } + } + } +} + +func (service ServiceTx) updateEdgeStacksAfterRelationChange(previousRelationState *portainer.EndpointRelation, updatedRelationState *portainer.EndpointRelation) { + relations, _ := service.EndpointRelations() + + stacksToUpdate := map[portainer.EdgeStackID]bool{} + + if previousRelationState != nil { + for stackId, enabled := range previousRelationState.EdgeStacks { + // flag stack for update if stack is not in the updated relation state + // = stack has been removed for this relation + // or this relation has been deleted + if enabled && (updatedRelationState == nil || !updatedRelationState.EdgeStacks[stackId]) { + stacksToUpdate[stackId] = true + } + } + } + + if updatedRelationState != nil { + for stackId, enabled := range updatedRelationState.EdgeStacks { + // flag stack for update if stack is not in the previous relation state + // = stack has been added for this relation + if enabled && (previousRelationState == nil || !previousRelationState.EdgeStacks[stackId]) { + stacksToUpdate[stackId] = true + } + } + } + + // for each stack referenced by the updated relation + // list how many time this stack is referenced in all relations + // in order to update the stack deployments count + for refStackId, refStackEnabled := range stacksToUpdate { + if refStackEnabled { + numDeployments := 0 + for _, r := range relations { + for sId, enabled := range r.EdgeStacks { + if enabled && sId == refStackId { + numDeployments += 1 + } + } + } + + service.service.updateStackFn(refStackId, func(edgeStack *portainer.EdgeStack) { + edgeStack.NumDeployments = numDeployments + }) + } + } +} diff --git a/api/dataservices/snapshot/snapshot.go b/api/dataservices/snapshot/snapshot.go index 0a2f23ddc..489fd68dd 100644 --- a/api/dataservices/snapshot/snapshot.go +++ b/api/dataservices/snapshot/snapshot.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, + } +} + func (service *Service) Snapshot(endpointID portainer.EndpointID) (*portainer.Snapshot, error) { var snapshot portainer.Snapshot identifier := service.connection.ConvertToKey(int(endpointID)) diff --git a/api/dataservices/snapshot/tx.go b/api/dataservices/snapshot/tx.go new file mode 100644 index 000000000..52f54aabd --- /dev/null +++ b/api/dataservices/snapshot/tx.go @@ -0,0 +1,63 @@ +package snapshot + +import ( + "fmt" + + portainer "github.com/portainer/portainer/api" + + "github.com/rs/zerolog/log" +) + +type ServiceTx struct { + service *Service + tx portainer.Transaction +} + +func (service ServiceTx) BucketName() string { + return BucketName +} + +func (service ServiceTx) Snapshot(endpointID portainer.EndpointID) (*portainer.Snapshot, error) { + var snapshot portainer.Snapshot + identifier := service.service.connection.ConvertToKey(int(endpointID)) + + err := service.tx.GetObject(BucketName, identifier, &snapshot) + if err != nil { + return nil, err + } + + return &snapshot, nil +} + +func (service ServiceTx) Snapshots() ([]portainer.Snapshot, error) { + var snapshots = make([]portainer.Snapshot, 0) + + err := service.tx.GetAllWithJsoniter( + BucketName, + &portainer.Snapshot{}, + func(obj interface{}) (interface{}, error) { + snapshot, ok := obj.(*portainer.Snapshot) + if !ok { + log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Snapshot object") + return nil, fmt.Errorf("failed to convert to Snapshot object: %s", obj) + } + snapshots = append(snapshots, *snapshot) + return &portainer.Snapshot{}, nil + }) + + return snapshots, err +} + +func (service ServiceTx) UpdateSnapshot(snapshot *portainer.Snapshot) error { + identifier := service.service.connection.ConvertToKey(int(snapshot.EndpointID)) + return service.tx.UpdateObject(BucketName, identifier, snapshot) +} + +func (service ServiceTx) DeleteSnapshot(endpointID portainer.EndpointID) error { + identifier := service.service.connection.ConvertToKey(int(endpointID)) + return service.tx.DeleteObject(BucketName, identifier) +} + +func (service ServiceTx) Create(snapshot *portainer.Snapshot) error { + return service.tx.CreateObjectWithId(BucketName, int(snapshot.EndpointID), snapshot) +} diff --git a/api/dataservices/tag/tag.go b/api/dataservices/tag/tag.go index 1c4bd456c..b831a3f0b 100644 --- a/api/dataservices/tag/tag.go +++ b/api/dataservices/tag/tag.go @@ -34,6 +34,13 @@ func NewService(connection portainer.Connection) (*Service, error) { }, nil } +func (service *Service) Tx(tx portainer.Transaction) ServiceTx { + return ServiceTx{ + service: service, + tx: tx, + } +} + // Tags return an array containing all the tags. func (service *Service) Tags() ([]portainer.Tag, error) { var tags = make([]portainer.Tag, 0) diff --git a/api/dataservices/tag/tx.go b/api/dataservices/tag/tx.go new file mode 100644 index 000000000..0686c05ea --- /dev/null +++ b/api/dataservices/tag/tx.go @@ -0,0 +1,82 @@ +package tag + +import ( + "errors" + "fmt" + + portainer "github.com/portainer/portainer/api" + + "github.com/rs/zerolog/log" +) + +type ServiceTx struct { + service *Service + tx portainer.Transaction +} + +func (service ServiceTx) BucketName() string { + return BucketName +} + +// Tags return an array containing all the tags. +func (service ServiceTx) Tags() ([]portainer.Tag, error) { + var tags = make([]portainer.Tag, 0) + + err := service.tx.GetAll( + BucketName, + &portainer.Tag{}, + func(obj interface{}) (interface{}, error) { + tag, ok := obj.(*portainer.Tag) + if !ok { + log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Tag object") + return nil, fmt.Errorf("failed to convert to Tag object: %s", obj) + } + + tags = append(tags, *tag) + + return &portainer.Tag{}, nil + }) + + return tags, err +} + +// Tag returns a tag by ID. +func (service ServiceTx) Tag(ID portainer.TagID) (*portainer.Tag, error) { + var tag portainer.Tag + identifier := service.service.connection.ConvertToKey(int(ID)) + + err := service.tx.GetObject(BucketName, identifier, &tag) + if err != nil { + return nil, err + } + + return &tag, nil +} + +// CreateTag creates a new tag. +func (service ServiceTx) Create(tag *portainer.Tag) error { + return service.tx.CreateObject( + BucketName, + func(id uint64) (int, interface{}) { + tag.ID = portainer.TagID(id) + return int(tag.ID), tag + }, + ) +} + +// UpdateTag updates a tag +func (service ServiceTx) UpdateTag(ID portainer.TagID, tag *portainer.Tag) error { + identifier := service.service.connection.ConvertToKey(int(ID)) + return service.tx.UpdateObject(BucketName, identifier, tag) +} + +// UpdateTagFunc is a no-op inside a transaction +func (service ServiceTx) UpdateTagFunc(ID portainer.TagID, updateFunc func(tag *portainer.Tag)) error { + return errors.New("cannot be called inside a transaction") +} + +// DeleteTag deletes a tag. +func (service ServiceTx) DeleteTag(ID portainer.TagID) error { + identifier := service.service.connection.ConvertToKey(int(ID)) + return service.tx.DeleteObject(BucketName, identifier) +} diff --git a/api/datastore/services_tx.go b/api/datastore/services_tx.go index d8fed15d7..ae44946cf 100644 --- a/api/datastore/services_tx.go +++ b/api/datastore/services_tx.go @@ -20,15 +20,26 @@ func (tx *StoreTx) EdgeGroup() dataservices.EdgeGroupService { return tx.store.EdgeGroupService.Tx(tx.tx) } -func (tx *StoreTx) EdgeJob() dataservices.EdgeJobService { return nil } -func (tx *StoreTx) EdgeStack() dataservices.EdgeStackService { return nil } +func (tx *StoreTx) EdgeJob() dataservices.EdgeJobService { + return tx.store.EdgeJobService.Tx(tx.tx) +} + +func (tx *StoreTx) EdgeStack() dataservices.EdgeStackService { + return tx.store.EdgeStackService.Tx(tx.tx) +} func (tx *StoreTx) Endpoint() dataservices.EndpointService { return tx.store.EndpointService.Tx(tx.tx) } -func (tx *StoreTx) EndpointGroup() dataservices.EndpointGroupService { return nil } -func (tx *StoreTx) EndpointRelation() dataservices.EndpointRelationService { return nil } +func (tx *StoreTx) EndpointGroup() dataservices.EndpointGroupService { + return tx.store.EndpointGroupService.Tx(tx.tx) +} + +func (tx *StoreTx) EndpointRelation() dataservices.EndpointRelationService { + return tx.store.EndpointRelationService.Tx(tx.tx) +} + func (tx *StoreTx) FDOProfile() dataservices.FDOProfileService { return nil } func (tx *StoreTx) HelmUserRepository() dataservices.HelmUserRepositoryService { return nil } func (tx *StoreTx) Registry() dataservices.RegistryService { return nil } @@ -36,13 +47,21 @@ func (tx *StoreTx) ResourceControl() dataservices.ResourceControlService { func (tx *StoreTx) Role() dataservices.RoleService { return nil } func (tx *StoreTx) APIKeyRepository() dataservices.APIKeyRepository { return nil } func (tx *StoreTx) Settings() dataservices.SettingsService { return nil } -func (tx *StoreTx) Snapshot() dataservices.SnapshotService { return nil } -func (tx *StoreTx) SSLSettings() dataservices.SSLSettingsService { return nil } -func (tx *StoreTx) Stack() dataservices.StackService { return nil } -func (tx *StoreTx) Tag() dataservices.TagService { return nil } -func (tx *StoreTx) TeamMembership() dataservices.TeamMembershipService { return nil } -func (tx *StoreTx) Team() dataservices.TeamService { return nil } -func (tx *StoreTx) TunnelServer() dataservices.TunnelServerService { return nil } -func (tx *StoreTx) User() dataservices.UserService { return nil } -func (tx *StoreTx) Version() dataservices.VersionService { return nil } -func (tx *StoreTx) Webhook() dataservices.WebhookService { return nil } + +func (tx *StoreTx) Snapshot() dataservices.SnapshotService { + return tx.store.SnapshotService.Tx(tx.tx) +} + +func (tx *StoreTx) SSLSettings() dataservices.SSLSettingsService { return nil } +func (tx *StoreTx) Stack() dataservices.StackService { return nil } + +func (tx *StoreTx) Tag() dataservices.TagService { + return tx.store.TagService.Tx(tx.tx) +} + +func (tx *StoreTx) TeamMembership() dataservices.TeamMembershipService { return nil } +func (tx *StoreTx) Team() dataservices.TeamService { return nil } +func (tx *StoreTx) TunnelServer() dataservices.TunnelServerService { return nil } +func (tx *StoreTx) User() dataservices.UserService { return nil } +func (tx *StoreTx) Version() dataservices.VersionService { return nil } +func (tx *StoreTx) Webhook() dataservices.WebhookService { return nil }