fix(edgestacks): remove edge stacks even after a system crash or power-off BE-10822 (#208)

release/2.25
andres-portainer 2024-12-04 19:52:53 -03:00 committed by GitHub
parent a8147b9713
commit 473084e915
3 changed files with 67 additions and 4 deletions

View File

@ -49,7 +49,7 @@ func (kcl *KubeClient) fetchResourceQuotasForNonAdmin(namespace string) (*[]core
func (kcl *KubeClient) fetchResourceQuotas(namespace string) (*[]corev1.ResourceQuota, error) {
resourceQuotas, err := kcl.cli.CoreV1().ResourceQuotas(namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("an error occured, failed to list resource quotas for the admin user: %w", err)
return nil, fmt.Errorf("an error occurred, failed to list resource quotas for the admin user: %w", err)
}
return &resourceQuotas.Items, nil

View File

@ -2,13 +2,16 @@ package compose
import (
"context"
"errors"
"fmt"
"maps"
"path/filepath"
"slices"
"strconv"
"strings"
"sync"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/pkg/libstack"
"github.com/compose-spec/compose-go/v2/dotenv"
@ -22,6 +25,8 @@ import (
"github.com/rs/zerolog/log"
)
const PortainerEdgeStackLabel = "io.portainer.edge_stack_id"
var mu sync.Mutex
func withCli(
@ -125,7 +130,7 @@ func withComposeService(
// Deploy creates and starts containers
func (c *ComposeDeployer) Deploy(ctx context.Context, filePaths []string, options libstack.DeployOptions) error {
return withComposeService(ctx, filePaths, options.Options, func(composeService api.Service, project *types.Project) error {
addServiceLabels(project, false)
addServiceLabels(project, false, options.EdgeStackID)
project = project.WithoutUnnecessaryResources()
@ -154,7 +159,7 @@ func (c *ComposeDeployer) Deploy(ctx context.Context, filePaths []string, option
// Run runs the given service just once, without considering dependencies
func (c *ComposeDeployer) Run(ctx context.Context, filePaths []string, serviceName string, options libstack.RunOptions) error {
return withComposeService(ctx, filePaths, options.Options, func(composeService api.Service, project *types.Project) error {
addServiceLabels(project, true)
addServiceLabels(project, true, 0)
for name, service := range project.Services {
if name == serviceName {
@ -242,7 +247,51 @@ func (c *ComposeDeployer) Config(ctx context.Context, filePaths []string, option
return payload, nil
}
func addServiceLabels(project *types.Project, oneOff bool) {
func (c *ComposeDeployer) GetExistingEdgeStacks(ctx context.Context) ([]libstack.EdgeStack, error) {
m := make(map[int]libstack.EdgeStack)
if err := withComposeService(ctx, nil, libstack.Options{}, func(composeService api.Service, project *types.Project) error {
stacks, err := composeService.List(ctx, api.ListOptions{
All: true,
})
if err != nil {
return err
}
for _, s := range stacks {
summary, err := composeService.Ps(ctx, s.Name, api.PsOptions{All: true})
if err != nil {
return err
}
for _, cs := range summary {
if sid, ok := cs.Labels[PortainerEdgeStackLabel]; ok {
id, err := strconv.Atoi(sid)
if err != nil {
return err
}
if cs.Labels[api.ProjectLabel] == "" {
return errors.New("invalid project label")
}
m[id] = libstack.EdgeStack{
ID: id,
Name: cs.Labels[api.ProjectLabel],
}
}
}
}
return nil
}); err != nil {
return nil, err
}
return slices.Collect(maps.Values(m)), nil
}
func addServiceLabels(project *types.Project, oneOff bool, edgeStackID portainer.EdgeStackID) {
oneOffLabel := "False"
if oneOff {
oneOffLabel = "True"
@ -257,6 +306,11 @@ func addServiceLabels(project *types.Project, oneOff bool) {
api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
api.OneoffLabel: oneOffLabel,
}
if edgeStackID > 0 {
s.CustomLabels.Add(PortainerEdgeStackLabel, strconv.Itoa(int(edgeStackID)))
}
project.Services[i] = s
}
}

View File

@ -3,6 +3,8 @@ package libstack
import (
"context"
portainer "github.com/portainer/portainer/api"
configtypes "github.com/docker/cli/cli/config/types"
)
@ -18,6 +20,7 @@ type Deployer interface {
Validate(ctx context.Context, filePaths []string, options Options) error
WaitForStatus(ctx context.Context, name string, status Status) <-chan WaitResult
Config(ctx context.Context, filePaths []string, options Options) ([]byte, error)
GetExistingEdgeStacks(ctx context.Context) ([]EdgeStack, error)
}
type Status string
@ -65,6 +68,7 @@ type DeployOptions struct {
// When this is set, docker compose will output its logs to stdout
AbortOnContainerExit bool
RemoveOrphans bool
EdgeStackID portainer.EdgeStackID
}
type RunOptions struct {
@ -82,3 +86,8 @@ type RemoveOptions struct {
Volumes bool
}
type EdgeStack struct {
ID int
Name string
}