2020-08-03 22:18:53 +00:00
package stacks
import (
2021-09-06 07:58:26 +00:00
"context"
2020-08-03 22:18:53 +00:00
"errors"
2021-02-23 20:18:05 +00:00
"fmt"
2020-08-03 22:18:53 +00:00
"net/http"
2021-01-25 19:16:53 +00:00
portainer "github.com/portainer/portainer/api"
2020-08-21 03:16:38 +00:00
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
2021-02-23 20:18:05 +00:00
"github.com/portainer/portainer/api/internal/stackutils"
2020-08-21 03:16:38 +00:00
2020-08-03 22:18:53 +00:00
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
2021-02-23 03:21:39 +00:00
// @id StackStart
// @summary Starts a stopped Stack
// @description Starts a stopped Stack.
// @description **Access policy**: restricted
// @tags stacks
// @security jwt
// @param id path int true "Stack identifier"
// @success 200 {object} portainer.Stack "Success"
// @failure 400 "Invalid request"
// @failure 403 "Permission denied"
2021-07-21 23:40:53 +00:00
// @failure 404 "Not found"
2021-02-23 03:21:39 +00:00
// @failure 500 "Server error"
// @router /stacks/{id}/start [post]
2020-08-03 22:18:53 +00:00
func ( handler * Handler ) stackStart ( w http . ResponseWriter , r * http . Request ) * httperror . HandlerError {
stackID , err := request . RetrieveNumericRouteVariableValue ( r , "id" )
if err != nil {
2021-09-29 23:58:10 +00:00
return & httperror . HandlerError { StatusCode : http . StatusBadRequest , Message : "Invalid stack identifier route variable" , Err : err }
2020-08-03 22:18:53 +00:00
}
2020-08-21 03:16:38 +00:00
securityContext , err := security . RetrieveRestrictedRequestContext ( r )
if err != nil {
2021-09-29 23:58:10 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to retrieve info from request context" , Err : err }
2020-08-21 03:16:38 +00:00
}
2020-08-03 22:18:53 +00:00
stack , err := handler . DataStore . Stack ( ) . Stack ( portainer . StackID ( stackID ) )
if err == bolterrors . ErrObjectNotFound {
2021-09-29 23:58:10 +00:00
return & httperror . HandlerError { StatusCode : http . StatusNotFound , Message : "Unable to find a stack with the specified identifier inside the database" , Err : err }
2020-08-03 22:18:53 +00:00
} else if err != nil {
2021-09-29 23:58:10 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to find a stack with the specified identifier inside the database" , Err : err }
}
if stack . Type == portainer . KubernetesStack {
return & httperror . HandlerError { StatusCode : http . StatusBadRequest , Message : "Starting a kubernetes stack is not supported" , Err : err }
2020-08-03 22:18:53 +00:00
}
endpoint , err := handler . DataStore . Endpoint ( ) . Endpoint ( stack . EndpointID )
if err == bolterrors . ErrObjectNotFound {
2021-09-29 23:58:10 +00:00
return & httperror . HandlerError { StatusCode : http . StatusNotFound , Message : "Unable to find an endpoint with the specified identifier inside the database" , Err : err }
2020-08-03 22:18:53 +00:00
} else if err != nil {
2021-09-29 23:58:10 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to find an endpoint with the specified identifier inside the database" , Err : err }
2020-08-03 22:18:53 +00:00
}
2020-08-21 03:16:38 +00:00
err = handler . requestBouncer . AuthorizedEndpointOperation ( r , endpoint )
if err != nil {
2021-09-29 23:58:10 +00:00
return & httperror . HandlerError { StatusCode : http . StatusForbidden , Message : "Permission denied to access endpoint" , Err : err }
2020-08-21 03:16:38 +00:00
}
2021-09-29 23:58:10 +00:00
isUnique , err := handler . checkUniqueStackNameInDocker ( endpoint , stack . Name , stack . ID , stack . SwarmID != "" )
2021-02-23 20:18:05 +00:00
if err != nil {
2021-09-29 23:58:10 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to check for name collision" , Err : err }
2021-02-23 20:18:05 +00:00
}
if ! isUnique {
errorMessage := fmt . Sprintf ( "A stack with the name '%s' is already running" , stack . Name )
2021-09-29 23:58:10 +00:00
return & httperror . HandlerError { StatusCode : http . StatusConflict , Message : errorMessage , Err : errors . New ( errorMessage ) }
2021-02-23 20:18:05 +00:00
}
2021-09-29 23:58:10 +00:00
resourceControl , err := handler . DataStore . ResourceControl ( ) . ResourceControlByResourceIDAndType ( stackutils . ResourceControlID ( stack . EndpointID , stack . Name ) , portainer . StackResourceControl )
if err != nil {
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to retrieve a resource control associated to the stack" , Err : err }
}
2020-08-21 03:16:38 +00:00
2021-09-29 23:58:10 +00:00
access , err := handler . userCanAccessStack ( securityContext , endpoint . ID , resourceControl )
if err != nil {
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to verify user authorizations to validate stack access" , Err : err }
}
if ! access {
return & httperror . HandlerError { StatusCode : http . StatusForbidden , Message : "Access denied to resource" , Err : httperrors . ErrResourceAccessDenied }
2020-08-21 03:16:38 +00:00
}
if stack . Status == portainer . StackStatusActive {
2021-09-29 23:58:10 +00:00
return & httperror . HandlerError { StatusCode : http . StatusBadRequest , Message : "Stack is already active" , Err : errors . New ( "Stack is already active" ) }
2020-08-21 03:16:38 +00:00
}
2021-08-17 01:12:07 +00:00
if stack . AutoUpdate != nil && stack . AutoUpdate . Interval != "" {
stopAutoupdate ( stack . ID , stack . AutoUpdate . JobID , * handler . Scheduler )
jobID , e := startAutoupdate ( stack . ID , stack . AutoUpdate . Interval , handler . Scheduler , handler . StackDeployer , handler . DataStore , handler . GitService )
if e != nil {
return e
}
stack . AutoUpdate . JobID = jobID
}
2020-08-03 22:18:53 +00:00
err = handler . startStack ( stack , endpoint )
if err != nil {
2021-09-29 23:58:10 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to start stack" , Err : err }
2020-08-03 22:18:53 +00:00
}
stack . Status = portainer . StackStatusActive
err = handler . DataStore . Stack ( ) . UpdateStack ( stack . ID , stack )
if err != nil {
2021-09-29 23:58:10 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to update stack status" , Err : err }
2020-08-03 22:18:53 +00:00
}
2021-08-17 01:12:07 +00:00
if stack . GitConfig != nil && stack . GitConfig . Authentication != nil && stack . GitConfig . Authentication . Password != "" {
// sanitize password in the http response to minimise possible security leaks
stack . GitConfig . Authentication . Password = ""
}
2020-08-03 22:18:53 +00:00
return response . JSON ( w , stack )
}
func ( handler * Handler ) startStack ( stack * portainer . Stack , endpoint * portainer . Endpoint ) error {
switch stack . Type {
case portainer . DockerComposeStack :
2021-09-06 07:58:26 +00:00
return handler . ComposeStackManager . Up ( context . TODO ( ) , stack , endpoint )
2020-08-03 22:18:53 +00:00
case portainer . DockerSwarmStack :
return handler . SwarmStackManager . Deploy ( stack , true , endpoint )
}
return nil
}