diff --git a/api/exec/stack_manager.go b/api/exec/stack_manager.go index 7e418d70f..8ba6b357e 100644 --- a/api/exec/stack_manager.go +++ b/api/exec/stack_manager.go @@ -54,10 +54,15 @@ func (manager *StackManager) Logout(endpoint *portainer.Endpoint) error { } // Deploy executes the docker stack deploy command. -func (manager *StackManager) Deploy(stack *portainer.Stack, endpoint *portainer.Endpoint) error { +func (manager *StackManager) Deploy(stack *portainer.Stack, prune bool, endpoint *portainer.Endpoint) error { stackFilePath := path.Join(stack.ProjectPath, stack.EntryPoint) command, args := prepareDockerCommandAndArgs(manager.binaryPath, endpoint) - args = append(args, "stack", "deploy", "--with-registry-auth", "--compose-file", stackFilePath, stack.Name) + + if prune { + args = append(args, "stack", "deploy", "--prune", "--with-registry-auth", "--compose-file", stackFilePath, stack.Name) + } else { + args = append(args, "stack", "deploy", "--with-registry-auth", "--compose-file", stackFilePath, stack.Name) + } env := make([]string, 0) for _, envvar := range stack.Env { diff --git a/api/http/handler/stack.go b/api/http/handler/stack.go index ed54c500f..3ad0ce7ae 100644 --- a/api/http/handler/stack.go +++ b/api/http/handler/stack.go @@ -37,6 +37,14 @@ type StackHandler struct { StackManager portainer.StackManager } +type stackDeploymentConfig struct { + endpoint *portainer.Endpoint + stack *portainer.Stack + prune bool + dockerhub *portainer.DockerHub + registries []portainer.Registry +} + // NewStackHandler returns a new instance of StackHandler. func NewStackHandler(bouncer *security.RequestBouncer) *StackHandler { h := &StackHandler{ @@ -78,6 +86,7 @@ type ( putStackRequest struct { StackFileContent string `valid:"required"` Env []portainer.Pair `valid:""` + Prune bool `valid:"-"` } ) @@ -207,7 +216,14 @@ func (handler *StackHandler) handlePostStacksStringMethod(w http.ResponseWriter, return } - err = handler.deployStack(endpoint, stack, dockerhub, filteredRegistries) + config := stackDeploymentConfig{ + stack: stack, + endpoint: endpoint, + dockerhub: dockerhub, + registries: filteredRegistries, + prune: false, + } + err = handler.deployStack(&config) if err != nil { httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) return @@ -334,7 +350,14 @@ func (handler *StackHandler) handlePostStacksRepositoryMethod(w http.ResponseWri return } - err = handler.deployStack(endpoint, stack, dockerhub, filteredRegistries) + config := stackDeploymentConfig{ + stack: stack, + endpoint: endpoint, + dockerhub: dockerhub, + registries: filteredRegistries, + prune: false, + } + err = handler.deployStack(&config) if err != nil { httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) return @@ -445,7 +468,14 @@ func (handler *StackHandler) handlePostStacksFileMethod(w http.ResponseWriter, r return } - err = handler.deployStack(endpoint, stack, dockerhub, filteredRegistries) + config := stackDeploymentConfig{ + stack: stack, + endpoint: endpoint, + dockerhub: dockerhub, + registries: filteredRegistries, + prune: false, + } + err = handler.deployStack(&config) if err != nil { httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) return @@ -637,7 +667,14 @@ func (handler *StackHandler) handlePutStack(w http.ResponseWriter, r *http.Reque return } - err = handler.deployStack(endpoint, stack, dockerhub, filteredRegistries) + config := stackDeploymentConfig{ + stack: stack, + endpoint: endpoint, + dockerhub: dockerhub, + registries: filteredRegistries, + prune: req.Prune, + } + err = handler.deployStack(&config) if err != nil { httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger) return @@ -732,22 +769,22 @@ func (handler *StackHandler) handleDeleteStack(w http.ResponseWriter, r *http.Re } } -func (handler *StackHandler) deployStack(endpoint *portainer.Endpoint, stack *portainer.Stack, dockerhub *portainer.DockerHub, registries []portainer.Registry) error { +func (handler *StackHandler) deployStack(config *stackDeploymentConfig) error { handler.stackCreationMutex.Lock() - err := handler.StackManager.Login(dockerhub, registries, endpoint) + err := handler.StackManager.Login(config.dockerhub, config.registries, config.endpoint) if err != nil { handler.stackCreationMutex.Unlock() return err } - err = handler.StackManager.Deploy(stack, endpoint) + err = handler.StackManager.Deploy(config.stack, config.prune, config.endpoint) if err != nil { handler.stackCreationMutex.Unlock() return err } - err = handler.StackManager.Logout(endpoint) + err = handler.StackManager.Logout(config.endpoint) if err != nil { handler.stackCreationMutex.Unlock() return err diff --git a/api/portainer.go b/api/portainer.go index 8b250009b..82a3bc475 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -384,7 +384,7 @@ type ( StackManager interface { Login(dockerhub *DockerHub, registries []Registry, endpoint *Endpoint) error Logout(endpoint *Endpoint) error - Deploy(stack *Stack, endpoint *Endpoint) error + Deploy(stack *Stack, prune bool, endpoint *Endpoint) error Remove(stack *Stack, endpoint *Endpoint) error } ) diff --git a/api/swagger.yaml b/api/swagger.yaml index 5f4b9308b..5a5d2d008 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -2976,10 +2976,14 @@ definitions: description: "A list of environment variables used during stack deployment" items: $ref: "#/definitions/Stack_Env" + Prune: + type: "boolean" + example: false + description: "Prune services that are no longer referenced" StackFileInspectResponse: type: "object" properties: StackFileContent: type: "string" example: "version: 3\n services:\n web:\n image:nginx" - description: "Content of the Stack file." + description: "Content of the Stack file." diff --git a/app/components/stack/stack.html b/app/components/stack/stack.html index 970f8c294..37f3785ec 100644 --- a/app/components/stack/stack.html +++ b/app/components/stack/stack.html @@ -90,6 +90,22 @@ + +