mirror of
https://github.com/portainer/portainer.git
synced 2025-07-21 22:39:41 +02:00
fix(stack): support removing duplicated stacks EE-1962 (#6068)
* fix/EE-1962/cannot-same-stack-name handle multiple names duplicate case Co-authored-by: Eric Sun <ericsun@SG1.local>
This commit is contained in:
parent
5f2e3452e4
commit
cea634a7aa
3 changed files with 103 additions and 3 deletions
|
@ -77,6 +77,31 @@ func (service *Service) StackByName(name string) (*portainer.Stack, error) {
|
||||||
return stack, err
|
return stack, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stacks returns an array containing all the stacks with same name
|
||||||
|
func (service *Service) StacksByName(name string) ([]portainer.Stack, error) {
|
||||||
|
var stacks = make([]portainer.Stack, 0)
|
||||||
|
|
||||||
|
err := service.connection.View(func(tx *bolt.Tx) error {
|
||||||
|
bucket := tx.Bucket([]byte(BucketName))
|
||||||
|
cursor := bucket.Cursor()
|
||||||
|
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||||
|
var t portainer.Stack
|
||||||
|
err := internal.UnmarshalObject(v, &t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Name == name {
|
||||||
|
stacks = append(stacks, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return stacks, err
|
||||||
|
}
|
||||||
|
|
||||||
// Stacks returns an array containing all the stacks.
|
// Stacks returns an array containing all the stacks.
|
||||||
func (service *Service) Stacks() ([]portainer.Stack, error) {
|
func (service *Service) Stacks() ([]portainer.Stack, error) {
|
||||||
var stacks = make([]portainer.Stack, 0)
|
var stacks = make([]portainer.Stack, 0)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package stacks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
@ -14,6 +15,7 @@ import (
|
||||||
"github.com/portainer/portainer/api/filesystem"
|
"github.com/portainer/portainer/api/filesystem"
|
||||||
gittypes "github.com/portainer/portainer/api/git/types"
|
gittypes "github.com/portainer/portainer/api/git/types"
|
||||||
"github.com/portainer/portainer/api/http/security"
|
"github.com/portainer/portainer/api/http/security"
|
||||||
|
"github.com/portainer/portainer/api/internal/stackutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type composeStackFromFileContentPayload struct {
|
type composeStackFromFileContentPayload struct {
|
||||||
|
@ -35,6 +37,36 @@ func (payload *composeStackFromFileContentPayload) Validate(r *http.Request) err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (handler *Handler) checkAndCleanStackDupFromSwarm(w http.ResponseWriter, r *http.Request, endpoint *portainer.Endpoint, userID portainer.UserID, stack *portainer.Stack) error {
|
||||||
|
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// stop scheduler updates of the stack before removal
|
||||||
|
if stack.AutoUpdate != nil {
|
||||||
|
stopAutoupdate(stack.ID, stack.AutoUpdate.JobID, *handler.Scheduler)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = handler.DataStore.Stack().DeleteStack(stack.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resourceControl != nil {
|
||||||
|
err = handler.DataStore.ResourceControl().DeleteResourceControl(resourceControl.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] [Stack] Unable to remove the associated resource control from the database for stack: [%+v].", stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists, _ := handler.FileService.FileExists(stack.ProjectPath); exists {
|
||||||
|
err = handler.FileService.RemoveDirectory(stack.ProjectPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to remove stack files from disk for stack: [%+v].", stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter, r *http.Request, endpoint *portainer.Endpoint, userID portainer.UserID) *httperror.HandlerError {
|
func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter, r *http.Request, endpoint *portainer.Endpoint, userID portainer.UserID) *httperror.HandlerError {
|
||||||
var payload composeStackFromFileContentPayload
|
var payload composeStackFromFileContentPayload
|
||||||
|
@ -49,8 +81,22 @@ func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check for name collision", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isUnique {
|
if !isUnique {
|
||||||
return stackExistsError(payload.Name)
|
stacks, err := handler.DataStore.Stack().StacksByName(payload.Name)
|
||||||
|
if err != nil {
|
||||||
|
return stackExistsError(payload.Name)
|
||||||
|
}
|
||||||
|
for _, stack := range stacks {
|
||||||
|
if stack.Type != portainer.DockerComposeStack && stack.EndpointID == endpoint.ID {
|
||||||
|
err := handler.checkAndCleanStackDupFromSwarm(w, r, endpoint, userID, &stack)
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid request payload", Err: err}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return stackExistsError(payload.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stackID := handler.DataStore.Stack().GetNextIdentifier()
|
stackID := handler.DataStore.Stack().GetNextIdentifier()
|
||||||
|
@ -154,8 +200,22 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to check for name collision", Err: err}
|
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to check for name collision", Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isUnique {
|
if !isUnique {
|
||||||
return stackExistsError(payload.Name)
|
stacks, err := handler.DataStore.Stack().StacksByName(payload.Name)
|
||||||
|
if err != nil {
|
||||||
|
return stackExistsError(payload.Name)
|
||||||
|
}
|
||||||
|
for _, stack := range stacks {
|
||||||
|
if stack.Type != portainer.DockerComposeStack && stack.EndpointID == endpoint.ID {
|
||||||
|
err := handler.checkAndCleanStackDupFromSwarm(w, r, endpoint, userID, &stack)
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid request payload", Err: err}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return stackExistsError(payload.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//make sure the webhook ID is unique
|
//make sure the webhook ID is unique
|
||||||
|
@ -283,8 +343,22 @@ func (handler *Handler) createComposeStackFromFileUpload(w http.ResponseWriter,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to check for name collision", Err: err}
|
return &httperror.HandlerError{StatusCode: http.StatusInternalServerError, Message: "Unable to check for name collision", Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isUnique {
|
if !isUnique {
|
||||||
return stackExistsError(payload.Name)
|
stacks, err := handler.DataStore.Stack().StacksByName(payload.Name)
|
||||||
|
if err != nil {
|
||||||
|
return stackExistsError(payload.Name)
|
||||||
|
}
|
||||||
|
for _, stack := range stacks {
|
||||||
|
if stack.Type != portainer.DockerComposeStack && stack.EndpointID == endpoint.ID {
|
||||||
|
err := handler.checkAndCleanStackDupFromSwarm(w, r, endpoint, userID, &stack)
|
||||||
|
if err != nil {
|
||||||
|
return &httperror.HandlerError{StatusCode: http.StatusBadRequest, Message: "Invalid request payload", Err: err}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return stackExistsError(payload.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stackID := handler.DataStore.Stack().GetNextIdentifier()
|
stackID := handler.DataStore.Stack().GetNextIdentifier()
|
||||||
|
|
|
@ -1379,6 +1379,7 @@ type (
|
||||||
StackService interface {
|
StackService interface {
|
||||||
Stack(ID StackID) (*Stack, error)
|
Stack(ID StackID) (*Stack, error)
|
||||||
StackByName(name string) (*Stack, error)
|
StackByName(name string) (*Stack, error)
|
||||||
|
StacksByName(name string) ([]Stack, error)
|
||||||
Stacks() ([]Stack, error)
|
Stacks() ([]Stack, error)
|
||||||
CreateStack(stack *Stack) error
|
CreateStack(stack *Stack) error
|
||||||
UpdateStack(ID StackID, stack *Stack) error
|
UpdateStack(ID StackID, stack *Stack) error
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue