feat: Implemented (un)install,start/stop flow and fixed actions flow
This commit is contained in:
+120
-26
@@ -5,10 +5,12 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.dcntrlized.org/Dcntrlized/Maestro/lib/docker"
|
||||
"git.dcntrlized.org/Dcntrlized/Maestro/lib/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
)
|
||||
@@ -41,7 +43,7 @@ func (cache *ContainerCache) WatchContainers(ctx context.Context, dc *docker.Doc
|
||||
}
|
||||
continue
|
||||
}
|
||||
_ = serviceLabel
|
||||
key := normalizeName(serviceLabel)
|
||||
containerID := event.Actor.ID
|
||||
containerName := event.Actor.Attributes["name"]
|
||||
|
||||
@@ -61,52 +63,70 @@ func (cache *ContainerCache) WatchContainers(ctx context.Context, dc *docker.Doc
|
||||
netInfo, shouldCache, err := docker.ExtractNetworkInfo(ctx, dc, inspectData)
|
||||
if err != nil {
|
||||
log.Printf("Error extracting network details: %v", err)
|
||||
} else if !shouldCache {
|
||||
log.Printf("Bypassing special network mode: %s", inspectData.HostConfig.NetworkMode)
|
||||
} else {
|
||||
containerNetwork = netInfo
|
||||
}
|
||||
if !shouldCache {
|
||||
fmt.Printf("Bypassing special network mode: %s\n", inspectData.HostConfig.NetworkMode)
|
||||
cache.Unlock()
|
||||
return
|
||||
}
|
||||
containerNetwork = netInfo
|
||||
}
|
||||
|
||||
// Get Versions
|
||||
ver, err := docker.GetContainerVersion(ctx, dc, containerID)
|
||||
ver, _ := docker.GetContainerVersion(ctx, dc, containerID)
|
||||
|
||||
// Fetch Manifest
|
||||
// Fetch Manifest (best effort; fall back to basic info if it fails)
|
||||
host := containerID[:12]
|
||||
port := event.Actor.Attributes["org.dcntrlized.port"]
|
||||
manifest, err := docker.FetchManifestWithRetry(ctx, host, port, 20, 50*time.Millisecond)
|
||||
if err != nil {
|
||||
log.Printf("Error fetching manifest for container %s: %v", containerID, err)
|
||||
cache.Unlock()
|
||||
continue
|
||||
}
|
||||
|
||||
// Save to Cache
|
||||
log.Printf("Service started. Adding container %s - %s to cache", host, containerName)
|
||||
cache.services[containerID] = types.Container{
|
||||
ID: containerID,
|
||||
Name: manifest.Name,
|
||||
DockerName: containerName,
|
||||
Image: event.Actor.Attributes["image"],
|
||||
CreatedAt: fmt.Sprint(event.TimeNano),
|
||||
Network: containerNetwork,
|
||||
Status: "running",
|
||||
Version: ver,
|
||||
existing, _ := cache.services[key]
|
||||
container := types.Container{
|
||||
ID: containerID,
|
||||
ServiceName: serviceLabel,
|
||||
DockerName: containerName,
|
||||
Image: event.Actor.Attributes["image"],
|
||||
CreatedAt: fmt.Sprint(event.TimeNano),
|
||||
Network: containerNetwork,
|
||||
Status: "running",
|
||||
Version: ver,
|
||||
Title: existing.Title,
|
||||
Vendor: existing.Vendor,
|
||||
Author: existing.Author,
|
||||
Description: existing.Description,
|
||||
Source: existing.Source,
|
||||
}
|
||||
if manifest != nil {
|
||||
container.Name = manifest.Name
|
||||
} else {
|
||||
container.Name = containerName
|
||||
}
|
||||
cache.services[key] = container
|
||||
|
||||
case "die":
|
||||
if container, exists := cache.services[containerID]; exists {
|
||||
log.Printf("Service stopping: %s", containerName)
|
||||
container.Status = "stopping"
|
||||
cache.services[containerID] = container
|
||||
if container, exists := cache.services[key]; exists {
|
||||
log.Printf("Service stopped: %s", containerName)
|
||||
container.Status = "stopped"
|
||||
cache.services[key] = container
|
||||
}
|
||||
|
||||
case "destroy":
|
||||
if container, exists := cache.services[containerID]; exists {
|
||||
if container, exists := cache.services[key]; exists {
|
||||
log.Printf("Service destroyed: %s", containerName)
|
||||
cache.RemoveService(container)
|
||||
if IsInstalled(serviceLabel) || IsInstalled(container.ServiceName) || IsInstalled(container.Name) {
|
||||
container.Status = "installed"
|
||||
container.ID = ""
|
||||
container.DockerName = ""
|
||||
container.Network = types.Network{}
|
||||
cache.services[key] = container
|
||||
} else {
|
||||
// We already hold the lock; delete directly instead of
|
||||
// calling RemoveService (which re-locks and deadlocks).
|
||||
delete(cache.services, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,3 +135,77 @@ func (cache *ContainerCache) WatchContainers(ctx context.Context, dc *docker.Doc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// imageTag returns the tag portion of an image reference, defaulting to latest.
|
||||
func imageTag(ref string) string {
|
||||
base := ref
|
||||
if i := strings.LastIndex(base, "/"); i >= 0 {
|
||||
base = base[i+1:]
|
||||
}
|
||||
if i := strings.LastIndex(base, ":"); i >= 0 {
|
||||
return base[i+1:]
|
||||
}
|
||||
return "latest"
|
||||
}
|
||||
|
||||
// normalizeDockerState maps raw Docker container states onto the smaller status
|
||||
// vocabulary used by the frontend (running / starting / stopped / installed).
|
||||
func normalizeDockerState(state string) string {
|
||||
switch state {
|
||||
case "running":
|
||||
return "running"
|
||||
case "restarting", "starting":
|
||||
return "starting"
|
||||
case "exited", "stopped", "paused", "dead", "removing":
|
||||
return "stopped"
|
||||
case "created":
|
||||
return "installed"
|
||||
default:
|
||||
return "installed"
|
||||
}
|
||||
}
|
||||
|
||||
// ReconcileContainers lists all dcntrlized-labeled containers (running or not) and
|
||||
// syncs their status into the cache, so state survives a Maestro restart.
|
||||
func (cache *ContainerCache) ReconcileContainers(ctx context.Context, dc *docker.DockerClient) {
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("label", "org.dcntrlized.service")
|
||||
options := container.ListOptions{
|
||||
All: true,
|
||||
Filters: filter,
|
||||
}
|
||||
|
||||
existing, err := dc.Client.ContainerList(ctx, options)
|
||||
if err != nil {
|
||||
log.Printf("Failed to list existing containers during reconciliation: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, c := range existing {
|
||||
serviceLabel := c.Labels["org.dcntrlized.service"]
|
||||
if serviceLabel == "" || len(c.Names) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := normalizeName(serviceLabel)
|
||||
cache.Lock()
|
||||
entry, has := cache.services[key]
|
||||
if !has {
|
||||
entry = types.Container{
|
||||
ServiceName: serviceLabel,
|
||||
Name: strings.TrimPrefix(c.Names[0], "/"),
|
||||
Image: c.Image,
|
||||
Version: imageTag(c.Image),
|
||||
Status: "installed",
|
||||
}
|
||||
}
|
||||
entry.Status = normalizeDockerState(c.State)
|
||||
entry.ID = c.ID
|
||||
entry.DockerName = strings.TrimPrefix(c.Names[0], "/")
|
||||
cache.services[key] = entry
|
||||
cache.Unlock()
|
||||
}
|
||||
|
||||
cache.TriggerUpdate()
|
||||
log.Printf("Reconciled %d existing containers with Docker", len(existing))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user