mirror of
https://github.com/henrygd/beszel.git
synced 2026-10-22 06:12:33 +00:00
124 lines
3.6 KiB
Go
124 lines
3.6 KiB
Go
package agent
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/henrygd/beszel/internal/entities/container"
|
|
)
|
|
|
|
// containerNameTemplate builds a container name from its Docker labels.
|
|
//
|
|
// CONTAINER_NAME_LABEL accepts either a plain label key ("coolify.serviceName") or a template
|
|
// that references labels with ${key} and may mix in literal text
|
|
// ("${coolify.projectName} - ${coolify.serviceName}"). Write $$ for a literal dollar sign.
|
|
type containerNameTemplate struct {
|
|
parts []nameTemplatePart
|
|
}
|
|
|
|
type nameTemplatePart struct {
|
|
literal string
|
|
key string // when set, the part is replaced by the value of this label
|
|
}
|
|
|
|
func parseContainerNameTemplate(value string) (*containerNameTemplate, error) {
|
|
value = strings.TrimSpace(value)
|
|
if !strings.Contains(value, "$") {
|
|
return &containerNameTemplate{parts: []nameTemplatePart{{key: value}}}, nil
|
|
}
|
|
var parts []nameTemplatePart
|
|
var literal strings.Builder
|
|
flush := func() {
|
|
if literal.Len() > 0 {
|
|
parts = append(parts, nameTemplatePart{literal: literal.String()})
|
|
literal.Reset()
|
|
}
|
|
}
|
|
for i := 0; i < len(value); i++ {
|
|
if value[i] != '$' {
|
|
literal.WriteByte(value[i])
|
|
continue
|
|
}
|
|
rest := value[i+1:]
|
|
switch {
|
|
case strings.HasPrefix(rest, "$"):
|
|
literal.WriteByte('$')
|
|
i++
|
|
case strings.HasPrefix(rest, "{"):
|
|
end := strings.IndexByte(rest, '}')
|
|
if end < 0 {
|
|
return nil, fmt.Errorf("unterminated ${ at position %d", i)
|
|
}
|
|
key := strings.TrimSpace(rest[1:end])
|
|
if key == "" {
|
|
return nil, fmt.Errorf("empty label name in ${} at position %d", i)
|
|
}
|
|
flush()
|
|
parts = append(parts, nameTemplatePart{key: key})
|
|
i += end + 1
|
|
default:
|
|
return nil, fmt.Errorf("use ${label} to reference a label (or $$ for a literal $) at position %d", i)
|
|
}
|
|
}
|
|
flush()
|
|
return &containerNameTemplate{parts: parts}, nil
|
|
}
|
|
|
|
// render returns the name for a container's labels. It reports false when any referenced
|
|
// label is missing or empty, so the caller can keep the Docker name instead of a partial one.
|
|
func (t *containerNameTemplate) render(labels map[string]string) (string, bool) {
|
|
var b strings.Builder
|
|
for _, part := range t.parts {
|
|
if part.key == "" {
|
|
b.WriteString(part.literal)
|
|
continue
|
|
}
|
|
v := strings.TrimSpace(labels[part.key])
|
|
if v == "" {
|
|
return "", false
|
|
}
|
|
b.WriteString(v)
|
|
}
|
|
name := strings.TrimSpace(b.String())
|
|
return name, name != ""
|
|
}
|
|
|
|
// resolveContainerNames sets DisplayName on each container. The rendered name is used when it is
|
|
// complete and unique among the reported containers. Containers that render to the same name
|
|
// (replicas) keep their Docker name so their series are never merged. tmpl must not be nil.
|
|
func resolveContainerNames(list []*container.ApiInfo, tmpl *containerNameTemplate, excluded func(string) bool) {
|
|
candidates := make(map[*container.ApiInfo]string, len(list))
|
|
fromLabel := make(map[*container.ApiInfo]bool, len(list))
|
|
for _, ctr := range list {
|
|
dockerName := ctr.Names[0][1:]
|
|
ctr.DisplayName = dockerName
|
|
if excluded(dockerName) {
|
|
continue
|
|
}
|
|
candidates[ctr] = dockerName
|
|
if name, ok := tmpl.render(ctr.Labels); ok {
|
|
candidates[ctr] = name
|
|
fromLabel[ctr] = true
|
|
}
|
|
}
|
|
// Revert label names that are not unique. A reverted Docker name can collide with
|
|
// another label name, so repeat until nothing changes.
|
|
for changed := true; changed; {
|
|
changed = false
|
|
counts := make(map[string]int, len(candidates))
|
|
for _, name := range candidates {
|
|
counts[name]++
|
|
}
|
|
for ctr := range fromLabel {
|
|
if counts[candidates[ctr]] > 1 {
|
|
candidates[ctr] = ctr.Names[0][1:]
|
|
delete(fromLabel, ctr)
|
|
changed = true
|
|
}
|
|
}
|
|
}
|
|
for ctr, name := range candidates {
|
|
ctr.DisplayName = name
|
|
}
|
|
}
|