2020-05-14 05:14:28 +03:00
|
|
|
package edgestacks
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
|
|
|
|
2025-07-01 15:04:10 +02:00
|
|
|
portainer "github.com/portainer/portainer/api"
|
|
|
|
"github.com/portainer/portainer/api/dataservices"
|
|
|
|
"github.com/portainer/portainer/api/slicesx"
|
2023-09-01 19:27:02 -03:00
|
|
|
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
2025-07-01 15:04:10 +02:00
|
|
|
"github.com/portainer/portainer/pkg/libhttp/request"
|
2023-09-01 19:27:02 -03:00
|
|
|
"github.com/portainer/portainer/pkg/libhttp/response"
|
2020-05-14 05:14:28 +03:00
|
|
|
)
|
|
|
|
|
2025-07-01 15:04:10 +02:00
|
|
|
type aggregatedStatusesMap map[portainer.EdgeStackStatusType]int
|
|
|
|
|
|
|
|
type SummarizedStatus string
|
|
|
|
|
|
|
|
const (
|
|
|
|
sumStatusUnavailable SummarizedStatus = "Unavailable"
|
|
|
|
sumStatusDeploying SummarizedStatus = "Deploying"
|
|
|
|
sumStatusFailed SummarizedStatus = "Failed"
|
|
|
|
sumStatusPaused SummarizedStatus = "Paused"
|
|
|
|
sumStatusPartiallyRunning SummarizedStatus = "PartiallyRunning"
|
|
|
|
sumStatusCompleted SummarizedStatus = "Completed"
|
|
|
|
sumStatusRunning SummarizedStatus = "Running"
|
|
|
|
)
|
|
|
|
|
|
|
|
type edgeStackStatusSummary struct {
|
|
|
|
AggregatedStatus aggregatedStatusesMap
|
|
|
|
Status SummarizedStatus
|
|
|
|
Reason string
|
|
|
|
}
|
|
|
|
|
|
|
|
type edgeStackListResponseItem struct {
|
|
|
|
portainer.EdgeStack
|
|
|
|
StatusSummary edgeStackStatusSummary
|
|
|
|
}
|
|
|
|
|
2021-02-23 05:21:39 +02:00
|
|
|
// @id EdgeStackList
|
|
|
|
// @summary Fetches the list of EdgeStacks
|
2021-10-12 12:12:08 +13:00
|
|
|
// @description **Access policy**: administrator
|
2021-02-23 05:21:39 +02:00
|
|
|
// @tags edge_stacks
|
2021-11-30 15:31:16 +13:00
|
|
|
// @security ApiKeyAuth
|
2021-02-23 05:21:39 +02:00
|
|
|
// @security jwt
|
|
|
|
// @produce json
|
2025-07-01 15:04:10 +02:00
|
|
|
// @param summarizeStatuses query boolean false "will summarize the statuses"
|
2021-02-23 05:21:39 +02:00
|
|
|
// @success 200 {array} portainer.EdgeStack
|
|
|
|
// @failure 500
|
|
|
|
// @failure 400
|
2021-09-13 15:42:53 +12:00
|
|
|
// @failure 503 "Edge compute features are disabled"
|
2021-02-23 05:21:39 +02:00
|
|
|
// @router /edge_stacks [get]
|
2020-05-14 05:14:28 +03:00
|
|
|
func (handler *Handler) edgeStackList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
2025-07-01 15:04:10 +02:00
|
|
|
summarizeStatuses, _ := request.RetrieveBooleanQueryParameter(r, "summarizeStatuses", true)
|
|
|
|
|
2020-05-20 17:23:15 +12:00
|
|
|
edgeStacks, err := handler.DataStore.EdgeStack().EdgeStacks()
|
2020-05-14 05:14:28 +03:00
|
|
|
if err != nil {
|
2022-09-14 20:42:39 -03:00
|
|
|
return httperror.InternalServerError("Unable to retrieve edge stacks from the database", err)
|
2020-05-14 05:14:28 +03:00
|
|
|
}
|
|
|
|
|
2025-07-01 15:04:10 +02:00
|
|
|
res := make([]edgeStackListResponseItem, len(edgeStacks))
|
|
|
|
|
2025-06-05 19:46:10 -03:00
|
|
|
for i := range edgeStacks {
|
2025-07-01 15:04:10 +02:00
|
|
|
res[i].EdgeStack = edgeStacks[i]
|
|
|
|
|
|
|
|
if summarizeStatuses {
|
|
|
|
if err := fillStatusSummary(handler.DataStore, &res[i]); err != nil {
|
|
|
|
return handlerDBErr(err, "Unable to retrieve edge stack status from the database")
|
|
|
|
}
|
|
|
|
} else if err := fillEdgeStackStatus(handler.DataStore, &res[i].EdgeStack); err != nil {
|
2025-06-05 19:46:10 -03:00
|
|
|
return handlerDBErr(err, "Unable to retrieve edge stack status from the database")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-01 15:04:10 +02:00
|
|
|
return response.JSON(w, res)
|
|
|
|
}
|
|
|
|
|
|
|
|
func fillStatusSummary(tx dataservices.DataStoreTx, edgeStack *edgeStackListResponseItem) error {
|
|
|
|
statuses, err := tx.EdgeStackStatus().ReadAll(edgeStack.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
aggregated := make(aggregatedStatusesMap)
|
|
|
|
|
|
|
|
for _, envStatus := range statuses {
|
|
|
|
for _, status := range envStatus.Status {
|
|
|
|
aggregated[status.Type]++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
status, reason := SummarizeStatuses(statuses, edgeStack.NumDeployments)
|
|
|
|
|
|
|
|
edgeStack.StatusSummary = edgeStackStatusSummary{
|
|
|
|
AggregatedStatus: aggregated,
|
|
|
|
Status: status,
|
|
|
|
Reason: reason,
|
|
|
|
}
|
|
|
|
|
|
|
|
edgeStack.Status = map[portainer.EndpointID]portainer.EdgeStackStatus{}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func SummarizeStatuses(statuses []portainer.EdgeStackStatusForEnv, numDeployments int) (SummarizedStatus, string) {
|
|
|
|
if numDeployments == 0 {
|
|
|
|
return sumStatusUnavailable, "Your edge stack is currently unavailable due to the absence of an available environment in your edge group"
|
|
|
|
}
|
|
|
|
|
|
|
|
allStatuses := slicesx.FlatMap(statuses, func(x portainer.EdgeStackStatusForEnv) []portainer.EdgeStackDeploymentStatus {
|
|
|
|
return x.Status
|
|
|
|
})
|
|
|
|
|
|
|
|
lastStatuses := slicesx.Map(
|
|
|
|
slicesx.Filter(
|
|
|
|
statuses,
|
|
|
|
func(s portainer.EdgeStackStatusForEnv) bool {
|
|
|
|
return len(s.Status) > 0
|
|
|
|
},
|
|
|
|
),
|
|
|
|
func(x portainer.EdgeStackStatusForEnv) portainer.EdgeStackDeploymentStatus {
|
|
|
|
return x.Status[len(x.Status)-1]
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
if len(lastStatuses) == 0 {
|
|
|
|
return sumStatusDeploying, ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if allFailed := slicesx.Every(lastStatuses, func(s portainer.EdgeStackDeploymentStatus) bool {
|
|
|
|
return s.Type == portainer.EdgeStackStatusError
|
|
|
|
}); allFailed {
|
|
|
|
return sumStatusFailed, ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if hasPaused := slicesx.Some(allStatuses, func(s portainer.EdgeStackDeploymentStatus) bool {
|
|
|
|
return s.Type == portainer.EdgeStackStatusPausedDeploying
|
|
|
|
}); hasPaused {
|
|
|
|
return sumStatusPaused, ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(lastStatuses) < numDeployments {
|
|
|
|
return sumStatusDeploying, ""
|
|
|
|
}
|
|
|
|
|
|
|
|
hasDeploying := slicesx.Some(lastStatuses, func(s portainer.EdgeStackDeploymentStatus) bool { return s.Type == portainer.EdgeStackStatusDeploying })
|
|
|
|
hasRunning := slicesx.Some(lastStatuses, func(s portainer.EdgeStackDeploymentStatus) bool { return s.Type == portainer.EdgeStackStatusRunning })
|
|
|
|
hasFailed := slicesx.Some(lastStatuses, func(s portainer.EdgeStackDeploymentStatus) bool { return s.Type == portainer.EdgeStackStatusError })
|
|
|
|
|
|
|
|
if hasRunning && hasFailed && !hasDeploying {
|
|
|
|
return sumStatusPartiallyRunning, ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if allCompleted := slicesx.Every(lastStatuses, func(s portainer.EdgeStackDeploymentStatus) bool { return s.Type == portainer.EdgeStackStatusCompleted }); allCompleted {
|
|
|
|
return sumStatusCompleted, ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if allRunning := slicesx.Every(lastStatuses, func(s portainer.EdgeStackDeploymentStatus) bool {
|
|
|
|
return s.Type == portainer.EdgeStackStatusRunning
|
|
|
|
}); allRunning {
|
|
|
|
return sumStatusRunning, ""
|
|
|
|
}
|
|
|
|
|
|
|
|
return sumStatusDeploying, ""
|
2020-05-14 05:14:28 +03:00
|
|
|
}
|