Add component-level targeting to start, stop, and restart
Allow start/stop/restart to target a single component via <service>/<component> syntax, matching deploy/logs/purge. When a component is specified, start/stop skip toggling the service-level active flag. Agent-side filtering returns NotFound for unknown components. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,8 +14,8 @@ import (
|
||||
|
||||
func stopCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "stop <service>",
|
||||
Short: "Stop all components, set active=false",
|
||||
Use: "stop <service>[/<component>]",
|
||||
Short: "Stop components (or all), set active=false",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cfg, err := config.LoadCLIConfig(cfgPath)
|
||||
@@ -23,7 +23,7 @@ func stopCmd() *cobra.Command {
|
||||
return fmt.Errorf("load config: %w", err)
|
||||
}
|
||||
|
||||
serviceName := args[0]
|
||||
serviceName, component := parseServiceArg(args[0])
|
||||
defPath := filepath.Join(cfg.Services.Dir, serviceName+".toml")
|
||||
|
||||
def, err := servicedef.Load(defPath)
|
||||
@@ -31,11 +31,14 @@ func stopCmd() *cobra.Command {
|
||||
return fmt.Errorf("load service def: %w", err)
|
||||
}
|
||||
|
||||
// Only flip active=false when stopping the whole service.
|
||||
if component == "" {
|
||||
active := false
|
||||
def.Active = &active
|
||||
if err := servicedef.Write(defPath, def); err != nil {
|
||||
return fmt.Errorf("write service def: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
address, err := findNodeAddress(cfg, def.Node)
|
||||
if err != nil {
|
||||
@@ -50,6 +53,7 @@ func stopCmd() *cobra.Command {
|
||||
|
||||
resp, err := client.StopService(context.Background(), &mcpv1.StopServiceRequest{
|
||||
Name: serviceName,
|
||||
Component: component,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("stop service: %w", err)
|
||||
@@ -63,8 +67,8 @@ func stopCmd() *cobra.Command {
|
||||
|
||||
func startCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "start <service>",
|
||||
Short: "Start all components, set active=true",
|
||||
Use: "start <service>[/<component>]",
|
||||
Short: "Start components (or all), set active=true",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cfg, err := config.LoadCLIConfig(cfgPath)
|
||||
@@ -72,7 +76,7 @@ func startCmd() *cobra.Command {
|
||||
return fmt.Errorf("load config: %w", err)
|
||||
}
|
||||
|
||||
serviceName := args[0]
|
||||
serviceName, component := parseServiceArg(args[0])
|
||||
defPath := filepath.Join(cfg.Services.Dir, serviceName+".toml")
|
||||
|
||||
def, err := servicedef.Load(defPath)
|
||||
@@ -80,11 +84,14 @@ func startCmd() *cobra.Command {
|
||||
return fmt.Errorf("load service def: %w", err)
|
||||
}
|
||||
|
||||
// Only flip active=true when starting the whole service.
|
||||
if component == "" {
|
||||
active := true
|
||||
def.Active = &active
|
||||
if err := servicedef.Write(defPath, def); err != nil {
|
||||
return fmt.Errorf("write service def: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
address, err := findNodeAddress(cfg, def.Node)
|
||||
if err != nil {
|
||||
@@ -99,6 +106,7 @@ func startCmd() *cobra.Command {
|
||||
|
||||
resp, err := client.StartService(context.Background(), &mcpv1.StartServiceRequest{
|
||||
Name: serviceName,
|
||||
Component: component,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("start service: %w", err)
|
||||
@@ -112,8 +120,8 @@ func startCmd() *cobra.Command {
|
||||
|
||||
func restartCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "restart <service>",
|
||||
Short: "Restart all components",
|
||||
Use: "restart <service>[/<component>]",
|
||||
Short: "Restart components (or all)",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cfg, err := config.LoadCLIConfig(cfgPath)
|
||||
@@ -121,7 +129,7 @@ func restartCmd() *cobra.Command {
|
||||
return fmt.Errorf("load config: %w", err)
|
||||
}
|
||||
|
||||
serviceName := args[0]
|
||||
serviceName, component := parseServiceArg(args[0])
|
||||
defPath := filepath.Join(cfg.Services.Dir, serviceName+".toml")
|
||||
|
||||
def, err := servicedef.Load(defPath)
|
||||
@@ -142,6 +150,7 @@ func restartCmd() *cobra.Command {
|
||||
|
||||
resp, err := client.RestartService(context.Background(), &mcpv1.RestartServiceRequest{
|
||||
Name: serviceName,
|
||||
Component: component,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("restart service: %w", err)
|
||||
|
||||
@@ -426,6 +426,7 @@ func (x *ComponentResult) GetError() string {
|
||||
type StopServiceRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Component string `protobuf:"bytes,2,opt,name=component,proto3" json:"component,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -467,6 +468,13 @@ func (x *StopServiceRequest) GetName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *StopServiceRequest) GetComponent() string {
|
||||
if x != nil {
|
||||
return x.Component
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type StopServiceResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Results []*ComponentResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"`
|
||||
@@ -514,6 +522,7 @@ func (x *StopServiceResponse) GetResults() []*ComponentResult {
|
||||
type StartServiceRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Component string `protobuf:"bytes,2,opt,name=component,proto3" json:"component,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -555,6 +564,13 @@ func (x *StartServiceRequest) GetName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *StartServiceRequest) GetComponent() string {
|
||||
if x != nil {
|
||||
return x.Component
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type StartServiceResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Results []*ComponentResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"`
|
||||
@@ -602,6 +618,7 @@ func (x *StartServiceResponse) GetResults() []*ComponentResult {
|
||||
type RestartServiceRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Component string `protobuf:"bytes,2,opt,name=component,proto3" json:"component,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -643,6 +660,13 @@ func (x *RestartServiceRequest) GetName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RestartServiceRequest) GetComponent() string {
|
||||
if x != nil {
|
||||
return x.Component
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type RestartServiceResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Results []*ComponentResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"`
|
||||
@@ -3060,17 +3084,20 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
|
||||
"\x0fComponentResult\x12\x12\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" +
|
||||
"\asuccess\x18\x02 \x01(\bR\asuccess\x12\x14\n" +
|
||||
"\x05error\x18\x03 \x01(\tR\x05error\"(\n" +
|
||||
"\x05error\x18\x03 \x01(\tR\x05error\"F\n" +
|
||||
"\x12StopServiceRequest\x12\x12\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\"H\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" +
|
||||
"\tcomponent\x18\x02 \x01(\tR\tcomponent\"H\n" +
|
||||
"\x13StopServiceResponse\x121\n" +
|
||||
"\aresults\x18\x01 \x03(\v2\x17.mcp.v1.ComponentResultR\aresults\")\n" +
|
||||
"\aresults\x18\x01 \x03(\v2\x17.mcp.v1.ComponentResultR\aresults\"G\n" +
|
||||
"\x13StartServiceRequest\x12\x12\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\"I\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" +
|
||||
"\tcomponent\x18\x02 \x01(\tR\tcomponent\"I\n" +
|
||||
"\x14StartServiceResponse\x121\n" +
|
||||
"\aresults\x18\x01 \x03(\v2\x17.mcp.v1.ComponentResultR\aresults\"+\n" +
|
||||
"\aresults\x18\x01 \x03(\v2\x17.mcp.v1.ComponentResultR\aresults\"I\n" +
|
||||
"\x15RestartServiceRequest\x12\x12\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\"K\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1c\n" +
|
||||
"\tcomponent\x18\x02 \x01(\tR\tcomponent\"K\n" +
|
||||
"\x16RestartServiceResponse\x121\n" +
|
||||
"\aresults\x18\x01 \x03(\v2\x17.mcp.v1.ComponentResultR\aresults\",\n" +
|
||||
"\x16UndeployServiceRequest\x12\x12\n" +
|
||||
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// StopService stops all components of a service.
|
||||
// StopService stops all components of a service, or a single component if specified.
|
||||
func (a *Agent) StopService(ctx context.Context, req *mcpv1.StopServiceRequest) (*mcpv1.StopServiceResponse, error) {
|
||||
a.Logger.Info("StopService", "service", req.GetName())
|
||||
a.Logger.Info("StopService", "service", req.GetName(), "component", req.GetComponent())
|
||||
|
||||
if req.GetName() == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "service name is required")
|
||||
@@ -25,6 +25,13 @@ func (a *Agent) StopService(ctx context.Context, req *mcpv1.StopServiceRequest)
|
||||
return nil, status.Errorf(codes.Internal, "list components: %v", err)
|
||||
}
|
||||
|
||||
if target := req.GetComponent(); target != "" {
|
||||
components, err = filterComponents(components, req.GetName(), target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var results []*mcpv1.ComponentResult
|
||||
for _, c := range components {
|
||||
containerName := ContainerNameFor(req.GetName(), c.Name)
|
||||
@@ -59,10 +66,10 @@ func (a *Agent) StopService(ctx context.Context, req *mcpv1.StopServiceRequest)
|
||||
return &mcpv1.StopServiceResponse{Results: results}, nil
|
||||
}
|
||||
|
||||
// StartService starts all components of a service. If a container already
|
||||
// exists but is stopped, it is removed first so a fresh one can be created.
|
||||
// StartService starts all components of a service, or a single component if specified.
|
||||
// If a container already exists but is stopped, it is removed first so a fresh one can be created.
|
||||
func (a *Agent) StartService(ctx context.Context, req *mcpv1.StartServiceRequest) (*mcpv1.StartServiceResponse, error) {
|
||||
a.Logger.Info("StartService", "service", req.GetName())
|
||||
a.Logger.Info("StartService", "service", req.GetName(), "component", req.GetComponent())
|
||||
|
||||
if req.GetName() == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "service name is required")
|
||||
@@ -73,6 +80,13 @@ func (a *Agent) StartService(ctx context.Context, req *mcpv1.StartServiceRequest
|
||||
return nil, status.Errorf(codes.Internal, "list components: %v", err)
|
||||
}
|
||||
|
||||
if target := req.GetComponent(); target != "" {
|
||||
components, err = filterComponents(components, req.GetName(), target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var results []*mcpv1.ComponentResult
|
||||
for _, c := range components {
|
||||
r := startComponent(ctx, a, req.GetName(), &c)
|
||||
@@ -82,10 +96,10 @@ func (a *Agent) StartService(ctx context.Context, req *mcpv1.StartServiceRequest
|
||||
return &mcpv1.StartServiceResponse{Results: results}, nil
|
||||
}
|
||||
|
||||
// RestartService restarts all components of a service by stopping, removing,
|
||||
// and re-creating each container. The desired_state is not changed.
|
||||
// RestartService restarts all components of a service, or a single component if specified,
|
||||
// by stopping, removing, and re-creating each container. The desired_state is not changed.
|
||||
func (a *Agent) RestartService(ctx context.Context, req *mcpv1.RestartServiceRequest) (*mcpv1.RestartServiceResponse, error) {
|
||||
a.Logger.Info("RestartService", "service", req.GetName())
|
||||
a.Logger.Info("RestartService", "service", req.GetName(), "component", req.GetComponent())
|
||||
|
||||
if req.GetName() == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "service name is required")
|
||||
@@ -96,6 +110,13 @@ func (a *Agent) RestartService(ctx context.Context, req *mcpv1.RestartServiceReq
|
||||
return nil, status.Errorf(codes.Internal, "list components: %v", err)
|
||||
}
|
||||
|
||||
if target := req.GetComponent(); target != "" {
|
||||
components, err = filterComponents(components, req.GetName(), target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var results []*mcpv1.ComponentResult
|
||||
for _, c := range components {
|
||||
r := restartComponent(ctx, a, req.GetName(), &c)
|
||||
@@ -167,6 +188,16 @@ func componentToSpec(service string, c *registry.Component) runtime.ContainerSpe
|
||||
}
|
||||
}
|
||||
|
||||
// filterComponents returns only the component matching target, or an error if not found.
|
||||
func filterComponents(components []registry.Component, service, target string) ([]registry.Component, error) {
|
||||
for _, c := range components {
|
||||
if c.Name == target {
|
||||
return []registry.Component{c}, nil
|
||||
}
|
||||
}
|
||||
return nil, status.Errorf(codes.NotFound, "component %q not found in service %q", target, service)
|
||||
}
|
||||
|
||||
// componentExists checks whether a component already exists in the registry.
|
||||
func componentExists(db *sql.DB, service, name string) bool {
|
||||
_, err := registry.GetComponent(db, service, name)
|
||||
|
||||
@@ -92,6 +92,7 @@ message ComponentResult {
|
||||
|
||||
message StopServiceRequest {
|
||||
string name = 1;
|
||||
string component = 2;
|
||||
}
|
||||
|
||||
message StopServiceResponse {
|
||||
@@ -100,6 +101,7 @@ message StopServiceResponse {
|
||||
|
||||
message StartServiceRequest {
|
||||
string name = 1;
|
||||
string component = 2;
|
||||
}
|
||||
|
||||
message StartServiceResponse {
|
||||
@@ -108,6 +110,7 @@ message StartServiceResponse {
|
||||
|
||||
message RestartServiceRequest {
|
||||
string name = 1;
|
||||
string component = 2;
|
||||
}
|
||||
|
||||
message RestartServiceResponse {
|
||||
|
||||
Reference in New Issue
Block a user