portainer/api/internal/edge/edgestacks/service.go

171 lines
5.4 KiB
Go

package edgestacks
import (
"fmt"
"strconv"
"strings"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/internal/edge"
edgetypes "github.com/portainer/portainer/api/internal/edge/types"
"github.com/pkg/errors"
)
// Service represents a service for managing edge stacks.
type Service struct {
dataStore dataservices.DataStore
}
// NewService returns a new instance of a service.
func NewService(dataStore dataservices.DataStore) *Service {
return &Service{
dataStore: dataStore,
}
}
// BuildEdgeStack builds the initial edge stack object
// PersistEdgeStack is required to be called after this to persist the edge stack
func (service *Service) BuildEdgeStack(
tx dataservices.DataStoreTx,
name string,
deploymentType portainer.EdgeStackDeploymentType,
edgeGroups []portainer.EdgeGroupID,
registries []portainer.RegistryID,
useManifestNamespaces bool,
) (*portainer.EdgeStack, error) {
err := validateUniqueName(tx.EdgeStack().EdgeStacks, name)
if err != nil {
return nil, err
}
stackID := tx.EdgeStack().GetNextIdentifier()
return &portainer.EdgeStack{
ID: portainer.EdgeStackID(stackID),
Name: name,
DeploymentType: deploymentType,
CreationDate: time.Now().Unix(),
EdgeGroups: edgeGroups,
Status: make(map[portainer.EndpointID]portainer.EdgeStackStatus),
Version: 1,
UseManifestNamespaces: useManifestNamespaces,
}, nil
}
func validateUniqueName(edgeStacksGetter func() ([]portainer.EdgeStack, error), name string) error {
edgeStacks, err := edgeStacksGetter()
if err != nil {
return err
}
for _, stack := range edgeStacks {
if strings.EqualFold(stack.Name, name) {
return httperrors.NewConflictError("Edge stack name must be unique")
}
}
return nil
}
// PersistEdgeStack persists the edge stack in the database and its relations
func (service *Service) PersistEdgeStack(
tx dataservices.DataStoreTx,
stack *portainer.EdgeStack,
storeManifest edgetypes.StoreManifestFunc) (*portainer.EdgeStack, error) {
relationConfig, err := edge.FetchEndpointRelationsConfig(tx)
if err != nil {
return nil, fmt.Errorf("unable to find environment relations in database: %w", err)
}
relatedEndpointIds, err := edge.EdgeStackRelatedEndpoints(stack.EdgeGroups, relationConfig.Endpoints, relationConfig.EndpointGroups, relationConfig.EdgeGroups)
if err != nil {
if errors.Is(err, edge.ErrEdgeGroupNotFound) {
return nil, httperrors.NewInvalidPayloadError(err.Error())
}
return nil, fmt.Errorf("unable to persist environment relation in database: %w", err)
}
stackFolder := strconv.Itoa(int(stack.ID))
composePath, manifestPath, projectPath, err := storeManifest(stackFolder, relatedEndpointIds)
if err != nil {
return nil, fmt.Errorf("unable to store manifest: %w", err)
}
stack.ManifestPath = manifestPath
stack.ProjectPath = projectPath
stack.EntryPoint = composePath
stack.NumDeployments = len(relatedEndpointIds)
err = service.updateEndpointRelations(tx, stack.ID, relatedEndpointIds)
if err != nil {
return nil, fmt.Errorf("unable to update endpoint relations: %w", err)
}
err = tx.EdgeStack().Create(stack.ID, stack)
if err != nil {
return nil, err
}
return stack, nil
}
// updateEndpointRelations adds a relation between the Edge Stack to the related environments(endpoints)
func (service *Service) updateEndpointRelations(tx dataservices.DataStoreTx, edgeStackID portainer.EdgeStackID, relatedEndpointIds []portainer.EndpointID) error {
endpointRelationService := tx.EndpointRelation()
for _, endpointID := range relatedEndpointIds {
relation, err := endpointRelationService.EndpointRelation(endpointID)
if err != nil {
return fmt.Errorf("unable to find endpoint relation in database: %w", err)
}
relation.EdgeStacks[edgeStackID] = true
err = endpointRelationService.UpdateEndpointRelation(endpointID, relation)
if err != nil {
return fmt.Errorf("unable to persist endpoint relation in database: %w", err)
}
}
return nil
}
// DeleteEdgeStack deletes the edge stack from the database and its relations
func (service *Service) DeleteEdgeStack(tx dataservices.DataStoreTx, edgeStackID portainer.EdgeStackID, relatedEdgeGroupsIds []portainer.EdgeGroupID) error {
relationConfig, err := edge.FetchEndpointRelationsConfig(tx)
if err != nil {
return errors.WithMessage(err, "Unable to retrieve environments relations config from database")
}
relatedEndpointIds, err := edge.EdgeStackRelatedEndpoints(relatedEdgeGroupsIds, relationConfig.Endpoints, relationConfig.EndpointGroups, relationConfig.EdgeGroups)
if err != nil {
return errors.WithMessage(err, "Unable to retrieve edge stack related environments from database")
}
for _, endpointID := range relatedEndpointIds {
relation, err := tx.EndpointRelation().EndpointRelation(endpointID)
if err != nil {
return errors.WithMessage(err, "Unable to find environment relation in database")
}
delete(relation.EdgeStacks, edgeStackID)
err = tx.EndpointRelation().UpdateEndpointRelation(endpointID, relation)
if err != nil {
return errors.WithMessage(err, "Unable to persist environment relation in database")
}
}
err = tx.EdgeStack().DeleteEdgeStack(portainer.EdgeStackID(edgeStackID))
if err != nil {
return errors.WithMessage(err, "Unable to remove the edge stack from the database")
}
return nil
}