package deployments import ( "context" "fmt" "log" "github.com/pkg/errors" portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/dataservices" "github.com/portainer/portainer/api/http/security" "github.com/portainer/portainer/api/internal/registryutils" "github.com/portainer/portainer/api/stacks/stackutils" ) type SwarmStackDeploymentConfig struct { stack *portainer.Stack endpoint *portainer.Endpoint registries []portainer.Registry prune bool isAdmin bool user *portainer.User pullImage bool FileService portainer.FileService StackDeployer StackDeployer } func CreateSwarmStackDeploymentConfigTx(tx dataservices.DataStoreTx, securityContext *security.RestrictedRequestContext, stack *portainer.Stack, endpoint *portainer.Endpoint, fileService portainer.FileService, deployer StackDeployer, prune bool, pullImage bool) (*SwarmStackDeploymentConfig, error) { user, err := tx.User().Read(securityContext.UserID) if err != nil { return nil, fmt.Errorf("unable to load user information from the database: %w", err) } registries, err := tx.Registry().ReadAll() if err != nil { return nil, fmt.Errorf("unable to retrieve registries from the database: %w", err) } filteredRegistries := security.FilterRegistries(registries, user, securityContext.UserMemberships, endpoint.ID) registryutils.RefreshAndPersistECRTokens(tx, filteredRegistries) config := &SwarmStackDeploymentConfig{ stack: stack, endpoint: endpoint, registries: filteredRegistries, prune: prune, isAdmin: securityContext.IsAdmin, user: user, pullImage: pullImage, FileService: fileService, StackDeployer: deployer, } return config, nil } func (config *SwarmStackDeploymentConfig) Deploy(ctx context.Context) error { if config.FileService == nil || config.StackDeployer == nil { log.Println("[deployment, swarm] file service or stack deployer is not initialised") return errors.New("file service or stack deployer cannot be nil") } isAdminOrEndpointAdmin := stackutils.UserIsAdminOrEndpointAdmin(config.user) settings := &config.endpoint.SecuritySettings if !isAdminOrEndpointAdmin { if err := stackutils.ValidateStackFiles(config.stack, settings, config.FileService); err != nil { return err } } if err := stackutils.ValidateComposeURLs(ctx, config.stack, config.FileService); err != nil { return err } if stackutils.IsRelativePathStack(config.stack) { return config.StackDeployer.DeployRemoteSwarmStack(ctx, config.user.ID, config.stack, config.endpoint, config.registries, config.prune, config.pullImage) } return config.StackDeployer.DeploySwarmStack(ctx, config.stack, config.endpoint, config.registries, config.prune, config.pullImage) } func (config *SwarmStackDeploymentConfig) Undeploy(ctx context.Context) error { // Swarm is an orchestrator that handles partial failures internally, // so there is no need to remove failed resources before redeploying. // This method exists only to satisfy the deployment interface. return nil } func (config *SwarmStackDeploymentConfig) GetResponse() string { return "" }