From 7926d5aaae1488d604e14f011b286ad8b17d18e6 Mon Sep 17 00:00:00 2001 From: Jeefos Date: Mon, 11 May 2026 18:37:13 -0400 Subject: [PATCH] service Instance Page --- .../adapters/out/runtime/docker/docker.go | 36 ++++++++++++++++++- internal/application/ports/workload.go | 12 ++++--- pkg/labels/labels.go | 17 +++++---- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/internal/adapters/out/runtime/docker/docker.go b/internal/adapters/out/runtime/docker/docker.go index 78c71eb..85bd323 100644 --- a/internal/adapters/out/runtime/docker/docker.go +++ b/internal/adapters/out/runtime/docker/docker.go @@ -475,7 +475,7 @@ func (a *Adapter) createContainer(ctx context.Context, spec ports.WorkloadSpec, }, netConfig, nil, - spec.ServerID, + containerName(spec), ) if err != nil { return "", fmt.Errorf("failed to create container: %w", err) @@ -562,3 +562,37 @@ func shortID(s string) string { } return s } + +// containerName builds a human-readable container name in the form +// username.projectslug.servername, falling back gracefully when fields are absent. +func containerName(spec ports.WorkloadSpec) string { + var parts []string + if spec.OwnerUsername != "" { + parts = append(parts, sanitizeNamePart(spec.OwnerUsername)) + } + if spec.ProjectSlug != "" { + parts = append(parts, sanitizeNamePart(spec.ProjectSlug)) + } + if spec.ServerName != "" { + parts = append(parts, sanitizeNamePart(spec.ServerName)) + } + if len(parts) == 0 { + return spec.ServerID + } + return strings.Join(parts, ".") +} + +// sanitizeNamePart strips characters not allowed in Docker container names. +var nameReplacer = strings.NewReplacer(" ", "-", "@", "-", "/", "-") + +func sanitizeNamePart(s string) string { + s = nameReplacer.Replace(strings.ToLower(strings.TrimSpace(s))) + var out []byte + for i := 0; i < len(s); i++ { + c := s[i] + if c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '-' || c == '_' || c == '.' { + out = append(out, c) + } + } + return strings.Trim(string(out), "-.") +} diff --git a/internal/application/ports/workload.go b/internal/application/ports/workload.go index 83d4f3a..c47a5ea 100644 --- a/internal/application/ports/workload.go +++ b/internal/application/ports/workload.go @@ -13,11 +13,13 @@ type RunningServer struct { // It is a superset of the old ServerOperationPayload — all existing fields carry over. type WorkloadSpec struct { // Identity / Tenancy - OwnerID string `json:"owner_id"` - ServerID string `json:"server_id"` - BlueprintID string `json:"blueprint_id"` - ProjectID string `json:"project_id"` - ProjectSlug string `json:"project_slug"` + OwnerID string `json:"owner_id"` + OwnerUsername string `json:"owner_username,omitempty"` + ServerID string `json:"server_id"` + ServerName string `json:"server_name,omitempty"` + BlueprintID string `json:"blueprint_id"` + ProjectID string `json:"project_id"` + ProjectSlug string `json:"project_slug"` // Blueprint details required for provision/start Image string `json:"image"` diff --git a/pkg/labels/labels.go b/pkg/labels/labels.go index 10b2d3b..f2f9079 100644 --- a/pkg/labels/labels.go +++ b/pkg/labels/labels.go @@ -11,6 +11,10 @@ const ( ManagedBy = "kleff.io/managed_by" ManagedByValue = "kleff-daemon" + + // ComposeProject is the standard Docker Compose label used by Portainer to group containers. + ComposeProject = "com.docker.compose.project" + ComposeProjectValue = "kleff-local" ) type WorkloadLabels struct { @@ -24,12 +28,13 @@ type WorkloadLabels struct { func (l *WorkloadLabels) ToMap() map[string]string { m := map[string]string{ - OwnerID: l.OwnerID, - WorkloadID: l.ServerID, // new key - ServerID: l.ServerID, // deprecated alias — kept during transition - BlueprintID: l.BlueprintID, - NodeID: l.NodeID, - ManagedBy: ManagedByValue, + OwnerID: l.OwnerID, + WorkloadID: l.ServerID, // new key + ServerID: l.ServerID, // deprecated alias — kept during transition + BlueprintID: l.BlueprintID, + NodeID: l.NodeID, + ManagedBy: ManagedByValue, + ComposeProject: ComposeProjectValue, } if l.ProjectID != "" { m[ProjectID] = l.ProjectID