mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 12:25:22 +02:00
feat(edge/stacks): increase status transparency [EE-5554] (#9094)
This commit is contained in:
parent
db61fb149b
commit
0bcb57568c
45 changed files with 1305 additions and 316 deletions
|
@ -3,6 +3,7 @@ package edgestacks
|
|||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
|
@ -25,6 +26,7 @@ import (
|
|||
// @failure 400
|
||||
// @failure 404
|
||||
// @failure 403
|
||||
// @deprecated
|
||||
// @router /edge_stacks/{id}/status/{environmentId} [delete]
|
||||
func (handler *Handler) edgeStackStatusDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
stackID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
|
@ -69,7 +71,17 @@ func (handler *Handler) deleteEdgeStackStatus(tx dataservices.DataStoreTx, stack
|
|||
return nil, handler.handlerDBErr(err, "Unable to find a stack with the specified identifier inside the database")
|
||||
}
|
||||
|
||||
delete(stack.Status, endpoint.ID)
|
||||
environmentStatus, ok := stack.Status[endpoint.ID]
|
||||
if !ok {
|
||||
environmentStatus = portainer.EdgeStackStatus{}
|
||||
}
|
||||
|
||||
environmentStatus.Status = append(environmentStatus.Status, portainer.EdgeStackDeploymentStatus{
|
||||
Time: time.Now().Unix(),
|
||||
Type: portainer.EdgeStackStatusRemoved,
|
||||
})
|
||||
|
||||
stack.Status[endpoint.ID] = environmentStatus
|
||||
|
||||
err = tx.EdgeStack().UpdateEdgeStack(stack.ID, stack)
|
||||
if err != nil {
|
||||
|
|
|
@ -3,21 +3,24 @@ package edgestacks
|
|||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/pkg/featureflags"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
)
|
||||
|
||||
type updateStatusPayload struct {
|
||||
Error string
|
||||
Status *portainer.EdgeStackStatusType
|
||||
EndpointID portainer.EndpointID
|
||||
Time int64
|
||||
}
|
||||
|
||||
func (payload *updateStatusPayload) Validate(r *http.Request) error {
|
||||
|
@ -33,6 +36,10 @@ func (payload *updateStatusPayload) Validate(r *http.Request) error {
|
|||
return errors.New("error message is mandatory when status is error")
|
||||
}
|
||||
|
||||
if payload.Time == 0 {
|
||||
payload.Time = time.Now().Unix()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -43,6 +50,7 @@ func (payload *updateStatusPayload) Validate(r *http.Request) error {
|
|||
// @accept json
|
||||
// @produce json
|
||||
// @param id path int true "EdgeStack Id"
|
||||
// @param body body updateStatusPayload true "EdgeStack status payload"
|
||||
// @success 200 {object} portainer.EdgeStack
|
||||
// @failure 500
|
||||
// @failure 400
|
||||
|
@ -84,6 +92,21 @@ func (handler *Handler) edgeStackStatusUpdate(w http.ResponseWriter, r *http.Req
|
|||
}
|
||||
|
||||
func (handler *Handler) updateEdgeStackStatus(tx dataservices.DataStoreTx, r *http.Request, stackID portainer.EdgeStackID, payload updateStatusPayload) (*portainer.EdgeStack, error) {
|
||||
stack, err := tx.EdgeStack().EdgeStack(stackID)
|
||||
if err != nil {
|
||||
if dataservices.IsErrObjectNotFound(err) {
|
||||
// skip error because agent tries to report on deleted stack
|
||||
log.Warn().
|
||||
Err(err).
|
||||
Int("stackID", int(stackID)).
|
||||
Int("status", int(*payload.Status)).
|
||||
Msg("Unable to find a stack inside the database, skipping error")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpoint, err := tx.Endpoint().Endpoint(payload.EndpointID)
|
||||
if err != nil {
|
||||
return nil, handler.handlerDBErr(err, "Unable to find an environment with the specified identifier inside the database")
|
||||
|
@ -94,67 +117,50 @@ func (handler *Handler) updateEdgeStackStatus(tx dataservices.DataStoreTx, r *ht
|
|||
return nil, httperror.Forbidden("Permission denied to access environment", err)
|
||||
}
|
||||
|
||||
var stack *portainer.EdgeStack
|
||||
status := *payload.Status
|
||||
|
||||
log.Debug().
|
||||
Int("stackID", int(stackID)).
|
||||
Int("status", int(status)).
|
||||
Msg("Updating stack status")
|
||||
|
||||
deploymentStatus := portainer.EdgeStackDeploymentStatus{
|
||||
Type: status,
|
||||
Error: payload.Error,
|
||||
Time: payload.Time,
|
||||
}
|
||||
|
||||
if featureflags.IsEnabled(portainer.FeatureNoTx) {
|
||||
err = tx.EdgeStack().UpdateEdgeStackFunc(portainer.EdgeStackID(stackID), func(edgeStack *portainer.EdgeStack) {
|
||||
details := edgeStack.Status[payload.EndpointID].Details
|
||||
details.Pending = false
|
||||
|
||||
switch *payload.Status {
|
||||
case portainer.EdgeStackStatusOk:
|
||||
details.Ok = true
|
||||
case portainer.EdgeStackStatusError:
|
||||
details.Error = true
|
||||
case portainer.EdgeStackStatusAcknowledged:
|
||||
details.Acknowledged = true
|
||||
case portainer.EdgeStackStatusRemove:
|
||||
details.Remove = true
|
||||
case portainer.EdgeStackStatusImagesPulled:
|
||||
details.ImagesPulled = true
|
||||
}
|
||||
|
||||
edgeStack.Status[payload.EndpointID] = portainer.EdgeStackStatus{
|
||||
Details: details,
|
||||
Error: payload.Error,
|
||||
EndpointID: payload.EndpointID,
|
||||
}
|
||||
err = tx.EdgeStack().UpdateEdgeStackFunc(stackID, func(edgeStack *portainer.EdgeStack) {
|
||||
updateEnvStatus(payload.EndpointID, edgeStack, deploymentStatus)
|
||||
|
||||
stack = edgeStack
|
||||
})
|
||||
} else {
|
||||
stack, err = tx.EdgeStack().EdgeStack(stackID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
details := stack.Status[payload.EndpointID].Details
|
||||
details.Pending = false
|
||||
|
||||
switch *payload.Status {
|
||||
case portainer.EdgeStackStatusOk:
|
||||
details.Ok = true
|
||||
case portainer.EdgeStackStatusError:
|
||||
details.Error = true
|
||||
case portainer.EdgeStackStatusAcknowledged:
|
||||
details.Acknowledged = true
|
||||
case portainer.EdgeStackStatusRemove:
|
||||
details.Remove = true
|
||||
case portainer.EdgeStackStatusImagesPulled:
|
||||
details.ImagesPulled = true
|
||||
}
|
||||
|
||||
stack.Status[payload.EndpointID] = portainer.EdgeStackStatus{
|
||||
Details: details,
|
||||
Error: payload.Error,
|
||||
EndpointID: payload.EndpointID,
|
||||
return nil, handler.handlerDBErr(err, "Unable to persist the stack changes inside the database")
|
||||
}
|
||||
} else {
|
||||
updateEnvStatus(payload.EndpointID, stack, deploymentStatus)
|
||||
|
||||
err = tx.EdgeStack().UpdateEdgeStack(stackID, stack)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, handler.handlerDBErr(err, "Unable to persist the stack changes inside the database")
|
||||
if err != nil {
|
||||
return nil, handler.handlerDBErr(err, "Unable to persist the stack changes inside the database")
|
||||
}
|
||||
}
|
||||
|
||||
return stack, nil
|
||||
}
|
||||
|
||||
func updateEnvStatus(environmentId portainer.EndpointID, stack *portainer.EdgeStack, deploymentStatus portainer.EdgeStackDeploymentStatus) {
|
||||
environmentStatus, ok := stack.Status[environmentId]
|
||||
if !ok {
|
||||
environmentStatus = portainer.EdgeStackStatus{
|
||||
EndpointID: environmentId,
|
||||
Status: []portainer.EdgeStackDeploymentStatus{},
|
||||
}
|
||||
}
|
||||
|
||||
environmentStatus.Status = append(environmentStatus.Status, deploymentStatus)
|
||||
|
||||
stack.Status[environmentId] = environmentStatus
|
||||
}
|
||||
|
|
|
@ -59,23 +59,31 @@ func TestUpdateStatusAndInspect(t *testing.T) {
|
|||
t.Fatalf("expected a %d response, found: %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
|
||||
data := portainer.EdgeStack{}
|
||||
err = json.NewDecoder(rec.Body).Decode(&data)
|
||||
updatedStack := portainer.EdgeStack{}
|
||||
err = json.NewDecoder(rec.Body).Decode(&updatedStack)
|
||||
if err != nil {
|
||||
t.Fatal("error decoding response:", err)
|
||||
}
|
||||
|
||||
if !data.Status[endpoint.ID].Details.Error {
|
||||
t.Fatalf("expected EdgeStackStatusType %d, found %t", payload.Status, data.Status[endpoint.ID].Details.Error)
|
||||
endpointStatus, ok := updatedStack.Status[payload.EndpointID]
|
||||
if !ok {
|
||||
t.Fatal("Missing status")
|
||||
}
|
||||
|
||||
if data.Status[endpoint.ID].Error != payload.Error {
|
||||
t.Fatalf("expected EdgeStackStatusError %s, found %s", payload.Error, data.Status[endpoint.ID].Error)
|
||||
lastStatus := endpointStatus.Status[len(endpointStatus.Status)-1]
|
||||
|
||||
if len(endpointStatus.Status) == len(edgeStack.Status[payload.EndpointID].Status) {
|
||||
t.Fatal("expected status array to be updated")
|
||||
}
|
||||
|
||||
if data.Status[endpoint.ID].EndpointID != payload.EndpointID {
|
||||
t.Fatalf("expected EndpointID %d, found %d", payload.EndpointID, data.Status[endpoint.ID].EndpointID)
|
||||
if lastStatus.Type != *payload.Status {
|
||||
t.Fatalf("expected EdgeStackStatusType %d, found %d", *payload.Status, lastStatus.Type)
|
||||
}
|
||||
|
||||
if endpointStatus.EndpointID != portainer.EndpointID(payload.EndpointID) {
|
||||
t.Fatalf("expected EndpointID %d, found %d", payload.EndpointID, endpointStatus.EndpointID)
|
||||
}
|
||||
|
||||
}
|
||||
func TestUpdateStatusWithInvalidPayload(t *testing.T) {
|
||||
handler, _ := setupHandler(t)
|
||||
|
@ -85,7 +93,7 @@ func TestUpdateStatusWithInvalidPayload(t *testing.T) {
|
|||
|
||||
// Update edge stack status
|
||||
statusError := portainer.EdgeStackStatusError
|
||||
statusOk := portainer.EdgeStackStatusOk
|
||||
statusOk := portainer.EdgeStackStatusDeploymentReceived
|
||||
cases := []struct {
|
||||
Name string
|
||||
Payload updateStatusPayload
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package edgestacks
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -41,20 +42,22 @@ func setupHandler(t *testing.T) (*Handler, string) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
edgeStacksService := edgestacks.NewService(store)
|
||||
|
||||
handler := NewHandler(
|
||||
security.NewRequestBouncer(store, jwtService, apiKeyService),
|
||||
store,
|
||||
edgeStacksService,
|
||||
)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
tmpDir, err := os.MkdirTemp(t.TempDir(), "portainer-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fs, err := filesystem.NewService(tmpDir, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
handler := NewHandler(
|
||||
security.NewRequestBouncer(store, jwtService, apiKeyService),
|
||||
store,
|
||||
edgestacks.NewService(store),
|
||||
)
|
||||
|
||||
handler.FileService = fs
|
||||
|
||||
settings, err := handler.DataStore.Settings().Settings()
|
||||
|
@ -116,11 +119,9 @@ func createEdgeStack(t *testing.T, store dataservices.DataStore, endpointID port
|
|||
|
||||
edgeStackID := portainer.EdgeStackID(14)
|
||||
edgeStack := portainer.EdgeStack{
|
||||
ID: edgeStackID,
|
||||
Name: "test-edge-stack-" + strconv.Itoa(int(edgeStackID)),
|
||||
Status: map[portainer.EndpointID]portainer.EdgeStackStatus{
|
||||
endpointID: {Details: portainer.EdgeStackStatusDetails{Ok: true}, Error: "", EndpointID: endpointID},
|
||||
},
|
||||
ID: edgeStackID,
|
||||
Name: "test-edge-stack-" + strconv.Itoa(int(edgeStackID)),
|
||||
Status: map[portainer.EndpointID]portainer.EdgeStackStatus{},
|
||||
CreationDate: time.Now().Unix(),
|
||||
EdgeGroups: []portainer.EdgeGroupID{edgeGroup.ID},
|
||||
ProjectPath: "/project/path",
|
||||
|
|
|
@ -2,7 +2,7 @@ package edgestacks
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
|
@ -10,12 +10,9 @@ import (
|
|||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
"github.com/portainer/portainer/api/internal/edge"
|
||||
"github.com/portainer/portainer/api/internal/set"
|
||||
"github.com/portainer/portainer/pkg/featureflags"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type updateEdgeStackPayload struct {
|
||||
|
@ -116,24 +113,6 @@ func (handler *Handler) updateEdgeStack(tx dataservices.DataStoreTx, stackID por
|
|||
|
||||
}
|
||||
|
||||
entryPoint := stack.EntryPoint
|
||||
manifestPath := stack.ManifestPath
|
||||
deploymentType := stack.DeploymentType
|
||||
|
||||
if deploymentType != payload.DeploymentType {
|
||||
// deployment type was changed - need to delete the old file
|
||||
err = handler.FileService.RemoveDirectory(stack.ProjectPath)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Unable to clear old files")
|
||||
}
|
||||
|
||||
entryPoint = ""
|
||||
manifestPath = ""
|
||||
deploymentType = payload.DeploymentType
|
||||
}
|
||||
|
||||
stackFolder := strconv.Itoa(int(stack.ID))
|
||||
|
||||
hasWrongType, err := hasWrongEnvironmentType(tx.Endpoint(), relatedEndpointIds, payload.DeploymentType)
|
||||
if err != nil {
|
||||
return nil, httperror.BadRequest("unable to check for existence of non fitting environments: %w", err)
|
||||
|
@ -142,50 +121,20 @@ func (handler *Handler) updateEdgeStack(tx dataservices.DataStoreTx, stackID por
|
|||
return nil, httperror.BadRequest("edge stack with config do not match the environment type", nil)
|
||||
}
|
||||
|
||||
if payload.DeploymentType == portainer.EdgeStackDeploymentCompose {
|
||||
if entryPoint == "" {
|
||||
entryPoint = filesystem.ComposeFileDefaultName
|
||||
}
|
||||
stack.NumDeployments = len(relatedEndpointIds)
|
||||
|
||||
_, err := handler.FileService.StoreEdgeStackFileFromBytes(stackFolder, entryPoint, []byte(payload.StackFileContent))
|
||||
stack.UseManifestNamespaces = payload.UseManifestNamespaces
|
||||
|
||||
stack.EdgeGroups = groupsIds
|
||||
|
||||
if payload.UpdateVersion {
|
||||
err := handler.updateStackVersion(stack, payload.DeploymentType, []byte(payload.StackFileContent), "", relatedEndpointIds)
|
||||
if err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to persist updated Compose file on disk", err)
|
||||
}
|
||||
|
||||
tempManifestPath, err := handler.convertAndStoreKubeManifestIfNeeded(stackFolder, stack.ProjectPath, entryPoint, relatedEndpointIds)
|
||||
if err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to convert and persist updated Kubernetes manifest file on disk", err)
|
||||
}
|
||||
|
||||
manifestPath = tempManifestPath
|
||||
}
|
||||
|
||||
if deploymentType == portainer.EdgeStackDeploymentKubernetes {
|
||||
if manifestPath == "" {
|
||||
manifestPath = filesystem.ManifestFileDefaultName
|
||||
}
|
||||
|
||||
_, err = handler.FileService.StoreEdgeStackFileFromBytes(stackFolder, manifestPath, []byte(payload.StackFileContent))
|
||||
if err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to persist updated Kubernetes manifest file on disk", err)
|
||||
return nil, httperror.InternalServerError("Unable to update stack version", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.EdgeStack().UpdateEdgeStackFunc(stack.ID, func(edgeStack *portainer.EdgeStack) {
|
||||
edgeStack.NumDeployments = len(relatedEndpointIds)
|
||||
if payload.UpdateVersion {
|
||||
edgeStack.Status = make(map[portainer.EndpointID]portainer.EdgeStackStatus)
|
||||
edgeStack.Version++
|
||||
}
|
||||
|
||||
edgeStack.UseManifestNamespaces = payload.UseManifestNamespaces
|
||||
|
||||
edgeStack.DeploymentType = deploymentType
|
||||
edgeStack.EntryPoint = entryPoint
|
||||
edgeStack.ManifestPath = manifestPath
|
||||
|
||||
edgeStack.EdgeGroups = groupsIds
|
||||
})
|
||||
err = tx.EdgeStack().UpdateEdgeStack(stack.ID, stack)
|
||||
if err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to persist the stack changes inside the database", err)
|
||||
}
|
||||
|
@ -246,3 +195,26 @@ func (handler *Handler) handleChangeEdgeGroups(tx dataservices.DataStoreTx, edge
|
|||
|
||||
return newRelatedEnvironmentIDs, endpointsToAdd, nil
|
||||
}
|
||||
|
||||
func newStatus(oldStatus map[portainer.EndpointID]portainer.EdgeStackStatus, relatedEnvironmentIds []portainer.EndpointID) map[portainer.EndpointID]portainer.EdgeStackStatus {
|
||||
newStatus := make(map[portainer.EndpointID]portainer.EdgeStackStatus)
|
||||
for _, endpointID := range relatedEnvironmentIds {
|
||||
newEnvStatus := portainer.EdgeStackStatus{}
|
||||
|
||||
oldEnvStatus, ok := oldStatus[endpointID]
|
||||
if ok {
|
||||
newEnvStatus = oldEnvStatus
|
||||
}
|
||||
|
||||
newEnvStatus.Status = []portainer.EdgeStackDeploymentStatus{
|
||||
{
|
||||
Time: time.Now().Unix(),
|
||||
Type: portainer.EdgeStackStatusPending,
|
||||
},
|
||||
}
|
||||
|
||||
newStatus[endpointID] = newEnvStatus
|
||||
}
|
||||
|
||||
return newStatus
|
||||
}
|
||||
|
|
|
@ -94,21 +94,21 @@ func TestUpdateAndInspect(t *testing.T) {
|
|||
t.Fatalf("expected a %d response, found: %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
|
||||
data := portainer.EdgeStack{}
|
||||
err = json.NewDecoder(rec.Body).Decode(&data)
|
||||
updatedStack := portainer.EdgeStack{}
|
||||
err = json.NewDecoder(rec.Body).Decode(&updatedStack)
|
||||
if err != nil {
|
||||
t.Fatal("error decoding response:", err)
|
||||
}
|
||||
|
||||
if payload.UpdateVersion && data.Version != edgeStack.Version+1 {
|
||||
t.Fatalf("expected EdgeStackID %d, found %d", edgeStack.Version, data.Version)
|
||||
if payload.UpdateVersion && updatedStack.Version != edgeStack.Version+1 {
|
||||
t.Fatalf("expected EdgeStack version %d, found %d", edgeStack.Version+1, updatedStack.Version+1)
|
||||
}
|
||||
|
||||
if data.DeploymentType != payload.DeploymentType {
|
||||
t.Fatalf("expected DeploymentType %d, found %d", edgeStack.DeploymentType, data.DeploymentType)
|
||||
if updatedStack.DeploymentType != payload.DeploymentType {
|
||||
t.Fatalf("expected DeploymentType %d, found %d", edgeStack.DeploymentType, updatedStack.DeploymentType)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(data.EdgeGroups, payload.EdgeGroups) {
|
||||
if !reflect.DeepEqual(updatedStack.EdgeGroups, payload.EdgeGroups) {
|
||||
t.Fatalf("expected EdgeGroups to be equal")
|
||||
}
|
||||
}
|
||||
|
|
59
api/http/handler/edgestacks/utils_update_stack_version.go
Normal file
59
api/http/handler/edgestacks/utils_update_stack_version.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package edgestacks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/filesystem"
|
||||
edgestackutils "github.com/portainer/portainer/api/internal/edge/edgestacks"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (handler *Handler) updateStackVersion(stack *portainer.EdgeStack, deploymentType portainer.EdgeStackDeploymentType, config []byte, oldGitHash string, relatedEnvironmentsIDs []portainer.EndpointID) error {
|
||||
|
||||
stack.Version = stack.Version + 1
|
||||
stack.Status = edgestackutils.NewStatus(stack.Status, relatedEnvironmentsIDs)
|
||||
|
||||
return handler.storeStackFile(stack, deploymentType, config)
|
||||
}
|
||||
|
||||
func (handler *Handler) storeStackFile(stack *portainer.EdgeStack, deploymentType portainer.EdgeStackDeploymentType, config []byte) error {
|
||||
|
||||
if deploymentType != stack.DeploymentType {
|
||||
// deployment type was changed - need to delete all old files
|
||||
err := handler.FileService.RemoveDirectory(stack.ProjectPath)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Unable to clear old files")
|
||||
}
|
||||
|
||||
stack.EntryPoint = ""
|
||||
stack.ManifestPath = ""
|
||||
stack.DeploymentType = deploymentType
|
||||
}
|
||||
|
||||
stackFolder := strconv.Itoa(int(stack.ID))
|
||||
entryPoint := ""
|
||||
if deploymentType == portainer.EdgeStackDeploymentCompose {
|
||||
if stack.EntryPoint == "" {
|
||||
stack.EntryPoint = filesystem.ComposeFileDefaultName
|
||||
}
|
||||
|
||||
entryPoint = stack.EntryPoint
|
||||
}
|
||||
|
||||
if deploymentType == portainer.EdgeStackDeploymentKubernetes {
|
||||
if stack.ManifestPath == "" {
|
||||
stack.ManifestPath = filesystem.ManifestFileDefaultName
|
||||
}
|
||||
|
||||
entryPoint = stack.ManifestPath
|
||||
}
|
||||
|
||||
_, err := handler.FileService.StoreEdgeStackFileFromBytesByVersion(stackFolder, entryPoint, stack.Version, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to persist updated Compose file with version on disk: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -291,7 +291,7 @@ func TestEdgeStackStatus(t *testing.T) {
|
|||
ID: edgeStackID,
|
||||
Name: "test-edge-stack-17",
|
||||
Status: map[portainer.EndpointID]portainer.EdgeStackStatus{
|
||||
endpointID: {Details: portainer.EdgeStackStatusDetails{Ok: true}, Error: "", EndpointID: endpoint.ID},
|
||||
endpointID: {},
|
||||
},
|
||||
CreationDate: time.Now().Unix(),
|
||||
EdgeGroups: []portainer.EdgeGroupID{1, 2},
|
||||
|
|
|
@ -17,18 +17,6 @@ import (
|
|||
"github.com/portainer/portainer/api/internal/unique"
|
||||
)
|
||||
|
||||
type EdgeStackStatusFilter string
|
||||
|
||||
const (
|
||||
statusFilterPending EdgeStackStatusFilter = "Pending"
|
||||
statusFilterOk EdgeStackStatusFilter = "Ok"
|
||||
statusFilterError EdgeStackStatusFilter = "Error"
|
||||
statusFilterAcknowledged EdgeStackStatusFilter = "Acknowledged"
|
||||
statusFilterRemove EdgeStackStatusFilter = "Remove"
|
||||
statusFilterRemoteUpdateSuccess EdgeStackStatusFilter = "RemoteUpdateSuccess"
|
||||
statusFilterImagesPulled EdgeStackStatusFilter = "ImagesPulled"
|
||||
)
|
||||
|
||||
type EnvironmentsQuery struct {
|
||||
search string
|
||||
types []portainer.EndpointType
|
||||
|
@ -45,7 +33,7 @@ type EnvironmentsQuery struct {
|
|||
agentVersions []string
|
||||
edgeCheckInPassedSeconds int
|
||||
edgeStackId portainer.EdgeStackID
|
||||
edgeStackStatus EdgeStackStatusFilter
|
||||
edgeStackStatus *portainer.EdgeStackStatusType
|
||||
}
|
||||
|
||||
func parseQuery(r *http.Request) (EnvironmentsQuery, error) {
|
||||
|
@ -99,7 +87,18 @@ func parseQuery(r *http.Request) (EnvironmentsQuery, error) {
|
|||
|
||||
edgeStackId, _ := request.RetrieveNumericQueryParameter(r, "edgeStackId", true)
|
||||
|
||||
edgeStackStatus, _ := request.RetrieveQueryParameter(r, "edgeStackStatus", true)
|
||||
edgeStackStatusQuery, _ := request.RetrieveQueryParameter(r, "edgeStackStatus", true)
|
||||
var edgeStackStatus *portainer.EdgeStackStatusType
|
||||
if edgeStackStatusQuery != "" {
|
||||
edgeStackStatusNumber, err := strconv.Atoi(edgeStackStatusQuery)
|
||||
if err != nil ||
|
||||
edgeStackStatusNumber < 0 ||
|
||||
edgeStackStatusNumber > int(portainer.EdgeStackStatusRemoving) {
|
||||
return EnvironmentsQuery{}, errors.New("invalid edgeStackStatus parameter")
|
||||
}
|
||||
|
||||
edgeStackStatus = ptr(portainer.EdgeStackStatusType(edgeStackStatusNumber))
|
||||
}
|
||||
|
||||
return EnvironmentsQuery{
|
||||
search: search,
|
||||
|
@ -116,7 +115,7 @@ func parseQuery(r *http.Request) (EnvironmentsQuery, error) {
|
|||
agentVersions: agentVersions,
|
||||
edgeCheckInPassedSeconds: edgeCheckInPassedSeconds,
|
||||
edgeStackId: portainer.EdgeStackID(edgeStackId),
|
||||
edgeStackStatus: EdgeStackStatusFilter(edgeStackStatus),
|
||||
edgeStackStatus: edgeStackStatus,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -213,30 +212,21 @@ func (handler *Handler) filterEndpointsByQuery(filteredEndpoints []portainer.End
|
|||
return filteredEndpoints, totalAvailableEndpoints, nil
|
||||
}
|
||||
|
||||
func endpointStatusInStackMatchesFilter(stackStatus map[portainer.EndpointID]portainer.EdgeStackStatus, envId portainer.EndpointID, statusFilter EdgeStackStatusFilter) bool {
|
||||
status, ok := stackStatus[envId]
|
||||
func endpointStatusInStackMatchesFilter(edgeStackStatus map[portainer.EndpointID]portainer.EdgeStackStatus, envId portainer.EndpointID, statusFilter portainer.EdgeStackStatusType) bool {
|
||||
status, ok := edgeStackStatus[envId]
|
||||
|
||||
// consider that if the env has no status in the stack it is in Pending state
|
||||
// workaround because Stack.Status[EnvId].Details.Pending is never set to True in the codebase
|
||||
if !ok && statusFilter == statusFilterPending {
|
||||
if !ok && statusFilter == portainer.EdgeStackStatusPending {
|
||||
return true
|
||||
}
|
||||
|
||||
valueMap := map[EdgeStackStatusFilter]bool{
|
||||
statusFilterPending: status.Details.Pending,
|
||||
statusFilterOk: status.Details.Ok,
|
||||
statusFilterError: status.Details.Error,
|
||||
statusFilterAcknowledged: status.Details.Acknowledged,
|
||||
statusFilterRemove: status.Details.Remove,
|
||||
statusFilterRemoteUpdateSuccess: status.Details.RemoteUpdateSuccess,
|
||||
statusFilterImagesPulled: status.Details.ImagesPulled,
|
||||
}
|
||||
|
||||
currentStatus, ok := valueMap[statusFilter]
|
||||
return ok && currentStatus
|
||||
return slices.ContainsFunc(status.Status, func(s portainer.EdgeStackDeploymentStatus) bool {
|
||||
return s.Type == statusFilter
|
||||
})
|
||||
}
|
||||
|
||||
func filterEndpointsByEdgeStack(endpoints []portainer.Endpoint, edgeStackId portainer.EdgeStackID, statusFilter EdgeStackStatusFilter, datastore dataservices.DataStore) ([]portainer.Endpoint, error) {
|
||||
func filterEndpointsByEdgeStack(endpoints []portainer.Endpoint, edgeStackId portainer.EdgeStackID, statusFilter *portainer.EdgeStackStatusType, datastore dataservices.DataStore) ([]portainer.Endpoint, error) {
|
||||
stack, err := datastore.EdgeStack().EdgeStack(edgeStackId)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "Unable to retrieve edge stack from the database")
|
||||
|
@ -258,10 +248,10 @@ func filterEndpointsByEdgeStack(endpoints []portainer.Endpoint, edgeStackId port
|
|||
envIds = append(envIds, edgeGroup.Endpoints...)
|
||||
}
|
||||
|
||||
if statusFilter != "" {
|
||||
if statusFilter != nil {
|
||||
n := 0
|
||||
for _, envId := range envIds {
|
||||
if endpointStatusInStackMatchesFilter(stack.Status, envId, statusFilter) {
|
||||
if endpointStatusInStackMatchesFilter(stack.Status, envId, *statusFilter) {
|
||||
envIds[n] = envId
|
||||
n++
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package endpoints
|
||||
|
||||
func ptr[T any](i T) *T { return &i }
|
||||
|
||||
func BoolAddr(b bool) *bool {
|
||||
boolVar := b
|
||||
return &boolVar
|
||||
return ptr(b)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue