mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-11-13 08:06:06 +00:00
Merge pull request #14292 from RaphaelFakhri/fix/compose-log-clean-project-name
fix(container): resolve compose project name when cleaning app logs
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -608,10 +609,14 @@ func (u *ContainerService) ComposeLogClean(req dto.ComposeLogClean) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
projectName, err := loadComposeProjectNameForLog(list, req.Name, req.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
for _, item := range list {
|
||||
if name, ok := item.Labels[composeProjectLabel]; ok {
|
||||
if name != req.Name {
|
||||
if name != projectName {
|
||||
continue
|
||||
}
|
||||
containerItem, err := client.ContainerInspect(ctx, item.ID)
|
||||
@@ -638,12 +643,48 @@ func (u *ContainerService) ComposeLogClean(req dto.ComposeLogClean) error {
|
||||
}
|
||||
}
|
||||
return u.ComposeOperation(dto.ComposeOperation{
|
||||
Name: req.Name,
|
||||
Name: projectName,
|
||||
Path: req.Path,
|
||||
Operation: "restart",
|
||||
})
|
||||
}
|
||||
|
||||
// loadComposeProjectNameForLog returns the project name that Compose uses for
|
||||
// the containers of composePath. Apps and runtimes pass their display name,
|
||||
// which can differ from the project name, for example in letter case.
|
||||
// If several projects use composePath and none of them matches name, it
|
||||
// returns an error rather than picking one of them.
|
||||
func loadComposeProjectNameForLog(list []container.Summary, name, composePath string) (string, error) {
|
||||
for _, item := range list {
|
||||
if item.Labels[composeProjectLabel] == name {
|
||||
return name, nil
|
||||
}
|
||||
}
|
||||
target := filepath.Clean(strings.TrimSpace(primaryComposePath(composePath)))
|
||||
var candidates []string
|
||||
for _, item := range list {
|
||||
projectName := item.Labels[composeProjectLabel]
|
||||
if projectName == "" || slices.Contains(candidates, projectName) {
|
||||
continue
|
||||
}
|
||||
configFile := strings.TrimSpace(primaryComposePath(item.Labels[composeConfigLabel]))
|
||||
if configFile != "" && filepath.Clean(configFile) == target {
|
||||
candidates = append(candidates, projectName)
|
||||
}
|
||||
}
|
||||
switch len(candidates) {
|
||||
case 0:
|
||||
return name, nil
|
||||
case 1:
|
||||
return candidates[0], nil
|
||||
}
|
||||
if normalized := normalizeComposeProjectName(name); slices.Contains(candidates, normalized) {
|
||||
return normalized, nil
|
||||
}
|
||||
slices.Sort(candidates)
|
||||
return "", fmt.Errorf("compose file %s is used by multiple projects (%s), cannot determine the project of %s", target, strings.Join(candidates, ", "), name)
|
||||
}
|
||||
|
||||
func (u *ContainerService) LoadComposeEnv(name string) (string, error) {
|
||||
envFilePath := path.Join(path.Dir(name), ".env")
|
||||
file, err := os.ReadFile(envFilePath)
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
)
|
||||
|
||||
func TestLoadComposeProjectNameForLog(t *testing.T) {
|
||||
list := []container.Summary{
|
||||
{Labels: map[string]string{
|
||||
composeProjectLabel: "openclaw",
|
||||
composeConfigLabel: "/opt/1panel/apps/openclaw/OpenClaw/docker-compose.yml",
|
||||
}},
|
||||
{Labels: map[string]string{
|
||||
composeProjectLabel: "openclaw",
|
||||
composeConfigLabel: "/opt/1panel/apps/openclaw/OpenClaw/docker-compose.yml",
|
||||
}},
|
||||
{Labels: map[string]string{
|
||||
composeProjectLabel: "demo",
|
||||
composeConfigLabel: "/opt/1panel/docker/compose/demo/docker-compose.yml,/opt/1panel/docker/compose/demo/override.yml",
|
||||
}},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
inputName string
|
||||
composePath string
|
||||
want string
|
||||
}{
|
||||
{name: "display name differs in case", inputName: "OpenClaw", composePath: "/opt/1panel/apps/openclaw/OpenClaw/docker-compose.yml", want: "openclaw"},
|
||||
{name: "project name matches", inputName: "demo", composePath: "/opt/1panel/docker/compose/demo/docker-compose.yml", want: "demo"},
|
||||
{name: "multiple compose files", inputName: "Demo", composePath: "/opt/1panel/docker/compose/demo/docker-compose.yml", want: "demo"},
|
||||
{name: "no matching container", inputName: "other", composePath: "/opt/1panel/apps/other/docker-compose.yml", want: "other"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := loadComposeProjectNameForLog(list, tt.inputName, tt.composePath)
|
||||
if err != nil {
|
||||
t.Fatalf("loadComposeProjectNameForLog(%q, %q) returned error: %v", tt.inputName, tt.composePath, err)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Fatalf("loadComposeProjectNameForLog(%q, %q) = %q, want %q", tt.inputName, tt.composePath, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadComposeProjectNameForLogSharedComposeFile(t *testing.T) {
|
||||
re.Init()
|
||||
const composePath = "/opt/1panel/apps/openclaw/OpenClaw/docker-compose.yml"
|
||||
openclaw := container.Summary{Labels: map[string]string{
|
||||
composeProjectLabel: "openclaw",
|
||||
composeConfigLabel: composePath,
|
||||
}}
|
||||
sandbox := container.Summary{Labels: map[string]string{
|
||||
composeProjectLabel: "sandbox",
|
||||
composeConfigLabel: composePath,
|
||||
}}
|
||||
orders := map[string][]container.Summary{
|
||||
"openclaw first": {openclaw, sandbox},
|
||||
"sandbox first": {sandbox, openclaw},
|
||||
}
|
||||
for order, list := range orders {
|
||||
t.Run(order, func(t *testing.T) {
|
||||
// The display name normalizes to one of the projects.
|
||||
got, err := loadComposeProjectNameForLog(list, "OpenClaw", composePath)
|
||||
if err != nil || got != "openclaw" {
|
||||
t.Fatalf("display name: got %q, %v, want %q", got, err, "openclaw")
|
||||
}
|
||||
// An exact project name selects that project.
|
||||
got, err = loadComposeProjectNameForLog(list, "sandbox", composePath)
|
||||
if err != nil || got != "sandbox" {
|
||||
t.Fatalf("project name: got %q, %v, want %q", got, err, "sandbox")
|
||||
}
|
||||
// A name that matches neither project is ambiguous.
|
||||
got, err = loadComposeProjectNameForLog(list, "My App", composePath)
|
||||
if err == nil {
|
||||
t.Fatalf("ambiguous name: got %q, want an error", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user