2018-06-11 13:13:19 +00:00
package stacks
import (
"net/http"
2021-08-17 01:12:07 +00:00
httperrors "github.com/portainer/portainer/api/http/errors"
2018-09-10 10:01:38 +00:00
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
2021-02-23 03:21:39 +00:00
portainer "github.com/portainer/portainer/api"
2019-03-21 01:20:14 +00:00
"github.com/portainer/portainer/api/http/security"
2020-06-16 07:58:16 +00:00
"github.com/portainer/portainer/api/internal/authorization"
2018-06-11 13:13:19 +00:00
)
type stackListOperationFilters struct {
2021-06-10 02:52:33 +00:00
SwarmID string ` json:"SwarmID" `
EndpointID int ` json:"EndpointID" `
IncludeOrphanedStacks bool ` json:"IncludeOrphanedStacks" `
2018-06-11 13:13:19 +00:00
}
2021-02-23 03:21:39 +00:00
// @id StackList
// @summary List stacks
// @description List all stacks based on the current user authorizations.
// @description Will return all stacks if using an administrator account otherwise it
// @description will only return the list of stacks the user have access to.
2021-10-11 23:12:08 +00:00
// @description **Access policy**: authenticated
2021-02-23 03:21:39 +00:00
// @tags stacks
2021-11-30 02:31:16 +00:00
// @security ApiKeyAuth
2021-02-23 03:21:39 +00:00
// @security jwt
2021-10-19 00:25:53 +00:00
// @param filters query string false "Filters to process on the stack list. Encoded as JSON (a map[string]string). For example, {'SwarmID': 'jpofkc0i9uo9wtx1zesuk649w'} will only return stacks that are part of the specified Swarm cluster. Available filters: EndpointID, SwarmID."
2021-02-23 03:21:39 +00:00
// @success 200 {array} portainer.Stack "Success"
// @success 204 "Success"
// @failure 400 "Invalid request"
// @failure 500 "Server error"
// @router /stacks [get]
2018-06-11 13:13:19 +00:00
func ( handler * Handler ) stackList ( w http . ResponseWriter , r * http . Request ) * httperror . HandlerError {
var filters stackListOperationFilters
err := request . RetrieveJSONQueryParameter ( r , "filters" , & filters , true )
if err != nil {
2022-09-14 23:42:39 +00:00
return httperror . BadRequest ( "Invalid query parameter: filters" , err )
2018-06-11 13:13:19 +00:00
}
2021-06-10 02:52:33 +00:00
endpoints , err := handler . DataStore . Endpoint ( ) . Endpoints ( )
if err != nil {
2022-09-14 23:42:39 +00:00
return httperror . InternalServerError ( "Unable to retrieve environments from database" , err )
2021-06-10 02:52:33 +00:00
}
2020-05-20 05:23:15 +00:00
stacks , err := handler . DataStore . Stack ( ) . Stacks ( )
2018-06-11 13:13:19 +00:00
if err != nil {
2022-09-14 23:42:39 +00:00
return httperror . InternalServerError ( "Unable to retrieve stacks from the database" , err )
2018-06-11 13:13:19 +00:00
}
2021-06-10 02:52:33 +00:00
stacks = filterStacks ( stacks , & filters , endpoints )
2018-06-11 13:13:19 +00:00
2020-05-20 05:23:15 +00:00
resourceControls , err := handler . DataStore . ResourceControl ( ) . ResourceControls ( )
2018-06-11 13:13:19 +00:00
if err != nil {
2022-09-14 23:42:39 +00:00
return httperror . InternalServerError ( "Unable to retrieve resource controls from the database" , err )
2018-06-11 13:13:19 +00:00
}
securityContext , err := security . RetrieveRestrictedRequestContext ( r )
if err != nil {
2022-09-14 23:42:39 +00:00
return httperror . InternalServerError ( "Unable to retrieve info from request context" , err )
2018-06-11 13:13:19 +00:00
}
2020-06-16 07:58:16 +00:00
stacks = authorization . DecorateStacks ( stacks , resourceControls )
2018-06-11 13:13:19 +00:00
2019-11-12 23:41:42 +00:00
if ! securityContext . IsAdmin {
2021-06-10 02:52:33 +00:00
if filters . IncludeOrphanedStacks {
2022-09-14 23:42:39 +00:00
return httperror . Forbidden ( "Permission denied to access orphaned stacks" , httperrors . ErrUnauthorized )
2021-06-10 02:52:33 +00:00
}
2020-05-20 05:23:15 +00:00
user , err := handler . DataStore . User ( ) . User ( securityContext . UserID )
2019-11-12 23:41:42 +00:00
if err != nil {
2022-09-14 23:42:39 +00:00
return httperror . InternalServerError ( "Unable to retrieve user information from the database" , err )
2019-11-12 23:41:42 +00:00
}
userTeamIDs := make ( [ ] portainer . TeamID , 0 )
for _ , membership := range securityContext . UserMemberships {
userTeamIDs = append ( userTeamIDs , membership . TeamID )
}
2020-08-11 05:41:37 +00:00
stacks = authorization . FilterAuthorizedStacks ( stacks , user , userTeamIDs )
2019-11-12 23:41:42 +00:00
}
2021-08-17 01:12:07 +00:00
for _ , stack := range stacks {
if stack . GitConfig != nil && stack . GitConfig . Authentication != nil && stack . GitConfig . Authentication . Password != "" {
// sanitize password in the http response to minimise possible security leaks
stack . GitConfig . Authentication . Password = ""
}
}
2019-11-12 23:41:42 +00:00
return response . JSON ( w , stacks )
2018-06-11 13:13:19 +00:00
}
2021-06-10 02:52:33 +00:00
func filterStacks ( stacks [ ] portainer . Stack , filters * stackListOperationFilters , endpoints [ ] portainer . Endpoint ) [ ] portainer . Stack {
2018-06-11 13:13:19 +00:00
if filters . EndpointID == 0 && filters . SwarmID == "" {
return stacks
}
filteredStacks := make ( [ ] portainer . Stack , 0 , len ( stacks ) )
for _ , stack := range stacks {
2021-06-10 02:52:33 +00:00
if filters . IncludeOrphanedStacks && isOrphanedStack ( stack , endpoints ) {
if ( stack . Type == portainer . DockerComposeStack && filters . SwarmID == "" ) || ( stack . Type == portainer . DockerSwarmStack && filters . SwarmID != "" ) {
filteredStacks = append ( filteredStacks , stack )
}
continue
}
2018-06-11 13:13:19 +00:00
if stack . Type == portainer . DockerComposeStack && stack . EndpointID == portainer . EndpointID ( filters . EndpointID ) {
filteredStacks = append ( filteredStacks , stack )
}
if stack . Type == portainer . DockerSwarmStack && stack . SwarmID == filters . SwarmID {
filteredStacks = append ( filteredStacks , stack )
}
}
return filteredStacks
}
2021-06-10 02:52:33 +00:00
func isOrphanedStack ( stack portainer . Stack , endpoints [ ] portainer . Endpoint ) bool {
for _ , endpoint := range endpoints {
if stack . EndpointID == endpoint . ID {
return false
}
}
return true
}