2018-06-11 13:13:19 +00:00
package endpoints
import (
2023-07-17 20:36:00 +00:00
"errors"
2024-05-01 01:32:20 +00:00
"fmt"
2018-06-11 13:13:19 +00:00
"net/http"
2023-09-06 01:35:16 +00:00
"slices"
2018-06-11 13:13:19 +00:00
"strconv"
2021-02-23 03:21:39 +00:00
portainer "github.com/portainer/portainer/api"
2023-07-17 20:36:00 +00:00
"github.com/portainer/portainer/api/dataservices"
2023-01-17 12:47:23 +00:00
"github.com/portainer/portainer/api/internal/endpointutils"
2023-09-01 22:27:02 +00:00
httperror "github.com/portainer/portainer/pkg/libhttp/error"
"github.com/portainer/portainer/pkg/libhttp/request"
"github.com/portainer/portainer/pkg/libhttp/response"
2023-07-17 20:36:00 +00:00
2023-07-12 01:20:13 +00:00
"github.com/rs/zerolog/log"
2018-06-11 13:13:19 +00:00
)
2024-05-28 12:05:35 +00:00
type endpointDeleteRequest struct {
ID int ` json:"id" `
DeleteCluster bool ` json:"deleteCluster" `
2024-05-01 01:32:20 +00:00
}
2024-05-28 12:05:35 +00:00
type endpointDeleteBatchPayload struct {
Endpoints [ ] endpointDeleteRequest ` json:"endpoints" `
}
type endpointDeleteBatchPartialResponse struct {
Deleted [ ] int ` json:"deleted" `
Errors [ ] int ` json:"errors" `
}
func ( payload * endpointDeleteBatchPayload ) Validate ( r * http . Request ) error {
2024-05-01 01:32:20 +00:00
if payload == nil || len ( payload . Endpoints ) == 0 {
2024-05-28 12:05:35 +00:00
return fmt . Errorf ( "invalid request payload. You must provide a list of environments to delete" )
2024-05-01 01:32:20 +00:00
}
2024-05-17 19:45:06 +00:00
2024-05-01 01:32:20 +00:00
return nil
}
2021-02-23 03:21:39 +00:00
// @id EndpointDelete
2024-05-28 12:05:35 +00:00
// @summary Remove an environment
// @description Remove the environment associated to the specified identifier and optionally clean-up associated resources.
// @description **Access policy**: Administrator only.
2021-02-23 03:21:39 +00:00
// @tags endpoints
2024-05-28 12:05:35 +00:00
// @security ApiKeyAuth || jwt
2021-09-20 00:14:22 +00:00
// @param id path int true "Environment(Endpoint) identifier"
2024-05-28 12:05:35 +00:00
// @success 204 "Environment successfully deleted."
// @failure 400 "Invalid request payload, such as missing required fields or fields not meeting validation criteria."
// @failure 403 "Unauthorized access or operation not allowed."
// @failure 404 "Unable to find the environment with the specified identifier inside the database."
// @failure 500 "Server error occurred while attempting to delete the environment."
2021-02-23 03:21:39 +00:00
// @router /endpoints/{id} [delete]
2018-06-11 13:13:19 +00:00
func ( handler * Handler ) endpointDelete ( w http . ResponseWriter , r * http . Request ) * httperror . HandlerError {
endpointID , err := request . RetrieveNumericRouteVariableValue ( r , "id" )
if err != nil {
2022-09-14 23:42:39 +00:00
return httperror . BadRequest ( "Invalid environment identifier route variable" , err )
2018-06-11 13:13:19 +00:00
}
2023-07-17 20:36:00 +00:00
// This is a Portainer provisioned cloud environment
deleteCluster , err := request . RetrieveBooleanQueryParameter ( r , "deleteCluster" , true )
if err != nil {
return httperror . BadRequest ( "Invalid boolean query parameter" , err )
}
2024-06-18 18:59:12 +00:00
if err := handler . DataStore . UpdateTx ( func ( tx dataservices . DataStoreTx ) error {
2023-09-05 23:27:20 +00:00
return handler . deleteEndpoint ( tx , portainer . EndpointID ( endpointID ) , deleteCluster )
2024-06-18 18:59:12 +00:00
} ) ; err != nil {
2023-07-17 20:36:00 +00:00
var handlerError * httperror . HandlerError
if errors . As ( err , & handlerError ) {
return handlerError
}
return httperror . InternalServerError ( "Unexpected error" , err )
}
return response . Empty ( w )
}
2024-05-28 12:05:35 +00:00
// @id EndpointDeleteBatch
// @summary Remove multiple environments
// @description Remove multiple environments and optionally clean-up associated resources.
// @description **Access policy**: Administrator only.
2024-05-01 01:32:20 +00:00
// @tags endpoints
2024-05-28 12:05:35 +00:00
// @security ApiKeyAuth || jwt
2024-05-01 01:32:20 +00:00
// @accept json
// @produce json
2024-06-18 18:59:12 +00:00
// @param body body endpointDeleteBatchPayload true "List of environments to delete, with optional deleteCluster flag to clean-up associated resources (cloud environments only)"
2024-05-28 12:05:35 +00:00
// @success 204 "Environment(s) successfully deleted."
// @failure 207 {object} endpointDeleteBatchPartialResponse "Partial success. Some environments were deleted successfully, while others failed."
// @failure 400 "Invalid request payload, such as missing required fields or fields not meeting validation criteria."
// @failure 403 "Unauthorized access or operation not allowed."
// @failure 500 "Server error occurred while attempting to delete the specified environments."
// @router /endpoints [delete]
func ( handler * Handler ) endpointDeleteBatch ( w http . ResponseWriter , r * http . Request ) * httperror . HandlerError {
var p endpointDeleteBatchPayload
2024-05-17 19:45:06 +00:00
if err := request . DecodeAndValidateJSONPayload ( r , & p ) ; err != nil {
2024-05-01 01:32:20 +00:00
return httperror . BadRequest ( "Invalid request payload" , err )
}
2024-05-28 12:05:35 +00:00
resp := endpointDeleteBatchPartialResponse {
Deleted : [ ] int { } ,
Errors : [ ] int { } ,
}
2024-05-01 01:32:20 +00:00
2024-05-28 12:05:35 +00:00
if err := handler . DataStore . UpdateTx ( func ( tx dataservices . DataStoreTx ) error {
2024-05-17 19:45:06 +00:00
for _ , e := range p . Endpoints {
2024-05-28 12:05:35 +00:00
if err := handler . deleteEndpoint ( tx , portainer . EndpointID ( e . ID ) , e . DeleteCluster ) ; err != nil {
resp . Errors = append ( resp . Errors , e . ID )
log . Warn ( ) . Err ( err ) . Int ( "environment_id" , e . ID ) . Msg ( "Unable to remove environment" )
continue
}
resp . Deleted = append ( resp . Deleted , e . ID )
2024-05-17 19:45:06 +00:00
}
return nil
2024-05-28 12:05:35 +00:00
} ) ; err != nil {
2024-05-17 19:45:06 +00:00
return httperror . InternalServerError ( "Unable to delete environments" , err )
2024-05-01 01:32:20 +00:00
}
2024-05-17 19:45:06 +00:00
2024-05-28 12:05:35 +00:00
if len ( resp . Errors ) > 0 {
return response . JSONWithStatus ( w , resp , http . StatusPartialContent )
}
return response . Empty ( w )
2024-05-01 01:32:20 +00:00
}
2023-07-17 20:36:00 +00:00
func ( handler * Handler ) deleteEndpoint ( tx dataservices . DataStoreTx , endpointID portainer . EndpointID , deleteCluster bool ) error {
2024-06-10 12:32:52 +00:00
endpoint , err := tx . Endpoint ( ) . Endpoint ( endpointID )
2023-07-17 20:36:00 +00:00
if tx . IsErrObjectNotFound ( err ) {
2022-09-14 23:42:39 +00:00
return httperror . NotFound ( "Unable to find an environment with the specified identifier inside the database" , err )
2018-06-11 13:13:19 +00:00
} else if err != nil {
2023-07-12 01:20:13 +00:00
return httperror . InternalServerError ( "Unable to read the environment record from the database" , err )
2018-06-11 13:13:19 +00:00
}
if endpoint . TLSConfig . TLS {
2023-07-17 20:36:00 +00:00
folder := strconv . Itoa ( int ( endpointID ) )
2024-06-18 18:59:12 +00:00
if err := handler . FileService . DeleteTLSFiles ( folder ) ; err != nil {
2023-07-12 01:20:13 +00:00
log . Error ( ) . Err ( err ) . Msgf ( "Unable to remove TLS files from disk when deleting endpoint %d" , endpointID )
2018-06-11 13:13:19 +00:00
}
}
2024-06-18 18:59:12 +00:00
if err := tx . Snapshot ( ) . Delete ( endpointID ) ; err != nil {
2024-08-10 09:53:16 +00:00
log . Warn ( ) . Err ( err ) . Msg ( "Unable to remove the snapshot from the database" )
2018-06-11 13:13:19 +00:00
}
2021-10-15 05:13:20 +00:00
handler . ProxyManager . DeleteEndpointProxy ( endpoint . ID )
2018-06-11 13:13:19 +00:00
2023-07-17 20:36:00 +00:00
if len ( endpoint . UserAccessPolicies ) > 0 || len ( endpoint . TeamAccessPolicies ) > 0 {
2024-06-18 18:59:12 +00:00
if err := handler . AuthorizationService . UpdateUsersAuthorizationsTx ( tx ) ; err != nil {
2024-08-10 09:53:16 +00:00
log . Warn ( ) . Err ( err ) . Msg ( "Unable to update user authorizations" )
2023-07-17 20:36:00 +00:00
}
}
2024-06-18 18:59:12 +00:00
if err := tx . EndpointRelation ( ) . DeleteEndpointRelation ( endpoint . ID ) ; err != nil {
2024-08-10 09:53:16 +00:00
log . Warn ( ) . Err ( err ) . Msg ( "Unable to remove environment relation from the database" )
2020-05-14 02:14:28 +00:00
}
for _ , tagID := range endpoint . TagIDs {
2023-09-05 23:27:20 +00:00
var tag * portainer . Tag
tag , err = tx . Tag ( ) . Read ( tagID )
if err == nil {
delete ( tag . Endpoints , endpoint . ID )
err = tx . Tag ( ) . Update ( tagID , tag )
2023-07-17 20:36:00 +00:00
}
2020-05-14 02:14:28 +00:00
2022-10-13 14:12:12 +00:00
if handler . DataStore . IsErrObjectNotFound ( err ) {
2024-08-10 09:53:16 +00:00
log . Warn ( ) . Err ( err ) . Msg ( "Unable to find tag inside the database" )
2022-10-13 14:12:12 +00:00
} else if err != nil {
2024-08-10 09:53:16 +00:00
log . Warn ( ) . Err ( err ) . Msg ( "Unable to delete tag relation from the database" )
2020-05-14 02:14:28 +00:00
}
}
2023-07-17 20:36:00 +00:00
edgeGroups , err := tx . EdgeGroup ( ) . ReadAll ( )
2020-05-14 02:14:28 +00:00
if err != nil {
2023-07-12 01:20:13 +00:00
log . Warn ( ) . Err ( err ) . Msgf ( "Unable to retrieve edge groups from the database" )
2020-05-14 02:14:28 +00:00
}
2022-11-24 00:36:17 +00:00
for _ , edgeGroup := range edgeGroups {
2023-09-06 01:35:16 +00:00
edgeGroup . Endpoints = slices . DeleteFunc ( edgeGroup . Endpoints , func ( e portainer . EndpointID ) bool {
return e == endpoint . ID
} )
2023-07-17 20:36:00 +00:00
2024-06-18 18:59:12 +00:00
if err := tx . EdgeGroup ( ) . Update ( edgeGroup . ID , & edgeGroup ) ; err != nil {
2024-08-10 09:53:16 +00:00
log . Warn ( ) . Err ( err ) . Msg ( "Unable to update edge group" )
2020-05-14 02:14:28 +00:00
}
}
2023-07-17 20:36:00 +00:00
edgeStacks , err := tx . EdgeStack ( ) . EdgeStacks ( )
2020-05-14 02:14:28 +00:00
if err != nil {
2024-08-10 09:53:16 +00:00
log . Warn ( ) . Err ( err ) . Msg ( "Unable to retrieve edge stacks from the database" )
2020-05-14 02:14:28 +00:00
}
for idx := range edgeStacks {
edgeStack := & edgeStacks [ idx ]
if _ , ok := edgeStack . Status [ endpoint . ID ] ; ok {
delete ( edgeStack . Status , endpoint . ID )
2024-08-10 09:53:16 +00:00
if err := tx . EdgeStack ( ) . UpdateEdgeStack ( edgeStack . ID , edgeStack ) ; err != nil {
log . Warn ( ) . Err ( err ) . Msg ( "Unable to update edge stack" )
2020-05-14 02:14:28 +00:00
}
}
}
2023-07-17 20:36:00 +00:00
registries , err := tx . Registry ( ) . ReadAll ( )
2021-07-14 09:15:21 +00:00
if err != nil {
2024-08-10 09:53:16 +00:00
log . Warn ( ) . Err ( err ) . Msg ( "Unable to retrieve registries from the database" )
2021-07-14 09:15:21 +00:00
}
for idx := range registries {
registry := & registries [ idx ]
if _ , ok := registry . RegistryAccesses [ endpoint . ID ] ; ok {
delete ( registry . RegistryAccesses , endpoint . ID )
2024-08-10 09:53:16 +00:00
if err := tx . Registry ( ) . Update ( registry . ID , registry ) ; err != nil {
log . Warn ( ) . Err ( err ) . Msg ( "Unable to update registry accesses" )
2021-07-14 09:15:21 +00:00
}
}
}
2023-07-12 01:20:13 +00:00
if endpointutils . IsEdgeEndpoint ( endpoint ) {
edgeJobs , err := handler . DataStore . EdgeJob ( ) . ReadAll ( )
if err != nil {
2024-08-10 09:53:16 +00:00
log . Warn ( ) . Err ( err ) . Msg ( "Unable to retrieve edge jobs from the database" )
2023-07-12 01:20:13 +00:00
}
2023-01-17 12:47:23 +00:00
2023-07-12 01:20:13 +00:00
for idx := range edgeJobs {
edgeJob := & edgeJobs [ idx ]
if _ , ok := edgeJob . Endpoints [ endpoint . ID ] ; ok {
2023-09-05 23:27:20 +00:00
delete ( edgeJob . Endpoints , endpoint . ID )
2023-01-17 12:47:23 +00:00
2024-08-10 09:53:16 +00:00
if err := tx . EdgeJob ( ) . Update ( edgeJob . ID , edgeJob ) ; err != nil {
log . Warn ( ) . Err ( err ) . Msg ( "Unable to update edge job" )
2023-07-12 01:20:13 +00:00
}
2023-01-17 12:47:23 +00:00
}
}
}
2024-04-16 03:09:10 +00:00
// delete the pending actions
2024-06-18 18:59:12 +00:00
if err := tx . PendingActions ( ) . DeleteByEndpointID ( endpoint . ID ) ; err != nil {
2024-08-10 09:53:16 +00:00
log . Warn ( ) . Err ( err ) . Int ( "endpointId" , int ( endpoint . ID ) ) . Msg ( "Unable to delete pending actions" )
2024-04-16 03:09:10 +00:00
}
2024-06-18 18:59:12 +00:00
if err := tx . Endpoint ( ) . DeleteEndpoint ( endpointID ) ; err != nil {
2023-07-17 20:36:00 +00:00
return httperror . InternalServerError ( "Unable to delete the environment from the database" , err )
2023-07-12 01:20:13 +00:00
}
2023-07-17 20:36:00 +00:00
return nil
2018-06-11 13:13:19 +00:00
}