2018-06-11 13:13:19 +00:00
package stacks
import (
"net/http"
"github.com/portainer/portainer"
httperror "github.com/portainer/portainer/http/error"
"github.com/portainer/portainer/http/proxy"
"github.com/portainer/portainer/http/request"
"github.com/portainer/portainer/http/response"
"github.com/portainer/portainer/http/security"
)
// DELETE request on /api/stacks/:id?external=<external>&endpointId=<endpointId>
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 )
}
stack , err := handler . StackService . Stack ( portainer . StackID ( stackID ) )
if err == portainer . ErrStackNotFound {
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 }
}
resourceControl , err := handler . ResourceControlService . ResourceControlByResourceID ( stack . Name )
if err != nil && err != portainer . ErrResourceControlNotFound {
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 }
}
if resourceControl != nil {
if ! securityContext . IsAdmin && ! proxy . CanAccessStack ( stack , resourceControl , securityContext . UserID , securityContext . UserMemberships ) {
return & httperror . HandlerError { http . StatusForbidden , "Access denied to resource" , portainer . ErrResourceAccessDenied }
}
}
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-11 13:13:19 +00:00
if err == portainer . ErrEndpointNotFound {
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 }
}
err = handler . deleteStack ( stack , endpoint )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , err . Error ( ) , err }
}
err = handler . StackService . DeleteStack ( portainer . StackID ( stackID ) )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to remove the stack from the database" , err }
}
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 )
if err != nil && err != portainer . ErrStackNotFound {
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 ) )
if err == portainer . ErrEndpointNotFound {
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 }
}
tokenData , err := security . RetrieveTokenData ( r )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to retrieve user authentication token" , err }
}
if tokenData . Role != portainer . AdministratorRole {
err = handler . checkEndpointAccess ( endpoint , tokenData . ID )
if err != nil && err == portainer . ErrEndpointAccessDenied {
return & httperror . HandlerError { http . StatusForbidden , "Permission denied to access endpoint" , portainer . ErrEndpointAccessDenied }
} else if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to verify permission to access endpoint" , err }
}
}
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 )
}