2018-06-11 13:13:19 +00:00
package endpoints
import (
"net/http"
2022-04-26 00:17:36 +00:00
"sort"
2019-07-20 23:28:11 +00:00
"strconv"
"strings"
2022-03-14 01:53:23 +00:00
"time"
2019-07-20 23:28:11 +00:00
"github.com/portainer/libhttp/request"
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/response"
2021-01-25 19:16:53 +00:00
portainer "github.com/portainer/portainer/api"
2019-03-21 01:20:14 +00:00
"github.com/portainer/portainer/api/http/security"
2022-04-26 00:17:36 +00:00
"github.com/portainer/portainer/api/internal/endpointutils"
"github.com/portainer/portainer/api/internal/utils"
2018-06-11 13:13:19 +00:00
)
2022-04-19 18:43:36 +00:00
const (
EdgeDeviceFilterAll = "all"
EdgeDeviceFilterTrusted = "trusted"
EdgeDeviceFilterUntrusted = "untrusted"
2022-04-26 11:25:20 +00:00
EdgeDeviceFilterNone = "none"
2022-04-19 18:43:36 +00:00
)
2022-04-26 23:40:23 +00:00
const (
EdgeDeviceIntervalMultiplier = 2
EdgeDeviceIntervalAdd = 20
)
2022-04-26 00:17:36 +00:00
var endpointGroupNames map [ portainer . EndpointGroupID ] string
2021-02-23 03:21:39 +00:00
// @id EndpointList
2021-09-20 00:14:22 +00:00
// @summary List environments(endpoints)
// @description List all environments(endpoints) based on the current user authorizations. Will
// @description return all environments(endpoints) if using an administrator account otherwise it will
// @description only return authorized environments(endpoints).
2021-02-23 03:21:39 +00:00
// @description **Access policy**: restricted
// @tags endpoints
2021-11-30 02:31:16 +00:00
// @security ApiKeyAuth
2021-02-23 03:21:39 +00:00
// @security jwt
// @produce json
// @param start query int false "Start searching from"
// @param search query string false "Search query"
2021-09-20 00:14:22 +00:00
// @param groupId query int false "List environments(endpoints) of this group"
2021-02-23 03:21:39 +00:00
// @param limit query int false "Limit results to this value"
2021-09-20 00:14:22 +00:00
// @param types query []int false "List environments(endpoints) of this type"
// @param tagIds query []int false "search environments(endpoints) with these tags (depends on tagsPartialMatch)"
// @param tagsPartialMatch query bool false "If true, will return environment(endpoint) which has one of tagIds, if false (or missing) will return only environments(endpoints) that has all the tags"
// @param endpointIds query []int false "will return only these environments(endpoints)"
2022-04-26 11:25:20 +00:00
// @param edgeDeviceFilter query string false "will return only these edge environments, none will return only regular edge environments" Enum("all", "trusted", "untrusted", "none")
2021-02-23 03:21:39 +00:00
// @success 200 {array} portainer.Endpoint "Endpoints"
2021-09-13 03:42:53 +00:00
// @failure 500 "Server error"
2021-02-23 03:21:39 +00:00
// @router /endpoints [get]
2018-06-11 13:13:19 +00:00
func ( handler * Handler ) endpointList ( w http . ResponseWriter , r * http . Request ) * httperror . HandlerError {
2019-07-20 23:28:11 +00:00
start , _ := request . RetrieveNumericQueryParameter ( r , "start" , true )
if start != 0 {
start --
}
search , _ := request . RetrieveQueryParameter ( r , "search" , true )
if search != "" {
search = strings . ToLower ( search )
2018-06-11 13:13:19 +00:00
}
2019-07-20 23:28:11 +00:00
groupID , _ := request . RetrieveNumericQueryParameter ( r , "groupId" , true )
limit , _ := request . RetrieveNumericQueryParameter ( r , "limit" , true )
2022-04-26 00:17:36 +00:00
sortField , _ := request . RetrieveQueryParameter ( r , "sort" , true )
sortOrder , _ := request . RetrieveQueryParameter ( r , "order" , true )
2021-07-21 07:16:22 +00:00
var endpointTypes [ ] int
request . RetrieveJSONQueryParameter ( r , "types" , & endpointTypes , true )
2019-07-20 23:28:11 +00:00
2020-04-08 09:14:50 +00:00
var tagIDs [ ] portainer . TagID
request . RetrieveJSONQueryParameter ( r , "tagIds" , & tagIDs , true )
2020-05-14 02:14:28 +00:00
tagsPartialMatch , _ := request . RetrieveBooleanQueryParameter ( r , "tagsPartialMatch" , true )
2020-04-08 09:14:50 +00:00
var endpointIDs [ ] portainer . EndpointID
request . RetrieveJSONQueryParameter ( r , "endpointIds" , & endpointIDs , true )
2022-04-26 00:17:36 +00:00
var statuses [ ] int
request . RetrieveJSONQueryParameter ( r , "status" , & statuses , true )
var groupIDs [ ] int
request . RetrieveJSONQueryParameter ( r , "groupIds" , & groupIDs , true )
2020-05-20 05:23:15 +00:00
endpointGroups , err := handler . DataStore . EndpointGroup ( ) . EndpointGroups ( )
2018-06-11 13:13:19 +00:00
if err != nil {
2021-09-08 08:42:17 +00:00
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to retrieve environment groups from the database" , err }
2018-06-11 13:13:19 +00:00
}
2022-04-26 00:17:36 +00:00
// create endpoint groups as a map for more convenient access
endpointGroupNames = make ( map [ portainer . EndpointGroupID ] string , 0 )
for _ , group := range endpointGroups {
endpointGroupNames [ group . ID ] = group . Name
}
2020-05-20 05:23:15 +00:00
endpoints , err := handler . DataStore . Endpoint ( ) . Endpoints ( )
2019-07-20 23:28:11 +00:00
if err != nil {
2021-09-08 08:42:17 +00:00
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to retrieve environments from the database" , err }
2019-07-20 23:28:11 +00:00
}
2021-03-11 20:00:05 +00:00
settings , err := handler . DataStore . Settings ( ) . Settings ( )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to retrieve settings from the database" , err }
}
2018-06-11 13:13:19 +00:00
securityContext , err := security . RetrieveRestrictedRequestContext ( r )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to retrieve info from request context" , err }
}
filteredEndpoints := security . FilterEndpoints ( endpoints , endpointGroups , securityContext )
2022-03-08 12:14:23 +00:00
totalAvailableEndpoints := len ( filteredEndpoints )
2018-06-11 13:13:19 +00:00
2022-04-26 00:17:36 +00:00
if groupID != 0 {
filteredEndpoints = filterEndpointsByGroupIDs ( filteredEndpoints , [ ] int { groupID } )
}
2020-04-08 09:14:50 +00:00
if endpointIDs != nil {
filteredEndpoints = filteredEndpointsByIds ( filteredEndpoints , endpointIDs )
}
2022-04-26 00:17:36 +00:00
if len ( groupIDs ) > 0 {
filteredEndpoints = filterEndpointsByGroupIDs ( filteredEndpoints , groupIDs )
2019-07-20 23:28:11 +00:00
}
2022-04-19 18:43:36 +00:00
edgeDeviceFilter , _ := request . RetrieveQueryParameter ( r , "edgeDeviceFilter" , false )
if edgeDeviceFilter != "" {
2022-01-23 19:48:04 +00:00
filteredEndpoints = filterEndpointsByEdgeDevice ( filteredEndpoints , edgeDeviceFilter )
}
2022-04-26 00:17:36 +00:00
if len ( statuses ) > 0 {
2022-04-26 23:40:23 +00:00
filteredEndpoints = filterEndpointsByStatuses ( filteredEndpoints , statuses , settings )
2022-04-26 00:17:36 +00:00
}
2019-07-20 23:28:11 +00:00
if search != "" {
2020-05-20 05:23:15 +00:00
tags , err := handler . DataStore . Tag ( ) . Tags ( )
2020-03-29 09:54:14 +00:00
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to retrieve tags from the database" , err }
}
tagsMap := make ( map [ portainer . TagID ] string )
for _ , tag := range tags {
tagsMap [ tag . ID ] = tag . Name
}
filteredEndpoints = filterEndpointsBySearchCriteria ( filteredEndpoints , endpointGroups , tagsMap , search )
2019-07-20 23:28:11 +00:00
}
2021-07-21 07:16:22 +00:00
if endpointTypes != nil {
filteredEndpoints = filterEndpointsByTypes ( filteredEndpoints , endpointTypes )
2020-03-26 05:44:27 +00:00
}
2020-04-08 09:14:50 +00:00
if tagIDs != nil {
2020-05-14 02:14:28 +00:00
filteredEndpoints = filteredEndpointsByTags ( filteredEndpoints , tagIDs , endpointGroups , tagsPartialMatch )
2020-04-08 09:14:50 +00:00
}
2022-04-26 00:17:36 +00:00
// Sort endpoints by field
sortEndpointsByField ( filteredEndpoints , sortField , sortOrder == "desc" )
2019-07-20 23:28:11 +00:00
filteredEndpointCount := len ( filteredEndpoints )
paginatedEndpoints := paginateEndpoints ( filteredEndpoints , start , limit )
for idx := range paginatedEndpoints {
hideFields ( & paginatedEndpoints [ idx ] )
2021-01-25 19:16:53 +00:00
paginatedEndpoints [ idx ] . ComposeSyntaxMaxVersion = handler . ComposeStackManager . ComposeSyntaxMaxVersion ( )
2021-03-11 20:00:05 +00:00
if paginatedEndpoints [ idx ] . EdgeCheckinInterval == 0 {
paginatedEndpoints [ idx ] . EdgeCheckinInterval = settings . EdgeAgentCheckinInterval
}
2022-03-14 01:53:23 +00:00
paginatedEndpoints [ idx ] . QueryDate = time . Now ( ) . Unix ( )
2019-07-20 23:28:11 +00:00
}
w . Header ( ) . Set ( "X-Total-Count" , strconv . Itoa ( filteredEndpointCount ) )
2022-03-08 12:14:23 +00:00
w . Header ( ) . Set ( "X-Total-Available" , strconv . Itoa ( totalAvailableEndpoints ) )
2019-07-20 23:28:11 +00:00
return response . JSON ( w , paginatedEndpoints )
}
func paginateEndpoints ( endpoints [ ] portainer . Endpoint , start , limit int ) [ ] portainer . Endpoint {
if limit == 0 {
return endpoints
}
endpointCount := len ( endpoints )
if start > endpointCount {
start = endpointCount
}
end := start + limit
if end > endpointCount {
end = endpointCount
}
return endpoints [ start : end ]
}
2022-04-26 00:17:36 +00:00
func filterEndpointsByGroupIDs ( endpoints [ ] portainer . Endpoint , endpointGroupIDs [ ] int ) [ ] portainer . Endpoint {
2019-07-20 23:28:11 +00:00
filteredEndpoints := make ( [ ] portainer . Endpoint , 0 )
for _ , endpoint := range endpoints {
2022-04-26 00:17:36 +00:00
if utils . Contains ( endpointGroupIDs , int ( endpoint . GroupID ) ) {
2019-07-20 23:28:11 +00:00
filteredEndpoints = append ( filteredEndpoints , endpoint )
}
}
return filteredEndpoints
}
2020-03-29 09:54:14 +00:00
func filterEndpointsBySearchCriteria ( endpoints [ ] portainer . Endpoint , endpointGroups [ ] portainer . EndpointGroup , tagsMap map [ portainer . TagID ] string , searchCriteria string ) [ ] portainer . Endpoint {
2019-07-20 23:28:11 +00:00
filteredEndpoints := make ( [ ] portainer . Endpoint , 0 )
for _ , endpoint := range endpoints {
2020-03-29 09:54:14 +00:00
endpointTags := convertTagIDsToTags ( tagsMap , endpoint . TagIDs )
if endpointMatchSearchCriteria ( & endpoint , endpointTags , searchCriteria ) {
2019-07-20 23:28:11 +00:00
filteredEndpoints = append ( filteredEndpoints , endpoint )
continue
}
2020-03-29 09:54:14 +00:00
if endpointGroupMatchSearchCriteria ( & endpoint , endpointGroups , tagsMap , searchCriteria ) {
2019-07-20 23:28:11 +00:00
filteredEndpoints = append ( filteredEndpoints , endpoint )
}
}
return filteredEndpoints
}
2022-04-26 23:40:23 +00:00
func filterEndpointsByStatuses ( endpoints [ ] portainer . Endpoint , statuses [ ] int , settings * portainer . Settings ) [ ] portainer . Endpoint {
2022-04-26 00:17:36 +00:00
filteredEndpoints := make ( [ ] portainer . Endpoint , 0 )
for _ , endpoint := range endpoints {
status := endpoint . Status
if endpointutils . IsEdgeEndpoint ( & endpoint ) {
isCheckValid := false
2022-04-26 23:40:23 +00:00
edgeCheckinInterval := endpoint . EdgeCheckinInterval
if endpoint . EdgeCheckinInterval == 0 {
edgeCheckinInterval = settings . EdgeAgentCheckinInterval
}
if edgeCheckinInterval != 0 && endpoint . LastCheckInDate != 0 {
isCheckValid = time . Now ( ) . Unix ( ) - endpoint . LastCheckInDate <= int64 ( edgeCheckinInterval * EdgeDeviceIntervalMultiplier + EdgeDeviceIntervalAdd )
2022-04-26 00:17:36 +00:00
}
status = portainer . EndpointStatusDown // Offline
if isCheckValid {
status = portainer . EndpointStatusUp // Online
}
}
if utils . Contains ( statuses , int ( status ) ) {
filteredEndpoints = append ( filteredEndpoints , endpoint )
}
}
return filteredEndpoints
}
func sortEndpointsByField ( endpoints [ ] portainer . Endpoint , sortField string , isSortDesc bool ) {
switch sortField {
case "Name" :
if isSortDesc {
sort . Stable ( sort . Reverse ( EndpointsByName ( endpoints ) ) )
} else {
sort . Stable ( EndpointsByName ( endpoints ) )
}
case "Group" :
if isSortDesc {
sort . Stable ( sort . Reverse ( EndpointsByGroup ( endpoints ) ) )
} else {
sort . Stable ( EndpointsByGroup ( endpoints ) )
}
case "Status" :
if isSortDesc {
sort . Slice ( endpoints , func ( i , j int ) bool {
return endpoints [ i ] . Status > endpoints [ j ] . Status
} )
} else {
sort . Slice ( endpoints , func ( i , j int ) bool {
return endpoints [ i ] . Status < endpoints [ j ] . Status
} )
}
}
}
2020-03-29 09:54:14 +00:00
func endpointMatchSearchCriteria ( endpoint * portainer . Endpoint , tags [ ] string , searchCriteria string ) bool {
2019-07-20 23:28:11 +00:00
if strings . Contains ( strings . ToLower ( endpoint . Name ) , searchCriteria ) {
return true
}
if strings . Contains ( strings . ToLower ( endpoint . URL ) , searchCriteria ) {
return true
}
if endpoint . Status == portainer . EndpointStatusUp && searchCriteria == "up" {
return true
} else if endpoint . Status == portainer . EndpointStatusDown && searchCriteria == "down" {
return true
}
2020-03-29 09:54:14 +00:00
for _ , tag := range tags {
2019-07-20 23:28:11 +00:00
if strings . Contains ( strings . ToLower ( tag ) , searchCriteria ) {
return true
}
}
return false
}
2020-03-29 09:54:14 +00:00
func endpointGroupMatchSearchCriteria ( endpoint * portainer . Endpoint , endpointGroups [ ] portainer . EndpointGroup , tagsMap map [ portainer . TagID ] string , searchCriteria string ) bool {
2019-07-20 23:28:11 +00:00
for _ , group := range endpointGroups {
if group . ID == endpoint . GroupID {
if strings . Contains ( strings . ToLower ( group . Name ) , searchCriteria ) {
return true
}
2020-03-29 09:54:14 +00:00
tags := convertTagIDsToTags ( tagsMap , group . TagIDs )
for _ , tag := range tags {
2019-07-20 23:28:11 +00:00
if strings . Contains ( strings . ToLower ( tag ) , searchCriteria ) {
return true
}
}
}
2018-06-11 13:13:19 +00:00
}
2018-07-31 09:50:04 +00:00
2019-07-20 23:28:11 +00:00
return false
2018-06-11 13:13:19 +00:00
}
2020-03-26 05:44:27 +00:00
2021-07-21 07:16:22 +00:00
func filterEndpointsByTypes ( endpoints [ ] portainer . Endpoint , endpointTypes [ ] int ) [ ] portainer . Endpoint {
2020-03-26 05:44:27 +00:00
filteredEndpoints := make ( [ ] portainer . Endpoint , 0 )
2021-07-21 07:16:22 +00:00
typeSet := map [ portainer . EndpointType ] bool { }
for _ , endpointType := range endpointTypes {
typeSet [ portainer . EndpointType ( endpointType ) ] = true
}
2020-03-26 05:44:27 +00:00
for _ , endpoint := range endpoints {
2021-07-21 07:16:22 +00:00
if typeSet [ endpoint . Type ] {
2020-03-26 05:44:27 +00:00
filteredEndpoints = append ( filteredEndpoints , endpoint )
}
}
return filteredEndpoints
}
2020-03-29 09:54:14 +00:00
2022-04-19 18:43:36 +00:00
func filterEndpointsByEdgeDevice ( endpoints [ ] portainer . Endpoint , edgeDeviceFilter string ) [ ] portainer . Endpoint {
2022-01-23 19:48:04 +00:00
filteredEndpoints := make ( [ ] portainer . Endpoint , 0 )
for _ , endpoint := range endpoints {
2022-04-19 18:43:36 +00:00
if shouldReturnEdgeDevice ( endpoint , edgeDeviceFilter ) {
2022-01-23 19:48:04 +00:00
filteredEndpoints = append ( filteredEndpoints , endpoint )
}
}
return filteredEndpoints
}
2022-04-19 18:43:36 +00:00
func shouldReturnEdgeDevice ( endpoint portainer . Endpoint , edgeDeviceFilter string ) bool {
2022-04-26 11:25:20 +00:00
// none - return all endpoints that are not edge devices
if edgeDeviceFilter == EdgeDeviceFilterNone && ! endpoint . IsEdgeDevice {
return true
}
if ! endpointutils . IsEdgeEndpoint ( & endpoint ) {
2022-04-19 18:43:36 +00:00
return false
}
switch edgeDeviceFilter {
case EdgeDeviceFilterAll :
return true
case EdgeDeviceFilterTrusted :
return endpoint . UserTrusted
case EdgeDeviceFilterUntrusted :
return ! endpoint . UserTrusted
}
return false
}
2020-03-29 09:54:14 +00:00
func convertTagIDsToTags ( tagsMap map [ portainer . TagID ] string , tagIDs [ ] portainer . TagID ) [ ] string {
tags := make ( [ ] string , 0 )
for _ , tagID := range tagIDs {
tags = append ( tags , tagsMap [ tagID ] )
}
return tags
}
2020-04-08 09:14:50 +00:00
2020-05-14 02:14:28 +00:00
func filteredEndpointsByTags ( endpoints [ ] portainer . Endpoint , tagIDs [ ] portainer . TagID , endpointGroups [ ] portainer . EndpointGroup , partialMatch bool ) [ ] portainer . Endpoint {
2020-04-08 09:14:50 +00:00
filteredEndpoints := make ( [ ] portainer . Endpoint , 0 )
for _ , endpoint := range endpoints {
2020-05-14 02:14:28 +00:00
endpointGroup := getEndpointGroup ( endpoint . GroupID , endpointGroups )
endpointMatched := false
if partialMatch {
endpointMatched = endpointPartialMatchTags ( endpoint , endpointGroup , tagIDs )
} else {
endpointMatched = endpointFullMatchTags ( endpoint , endpointGroup , tagIDs )
2020-04-08 09:14:50 +00:00
}
2020-05-14 02:14:28 +00:00
if endpointMatched {
2020-04-08 09:14:50 +00:00
filteredEndpoints = append ( filteredEndpoints , endpoint )
}
}
return filteredEndpoints
}
2020-05-14 02:14:28 +00:00
func getEndpointGroup ( groupID portainer . EndpointGroupID , groups [ ] portainer . EndpointGroup ) portainer . EndpointGroup {
2020-04-08 09:14:50 +00:00
var endpointGroup portainer . EndpointGroup
for _ , group := range groups {
if group . ID == groupID {
endpointGroup = group
break
}
}
2020-05-14 02:14:28 +00:00
return endpointGroup
}
func endpointPartialMatchTags ( endpoint portainer . Endpoint , endpointGroup portainer . EndpointGroup , tagIDs [ ] portainer . TagID ) bool {
tagSet := make ( map [ portainer . TagID ] bool )
for _ , tagID := range tagIDs {
tagSet [ tagID ] = true
}
for _ , tagID := range endpoint . TagIDs {
if tagSet [ tagID ] {
return true
}
}
for _ , tagID := range endpointGroup . TagIDs {
if tagSet [ tagID ] {
return true
}
}
return false
}
func endpointFullMatchTags ( endpoint portainer . Endpoint , endpointGroup portainer . EndpointGroup , tagIDs [ ] portainer . TagID ) bool {
missingTags := make ( map [ portainer . TagID ] bool )
for _ , tagID := range tagIDs {
missingTags [ tagID ] = true
}
for _ , tagID := range endpoint . TagIDs {
if missingTags [ tagID ] {
delete ( missingTags , tagID )
}
}
2020-04-08 09:14:50 +00:00
for _ , tagID := range endpointGroup . TagIDs {
if missingTags [ tagID ] {
delete ( missingTags , tagID )
}
}
2020-05-14 02:14:28 +00:00
return len ( missingTags ) == 0
2020-04-08 09:14:50 +00:00
}
func filteredEndpointsByIds ( endpoints [ ] portainer . Endpoint , ids [ ] portainer . EndpointID ) [ ] portainer . Endpoint {
filteredEndpoints := make ( [ ] portainer . Endpoint , 0 )
idsSet := make ( map [ portainer . EndpointID ] bool )
for _ , id := range ids {
idsSet [ id ] = true
}
for _ , endpoint := range endpoints {
if idsSet [ endpoint . ID ] {
filteredEndpoints = append ( filteredEndpoints , endpoint )
}
}
return filteredEndpoints
}