2018-06-11 13:13:19 +00:00
package stacks
import (
"net/http"
2018-06-18 10:07:56 +00:00
"strconv"
2018-06-11 13:13:19 +00:00
2019-05-24 06:04:58 +00:00
"github.com/portainer/portainer/api/http/security"
2018-09-10 10:01:38 +00:00
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
2019-03-21 01:20:14 +00:00
"github.com/portainer/portainer/api"
2018-06-11 13:13:19 +00:00
)
// DELETE request on /api/stacks/:id?external=<external>&endpointId=<endpointId>
2018-06-18 10:07:56 +00:00
// If the external query parameter is set to true, the id route variable is expected to be
// the name of an external stack as a string.
2018-06-11 13:13:19 +00:00
func ( handler * Handler ) stackDelete ( w http . ResponseWriter , r * http . Request ) * httperror . HandlerError {
stackID , err := request . RetrieveRouteVariableValue ( r , "id" )
if err != nil {
return & httperror . HandlerError { http . StatusBadRequest , "Invalid stack identifier route variable" , err }
}
externalStack , _ := request . RetrieveBooleanQueryParameter ( r , "external" , true )
if externalStack {
return handler . deleteExternalStack ( r , w , stackID )
}
2018-06-18 10:07:56 +00:00
id , err := strconv . Atoi ( stackID )
if err != nil {
return & httperror . HandlerError { http . StatusBadRequest , "Invalid stack identifier route variable" , err }
}
stack , err := handler . StackService . Stack ( portainer . StackID ( id ) )
2018-06-19 11:15:10 +00:00
if err == portainer . ErrObjectNotFound {
2018-06-11 13:13:19 +00:00
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 }
}
2018-06-15 15:14:01 +00:00
// TODO: this is a work-around for stacks created with Portainer version >= 1.17.1
// The EndpointID property is not available for these stacks, this API endpoint
// can use the optional EndpointID query parameter to set a valid endpoint identifier to be
// used in the context of this request.
endpointID , err := request . RetrieveNumericQueryParameter ( r , "endpointId" , true )
if err != nil {
return & httperror . HandlerError { http . StatusBadRequest , "Invalid query parameter: endpointId" , err }
}
endpointIdentifier := stack . EndpointID
if endpointID != 0 {
endpointIdentifier = portainer . EndpointID ( endpointID )
}
endpoint , err := handler . EndpointService . Endpoint ( endpointIdentifier )
2018-06-19 11:15:10 +00:00
if err == portainer . ErrObjectNotFound {
2018-06-11 13:13:19 +00:00
return & httperror . HandlerError { http . StatusNotFound , "Unable to find the endpoint associated to the stack inside the database" , err }
} else if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to find the endpoint associated to the stack inside the database" , err }
}
2019-05-24 06:04:58 +00:00
err = handler . requestBouncer . AuthorizedEndpointOperation ( r , endpoint , true )
if err != nil {
return & httperror . HandlerError { http . StatusForbidden , "Permission denied to access endpoint" , err }
}
2019-11-12 23:41:42 +00:00
resourceControl , err := handler . ResourceControlService . ResourceControlByResourceIDAndType ( stack . Name , portainer . StackResourceControl )
if err != nil {
2019-05-24 06:04:58 +00:00
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to retrieve a resource control associated to the stack" , err }
}
securityContext , err := security . RetrieveRestrictedRequestContext ( r )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to retrieve info from request context" , err }
}
2019-11-12 23:41:42 +00:00
access , err := handler . userCanAccessStack ( securityContext , endpoint . ID , resourceControl )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to verify user authorizations to validate stack access" , err }
}
if ! access {
return & httperror . HandlerError { http . StatusForbidden , "Access denied to resource" , portainer . ErrResourceAccessDenied }
2019-05-24 06:04:58 +00:00
}
2018-06-11 13:13:19 +00:00
err = handler . deleteStack ( stack , endpoint )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , err . Error ( ) , err }
}
2018-06-18 10:07:56 +00:00
err = handler . StackService . DeleteStack ( portainer . StackID ( id ) )
2018-06-11 13:13:19 +00:00
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to remove the stack from the database" , err }
}
2019-11-12 23:41:42 +00:00
if resourceControl != nil {
err = handler . ResourceControlService . DeleteResourceControl ( resourceControl . ID )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to remove the associated resource control from the database" , err }
}
}
2018-06-11 13:13:19 +00:00
err = handler . FileService . RemoveDirectory ( stack . ProjectPath )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to remove stack files from disk" , err }
}
return response . Empty ( w )
}
func ( handler * Handler ) deleteExternalStack ( r * http . Request , w http . ResponseWriter , stackName string ) * httperror . HandlerError {
stack , err := handler . StackService . StackByName ( stackName )
2018-06-19 11:15:10 +00:00
if err != nil && err != portainer . ErrObjectNotFound {
2018-06-11 13:13:19 +00:00
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to check for stack existence inside the database" , err }
}
if stack != nil {
return & httperror . HandlerError { http . StatusBadRequest , "A stack with this name exists inside the database. Cannot use external delete method" , portainer . ErrStackNotExternal }
}
endpointID , err := request . RetrieveNumericQueryParameter ( r , "endpointId" , false )
if err != nil {
return & httperror . HandlerError { http . StatusBadRequest , "Invalid query parameter: endpointId" , err }
}
endpoint , err := handler . EndpointService . Endpoint ( portainer . EndpointID ( endpointID ) )
2018-06-19 11:15:10 +00:00
if err == portainer . ErrObjectNotFound {
2018-06-11 13:13:19 +00:00
return & httperror . HandlerError { http . StatusNotFound , "Unable to find the endpoint associated to the stack inside the database" , err }
} else if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to find the endpoint associated to the stack inside the database" , err }
}
2019-05-24 06:04:58 +00:00
err = handler . requestBouncer . AuthorizedEndpointOperation ( r , endpoint , true )
2018-06-11 13:13:19 +00:00
if err != nil {
2019-05-24 06:04:58 +00:00
return & httperror . HandlerError { http . StatusForbidden , "Permission denied to access endpoint" , err }
2018-06-11 13:13:19 +00:00
}
stack = & portainer . Stack {
Name : stackName ,
Type : portainer . DockerSwarmStack ,
}
err = handler . deleteStack ( stack , endpoint )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to delete stack" , err }
}
return response . Empty ( w )
}
func ( handler * Handler ) deleteStack ( stack * portainer . Stack , endpoint * portainer . Endpoint ) error {
if stack . Type == portainer . DockerSwarmStack {
return handler . SwarmStackManager . Remove ( stack , endpoint )
}
return handler . ComposeStackManager . Down ( stack , endpoint )
}