1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-25 00:09:40 +02:00

feat(stacks): add the ability to stop a stack (#4042)

* feat(stacks): add stack status

* feat(stacks): add empty start/stop handlers

* feat(stacks): show start/stop button

* feat(stacks): implement stack stop

* feat(stacks): implement start stack

* feat(stacks): filter by active/inactive stacks

* fix(stacks): update authorizations for stack start/stop

* feat(stacks): assign default status on create

* fix(bolt): fix import

* fix(stacks): show external stacks

* fix(stacks): reload on stop/start

* feat(stacks): confirm before stop
This commit is contained in:
Chaim Lev-Ari 2020-08-04 01:18:53 +03:00 committed by GitHub
parent da143a7a22
commit 4d5836138b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 322 additions and 8 deletions

View file

@ -66,6 +66,7 @@ func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
}
stackFolder := strconv.Itoa(int(stack.ID))
@ -151,6 +152,7 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite
EndpointID: endpoint.ID,
EntryPoint: payload.ComposeFilePathInRepository,
Env: payload.Env,
Status: portainer.StackStatusActive,
}
projectPath := handler.FileService.GetStackProjectPath(strconv.Itoa(int(stack.ID)))
@ -246,6 +248,7 @@ func (handler *Handler) createComposeStackFromFileUpload(w http.ResponseWriter,
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
}
stackFolder := strconv.Itoa(int(stack.ID))

View file

@ -62,6 +62,7 @@ func (handler *Handler) createSwarmStackFromFileContent(w http.ResponseWriter, r
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
}
stackFolder := strconv.Itoa(int(stack.ID))
@ -151,6 +152,7 @@ func (handler *Handler) createSwarmStackFromGitRepository(w http.ResponseWriter,
EndpointID: endpoint.ID,
EntryPoint: payload.ComposeFilePathInRepository,
Env: payload.Env,
Status: portainer.StackStatusActive,
}
projectPath := handler.FileService.GetStackProjectPath(strconv.Itoa(int(stack.ID)))
@ -254,6 +256,7 @@ func (handler *Handler) createSwarmStackFromFileUpload(w http.ResponseWriter, r
EndpointID: endpoint.ID,
EntryPoint: filesystem.ComposeFileDefaultName,
Env: payload.Env,
Status: portainer.StackStatusActive,
}
stackFolder := strconv.Itoa(int(stack.ID))

View file

@ -54,6 +54,10 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.stackFile))).Methods(http.MethodGet)
h.Handle("/stacks/{id}/migrate",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.stackMigrate))).Methods(http.MethodPost)
h.Handle("/stacks/{id}/start",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.stackStart))).Methods(http.MethodPost)
h.Handle("/stacks/{id}/stop",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.stackStop))).Methods(http.MethodPost)
return h
}

View file

@ -0,0 +1,61 @@
package stacks
import (
"errors"
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
// POST request on /api/stacks/:id/start
func (handler *Handler) stackStart(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
stackID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid stack identifier route variable", err}
}
stack, err := handler.DataStore.Stack().Stack(portainer.StackID(stackID))
if err == bolterrors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a stack with the specified identifier inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a stack with the specified identifier inside the database", err}
}
if stack.Status == portainer.StackStatusActive {
return &httperror.HandlerError{http.StatusBadRequest, "Stack is already active", errors.New("Stack is already active")}
}
endpoint, err := handler.DataStore.Endpoint().Endpoint(stack.EndpointID)
if err == bolterrors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
err = handler.startStack(stack, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to stop stack", err}
}
stack.Status = portainer.StackStatusActive
err = handler.DataStore.Stack().UpdateStack(stack.ID, stack)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update stack status", err}
}
return response.JSON(w, stack)
}
func (handler *Handler) startStack(stack *portainer.Stack, endpoint *portainer.Endpoint) error {
switch stack.Type {
case portainer.DockerComposeStack:
return handler.ComposeStackManager.Up(stack, endpoint)
case portainer.DockerSwarmStack:
return handler.SwarmStackManager.Deploy(stack, true, endpoint)
}
return nil
}

View file

@ -0,0 +1,61 @@
package stacks
import (
"errors"
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
// POST request on /api/stacks/:id/stop
func (handler *Handler) stackStop(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
stackID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid stack identifier route variable", err}
}
stack, err := handler.DataStore.Stack().Stack(portainer.StackID(stackID))
if err == bolterrors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a stack with the specified identifier inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a stack with the specified identifier inside the database", err}
}
if stack.Status == portainer.StackStatusInactive {
return &httperror.HandlerError{http.StatusBadRequest, "Stack is already inactive", errors.New("Stack is already inactive")}
}
endpoint, err := handler.DataStore.Endpoint().Endpoint(stack.EndpointID)
if err == bolterrors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
err = handler.stopStack(stack, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to stop stack", err}
}
stack.Status = portainer.StackStatusInactive
err = handler.DataStore.Stack().UpdateStack(stack.ID, stack)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update stack status", err}
}
return response.JSON(w, stack)
}
func (handler *Handler) stopStack(stack *portainer.Stack, endpoint *portainer.Endpoint) error {
switch stack.Type {
case portainer.DockerComposeStack:
return handler.ComposeStackManager.Down(stack, endpoint)
case portainer.DockerSwarmStack:
return handler.SwarmStackManager.Remove(stack, endpoint)
}
return nil
}