feat: Implemented (un)install,start/stop flow and fixed actions flow

This commit is contained in:
2026-08-01 20:39:27 +01:00
parent eb01569f7d
commit d24fe0ffbf
29 changed files with 2331 additions and 166 deletions
+108
View File
@@ -0,0 +1,108 @@
package containers
import (
"context"
"fmt"
"strings"
"git.dcntrlized.org/Dcntrlized/Maestro/lib/docker"
"git.dcntrlized.org/Dcntrlized/Maestro/lib/logger"
"git.dcntrlized.org/Dcntrlized/Maestro/lib/types"
composetypes "github.com/compose-spec/compose-go/v2/types"
"github.com/docker/compose/v2/pkg/api"
)
// StartContainer creates and starts the service containers defined in the
// service's docker-compose.yaml.
func StartContainer(ctx context.Context, item types.RegistryContainer) error {
dc, err := docker.NewDockerClient()
if err != nil {
return fmt.Errorf("failed to initialize docker client: %w", err)
}
defer dc.Close()
serviceName := resolveServiceName(ctx, dc, item.Name)
project, err := loadComposeProject(ctx, serviceName)
if err != nil {
return err
}
// The compose file may reference the image by a short name (e.g.
// "testservice") while install pulled it under its full registry reference.
// Make sure those short names exist so Up() can find them.
installedImage := item.Image
if installedImage == "" {
if dir, dirErr := getContainerDataDir(serviceName); dirErr == nil {
if metadata, metaErr := readInstalledMetadata(dir); metaErr == nil {
installedImage = metadata.Image
}
}
}
ensureServiceImages(ctx, dc, project, installedImage)
composeService, err := newComposeService(dc)
if err != nil {
return err
}
logger.Info("Starting service %s...", serviceName)
err = composeService.Up(ctx, project, api.UpOptions{
Create: api.CreateOptions{
Recreate: api.RecreateDiverged,
AssumeYes: true,
},
Start: api.StartOptions{},
})
if err != nil {
return fmt.Errorf("failed to start service %s: %w", serviceName, err)
}
logger.Info("Successfully started service: %s", serviceName)
return nil
}
// ensureServiceImages makes sure every image referenced by the compose project
// exists locally. Registries install the image under its full reference (e.g.
// git.../testservice:3.4) while the service's compose file often references it
// by a short name (e.g. "testservice"); when the short name is missing locally
// and its base name matches the installed image, it is tagged so start works.
func ensureServiceImages(ctx context.Context, dc *docker.DockerClient, project *composetypes.Project, installedImage string) {
if installedImage == "" {
return
}
for name, s := range project.Services {
ref := s.Image
if ref == "" || ref == installedImage {
continue
}
if imageExists(ctx, dc, ref) {
continue
}
if baseImageName(ref) != baseImageName(installedImage) {
continue
}
logger.Info("Tagging %s as %s for compose service %s", installedImage, ref, name)
if err := dc.Client.ImageTag(ctx, installedImage, ref); err != nil {
logger.Warn("Failed to tag %s as %s: %v", installedImage, ref, err)
}
}
}
// imageExists reports whether an image reference exists locally.
func imageExists(ctx context.Context, dc *docker.DockerClient, ref string) bool {
_, _, err := dc.Client.ImageInspectWithRaw(ctx, ref)
return err == nil
}
// baseImageName strips the registry/namespace and tag from an image reference,
// returning the repository name (e.g. "testservice").
func baseImageName(ref string) string {
base := ref
if i := strings.LastIndex(base, "/"); i >= 0 {
base = base[i+1:]
}
if i := strings.LastIndex(base, ":"); i >= 0 {
base = base[:i]
}
return base
}