package agent import ( "context" "database/sql" "errors" "fmt" mcpv1 "git.wntrmute.dev/kyle/mcp/gen/mcp/v1" "git.wntrmute.dev/kyle/mcp/internal/registry" "git.wntrmute.dev/kyle/mcp/internal/runtime" ) // Deploy deploys a service (or a single component of it) to this node. func (a *Agent) Deploy(ctx context.Context, req *mcpv1.DeployRequest) (*mcpv1.DeployResponse, error) { spec := req.GetService() if spec == nil { return nil, fmt.Errorf("deploy: missing service spec") } serviceName := spec.GetName() a.Logger.Info("deploying", "service", serviceName) if err := ensureService(a.DB, serviceName, spec.GetActive()); err != nil { return nil, fmt.Errorf("deploy: ensure service %q: %w", serviceName, err) } components := spec.GetComponents() if target := req.GetComponent(); target != "" { var filtered []*mcpv1.ComponentSpec for _, cs := range components { if cs.GetName() == target { filtered = append(filtered, cs) } } components = filtered } var results []*mcpv1.ComponentResult active := spec.GetActive() for _, cs := range components { result := a.deployComponent(ctx, serviceName, cs, active) results = append(results, result) } return &mcpv1.DeployResponse{Results: results}, nil } // deployComponent handles the full deploy lifecycle for a single component. func (a *Agent) deployComponent(ctx context.Context, serviceName string, cs *mcpv1.ComponentSpec, active bool) *mcpv1.ComponentResult { compName := cs.GetName() containerName := ContainerNameFor(serviceName, compName) desiredState := "running" if !active { desiredState = "stopped" } a.Logger.Info("deploying component", "service", serviceName, "component", compName, "desired", desiredState) regComp := ®istry.Component{ Name: compName, Service: serviceName, Image: cs.GetImage(), Network: cs.GetNetwork(), UserSpec: cs.GetUser(), Restart: cs.GetRestart(), DesiredState: desiredState, Version: runtime.ExtractVersion(cs.GetImage()), Ports: cs.GetPorts(), Volumes: cs.GetVolumes(), Cmd: cs.GetCmd(), } if err := ensureComponent(a.DB, regComp); err != nil { return &mcpv1.ComponentResult{ Name: compName, Error: fmt.Sprintf("ensure component: %v", err), } } if err := a.Runtime.Pull(ctx, cs.GetImage()); err != nil { return &mcpv1.ComponentResult{ Name: compName, Error: fmt.Sprintf("pull image: %v", err), } } _ = a.Runtime.Stop(ctx, containerName) // may not exist yet _ = a.Runtime.Remove(ctx, containerName) // may not exist yet runSpec := runtime.ContainerSpec{ Name: containerName, Image: cs.GetImage(), Network: cs.GetNetwork(), User: cs.GetUser(), Restart: cs.GetRestart(), Ports: cs.GetPorts(), Volumes: cs.GetVolumes(), Cmd: cs.GetCmd(), } if err := a.Runtime.Run(ctx, runSpec); err != nil { _ = registry.UpdateComponentState(a.DB, serviceName, compName, "", "removed") return &mcpv1.ComponentResult{ Name: compName, Error: fmt.Sprintf("run container: %v", err), } } if err := registry.UpdateComponentState(a.DB, serviceName, compName, "running", "running"); err != nil { a.Logger.Warn("failed to update component state", "service", serviceName, "component", compName, "err", err) } return &mcpv1.ComponentResult{ Name: compName, Success: true, } } // ensureService creates the service if it does not exist, or updates its // active flag if it does. func ensureService(db *sql.DB, name string, active bool) error { _, err := registry.GetService(db, name) if errors.Is(err, sql.ErrNoRows) { return registry.CreateService(db, name, active) } if err != nil { return err } return registry.UpdateServiceActive(db, name, active) } // ensureComponent creates the component if it does not exist, or updates its // spec if it does. func ensureComponent(db *sql.DB, c *registry.Component) error { _, err := registry.GetComponent(db, c.Service, c.Name) if errors.Is(err, sql.ErrNoRows) { c.ObservedState = "unknown" return registry.CreateComponent(db, c) } if err != nil { return err } return registry.UpdateComponentSpec(db, c) }