2018-06-11 13:13:19 +00:00
package stacks
import (
2020-07-07 21:57:52 +00:00
"errors"
2018-06-11 13:13:19 +00:00
"net/http"
2018-06-18 10:07:56 +00:00
"strconv"
2018-06-11 13:13:19 +00:00
2018-09-10 10:01:38 +00:00
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
2020-05-13 03:37:35 +00:00
portainer "github.com/portainer/portainer/api"
2020-07-07 21:57:52 +00:00
bolterrors "github.com/portainer/portainer/api/bolt/errors"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
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 }
}
2020-05-13 03:37:35 +00:00
securityContext , err := security . RetrieveRestrictedRequestContext ( r )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to retrieve info from request context" , err }
}
2018-06-11 13:13:19 +00:00
externalStack , _ := request . RetrieveBooleanQueryParameter ( r , "external" , true )
if externalStack {
2020-05-13 03:37:35 +00:00
return handler . deleteExternalStack ( r , w , stackID , securityContext )
2018-06-11 13:13:19 +00:00
}
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 }
}
2020-05-20 05:23:15 +00:00
stack , err := handler . DataStore . Stack ( ) . Stack ( portainer . StackID ( id ) )
2020-07-07 21:57:52 +00:00
if err == bolterrors . 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 )
}
2020-05-20 05:23:15 +00:00
endpoint , err := handler . DataStore . Endpoint ( ) . Endpoint ( endpointIdentifier )
2020-07-07 21:57:52 +00:00
if err == bolterrors . 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 }
}
2020-05-20 05:23:15 +00:00
resourceControl , err := handler . DataStore . ResourceControl ( ) . ResourceControlByResourceIDAndType ( stack . Name , portainer . StackResourceControl )
2019-11-12 23:41:42 +00:00
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 }
}
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 {
2020-07-07 21:57:52 +00:00
return & httperror . HandlerError { http . StatusForbidden , "Access denied to resource" , httperrors . 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 }
}
2020-05-20 05:23:15 +00:00
err = handler . DataStore . Stack ( ) . 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 {
2020-05-20 05:23:15 +00:00
err = handler . DataStore . ResourceControl ( ) . DeleteResourceControl ( resourceControl . ID )
2019-11-12 23:41:42 +00:00
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 )
}
2020-05-13 03:37:35 +00:00
func ( handler * Handler ) deleteExternalStack ( r * http . Request , w http . ResponseWriter , stackName string , securityContext * security . RestrictedRequestContext ) * httperror . HandlerError {
endpointID , err := request . RetrieveNumericQueryParameter ( r , "endpointId" , false )
if err != nil {
return & httperror . HandlerError { http . StatusBadRequest , "Invalid query parameter: endpointId" , err }
}
2020-05-20 05:23:15 +00:00
user , err := handler . DataStore . User ( ) . User ( securityContext . UserID )
2020-05-13 03:37:35 +00:00
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to load user information from the database" , err }
}
2020-05-20 05:23:15 +00:00
rbacExtension , err := handler . DataStore . Extension ( ) . Extension ( portainer . RBACExtension )
2020-07-07 21:57:52 +00:00
if err != nil && err != bolterrors . ErrObjectNotFound {
2020-05-13 03:37:35 +00:00
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to verify if RBAC extension is loaded" , err }
}
endpointResourceAccess := false
_ , ok := user . EndpointAuthorizations [ portainer . EndpointID ( endpointID ) ] [ portainer . EndpointResourcesAccess ]
if ok {
endpointResourceAccess = true
}
if rbacExtension != nil {
if ! securityContext . IsAdmin && ! endpointResourceAccess {
2020-07-07 21:57:52 +00:00
return & httperror . HandlerError { http . StatusUnauthorized , "Permission denied to delete the stack" , httperrors . ErrUnauthorized }
2020-05-13 03:37:35 +00:00
}
} else {
if ! securityContext . IsAdmin {
2020-07-07 21:57:52 +00:00
return & httperror . HandlerError { http . StatusUnauthorized , "Permission denied to delete the stack" , httperrors . ErrUnauthorized }
2020-05-13 03:37:35 +00:00
}
}
2020-05-20 05:23:15 +00:00
stack , err := handler . DataStore . Stack ( ) . StackByName ( stackName )
2020-07-07 21:57:52 +00:00
if err != nil && err != bolterrors . 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 {
2020-07-07 21:57:52 +00:00
return & httperror . HandlerError { http . StatusBadRequest , "A stack with this name exists inside the database. Cannot use external delete method" , errors . New ( "A tag already exists with this name" ) }
2018-06-11 13:13:19 +00:00
}
2020-05-20 05:23:15 +00:00
endpoint , err := handler . DataStore . Endpoint ( ) . Endpoint ( portainer . EndpointID ( endpointID ) )
2020-07-07 21:57:52 +00:00
if err == bolterrors . 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 )
}