1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-24 15:59:41 +02:00

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

This commit is contained in:
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) { func (kcl *KubeClient) fetchResourceQuotas(namespace string) (*[]corev1.ResourceQuota, error) {
resourceQuotas, err := kcl.cli.CoreV1().ResourceQuotas(namespace).List(context.TODO(), metav1.ListOptions{}) resourceQuotas, err := kcl.cli.CoreV1().ResourceQuotas(namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil { 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 return &resourceQuotas.Items, nil

View file

@ -2,13 +2,16 @@ package compose
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"maps" "maps"
"path/filepath" "path/filepath"
"slices" "slices"
"strconv"
"strings" "strings"
"sync" "sync"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/pkg/libstack" "github.com/portainer/portainer/pkg/libstack"
"github.com/compose-spec/compose-go/v2/dotenv" "github.com/compose-spec/compose-go/v2/dotenv"
@ -22,6 +25,8 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
const PortainerEdgeStackLabel = "io.portainer.edge_stack_id"
var mu sync.Mutex var mu sync.Mutex
func withCli( func withCli(
@ -125,7 +130,7 @@ func withComposeService(
// Deploy creates and starts containers // Deploy creates and starts containers
func (c *ComposeDeployer) Deploy(ctx context.Context, filePaths []string, options libstack.DeployOptions) error { 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 { 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() 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 // 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 { 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 { 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 { for name, service := range project.Services {
if name == serviceName { if name == serviceName {
@ -242,7 +247,51 @@ func (c *ComposeDeployer) Config(ctx context.Context, filePaths []string, option
return payload, nil 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" oneOffLabel := "False"
if oneOff { if oneOff {
oneOffLabel = "True" oneOffLabel = "True"
@ -257,6 +306,11 @@ func addServiceLabels(project *types.Project, oneOff bool) {
api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","), api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
api.OneoffLabel: oneOffLabel, api.OneoffLabel: oneOffLabel,
} }
if edgeStackID > 0 {
s.CustomLabels.Add(PortainerEdgeStackLabel, strconv.Itoa(int(edgeStackID)))
}
project.Services[i] = s project.Services[i] = s
} }
} }

View file

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