package agent import ( "context" "fmt" "strings" mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1" "git.wntrmute.dev/mc/mcp/internal/registry" "git.wntrmute.dev/mc/mcp/internal/runtime" ) // AdoptContainers discovers running containers that match the given service // name and registers them in the component registry. Containers named // "-*" or exactly "" are matched. func (a *Agent) AdoptContainers(ctx context.Context, req *mcpv1.AdoptContainersRequest) (*mcpv1.AdoptContainersResponse, error) { service := req.GetService() if service == "" { return nil, fmt.Errorf("service name is required") } containers, err := a.Runtime.List(ctx) if err != nil { return nil, fmt.Errorf("list containers: %w", err) } prefix := service + "-" // Filter matching containers before modifying any state. type match struct { container runtime.ContainerInfo component string } var matches []match for _, c := range containers { switch { case c.Name == service: matches = append(matches, match{c, service}) case strings.HasPrefix(c.Name, prefix): matches = append(matches, match{c, strings.TrimPrefix(c.Name, prefix)}) } } if len(matches) == 0 { return &mcpv1.AdoptContainersResponse{}, nil } // Ensure the service exists once, before adopting any containers. if err := registry.CreateService(a.DB, service, true); err != nil { if _, getErr := registry.GetService(a.DB, service); getErr != nil { return nil, fmt.Errorf("create service %q: %w", service, err) } } var results []*mcpv1.AdoptResult for _, m := range matches { a.Logger.Info("adopting", "service", service, "container", m.container.Name, "component", m.component) // Inspect the container to get full details (List only returns // name, image, state, and version). info, err := a.Runtime.Inspect(ctx, m.container.Name) if err != nil { results = append(results, &mcpv1.AdoptResult{ Container: m.container.Name, Component: m.component, Success: false, Error: fmt.Sprintf("inspect container: %v", err), }) continue } comp := ®istry.Component{ Name: m.component, Service: service, Image: info.Image, Network: info.Network, UserSpec: info.User, Restart: info.Restart, DesiredState: desiredFromObserved(info.State), ObservedState: info.State, Version: info.Version, Ports: info.Ports, Volumes: info.Volumes, Cmd: info.Cmd, } if createErr := registry.CreateComponent(a.DB, comp); createErr != nil { results = append(results, &mcpv1.AdoptResult{ Container: m.container.Name, Component: m.component, Success: false, Error: "already managed", }) continue } results = append(results, &mcpv1.AdoptResult{ Container: m.container.Name, Component: m.component, Success: true, }) } return &mcpv1.AdoptContainersResponse{Results: results}, nil } // desiredFromObserved maps an observed container state to the desired state. // Running containers should stay running; everything else is treated as stopped. func desiredFromObserved(observed string) string { if observed == "running" { return "running" } return "stopped" }