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"
2021-02-23 20:18:05 +00:00
"github.com/portainer/portainer/api/internal/stackutils"
2018-06-11 13:13:19 +00:00
)
2021-02-23 03:21:39 +00:00
// @id StackDelete
// @summary Remove a stack
// @description Remove a stack.
// @description **Access policy**: restricted
// @tags stacks
// @security jwt
// @param id path int true "Stack identifier"
// @param external query boolean false "Set to true to delete an external stack. Only external Swarm stacks are supported"
// @param endpointId query int false "Endpoint identifier used to remove an external stack (required when external is set to true)"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 403 "Permission denied"
// @failure 404 " not found"
// @failure 500 "Server error"
// @router /stacks/{id} [delete]
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
endpointID , err := request . RetrieveNumericQueryParameter ( r , "endpointId" , true )
if err != nil {
return & httperror . HandlerError { http . StatusBadRequest , "Invalid query parameter: endpointId" , err }
}
2021-06-10 02:52:33 +00:00
isOrphaned := portainer . EndpointID ( endpointID ) != stack . EndpointID
if isOrphaned && ! securityContext . IsAdmin {
return & httperror . HandlerError { http . StatusForbidden , "Permission denied to remove orphaned stack" , errors . New ( "Permission denied to remove orphaned stack" ) }
2018-06-15 15:14:01 +00:00
}
2021-06-10 02:52:33 +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 }
}
2021-02-23 20:18:05 +00:00
resourceControl , err := handler . DataStore . ResourceControl ( ) . ResourceControlByResourceIDAndType ( stackutils . ResourceControlID ( stack . EndpointID , 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 }
}
2021-06-10 02:52:33 +00:00
if ! isOrphaned {
err = handler . requestBouncer . AuthorizedEndpointOperation ( r , endpoint )
if err != nil {
return & httperror . HandlerError { http . StatusForbidden , "Permission denied to access endpoint" , err }
}
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" , 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-08-11 05:41:37 +00:00
if ! securityContext . IsAdmin {
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 }
}
2020-08-11 05:41:37 +00:00
err = handler . requestBouncer . AuthorizedEndpointOperation ( r , endpoint )
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 )
}
2021-01-25 19:16:53 +00:00
2018-06-11 13:13:19 +00:00
return handler . ComposeStackManager . Down ( stack , endpoint )
}