mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
feat: Add uc service start/stop commands (#195)
* feat: Add `uc service start/stop` commands * Add nolint:dupl for service start/stop commands Co-authored-by: Pasha Sviderski <me@psviderski.name> * Add --signal and --timeout options to uc service stop * Check whether service stop --timeout flag was used to set options properly --------- Co-authored-by: Pasha Sviderski <me@psviderski.name>
This commit is contained in:
co-authored by
Pasha Sviderski
parent
4b4b721d49
commit
a9a720f3c3
@@ -18,6 +18,8 @@ func NewRootCommand() *cobra.Command {
|
|||||||
NewRmCommand(),
|
NewRmCommand(),
|
||||||
NewRunCommand(),
|
NewRunCommand(),
|
||||||
NewScaleCommand(),
|
NewScaleCommand(),
|
||||||
|
NewStopCommand(),
|
||||||
|
NewStartCommand(),
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
//nolint:dupl
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type startOptions struct {
|
||||||
|
services []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStartCommand() *cobra.Command {
|
||||||
|
opts := startOptions{}
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "start SERVICE [SERVICE...]",
|
||||||
|
Short: "Start one or more services.",
|
||||||
|
Long: "Start one or more services.",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
opts.services = args
|
||||||
|
return start(cmd.Context(), uncli, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func start(ctx context.Context, uncli *cli.CLI, opts startOptions) error {
|
||||||
|
client, err := uncli.ConnectCluster(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
for _, s := range opts.services {
|
||||||
|
err = progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||||
|
if err = client.StartService(ctx, s); err != nil {
|
||||||
|
return fmt.Errorf("start service '%s': %w", s, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, uncli.ProgressOut(), "Starting service "+s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
//nolint:dupl
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stopOptions struct {
|
||||||
|
services []string
|
||||||
|
signal string
|
||||||
|
timeoutChanged bool
|
||||||
|
timeout int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStopCommand() *cobra.Command {
|
||||||
|
opts := stopOptions{}
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "stop SERVICE [SERVICE...]",
|
||||||
|
Short: "Stop one or more services.",
|
||||||
|
Long: "Stop one or more services.",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
opts.services = args
|
||||||
|
opts.timeoutChanged = cmd.Flags().Changed("timeout")
|
||||||
|
return stop(cmd.Context(), uncli, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Flags().StringVarP(&opts.signal, "signal", "s", "", "Signal to send to the container")
|
||||||
|
cmd.Flags().IntVarP(&opts.timeout, "timeout", "t", 0, "Seconds to wait before killing the container")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop(ctx context.Context, uncli *cli.CLI, opts stopOptions) error {
|
||||||
|
client, err := uncli.ConnectCluster(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connect to cluster: %w", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
stopOpts := container.StopOptions{
|
||||||
|
Signal: opts.signal,
|
||||||
|
}
|
||||||
|
if opts.timeoutChanged {
|
||||||
|
stopOpts.Timeout = &opts.timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range opts.services {
|
||||||
|
err = progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||||
|
if err = client.StopService(ctx, s, stopOpts); err != nil {
|
||||||
|
return fmt.Errorf("stop service '%s': %w", s, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, uncli.ProgressOut(), "Stopping service "+s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -48,6 +48,8 @@ type ServiceClient interface {
|
|||||||
RunService(ctx context.Context, spec ServiceSpec) (RunServiceResponse, error)
|
RunService(ctx context.Context, spec ServiceSpec) (RunServiceResponse, error)
|
||||||
InspectService(ctx context.Context, id string) (Service, error)
|
InspectService(ctx context.Context, id string) (Service, error)
|
||||||
RemoveService(ctx context.Context, id string) error
|
RemoveService(ctx context.Context, id string) error
|
||||||
|
StopService(ctx context.Context, id string, opts container.StopOptions) error
|
||||||
|
StartService(ctx context.Context, id string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type VolumeClient interface {
|
type VolumeClient interface {
|
||||||
|
|||||||
@@ -273,6 +273,80 @@ func (cli *Client) RemoveService(ctx context.Context, id string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StopService stops all containers on all machines that belong to the specified service.
|
||||||
|
// The id parameter can be either a service ID or name.
|
||||||
|
func (cli *Client) StopService(ctx context.Context, id string, opts container.StopOptions) error {
|
||||||
|
svc, err := cli.InspectService(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
errCh := make(chan error)
|
||||||
|
|
||||||
|
// Stop all containers on all machines that belong to the service.
|
||||||
|
for _, mc := range svc.Containers {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
err := cli.StopContainer(ctx, svc.ID, mc.Container.ID, opts)
|
||||||
|
if err != nil {
|
||||||
|
errCh <- fmt.Errorf("stop container '%s': %w", mc.Container.ID, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(errCh)
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = nil
|
||||||
|
for e := range errCh {
|
||||||
|
err = errors.Join(err, e)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartService starts all containers on all machines that belong to the specified service.
|
||||||
|
// The id parameter can be either a service ID or name.
|
||||||
|
func (cli *Client) StartService(ctx context.Context, id string) error {
|
||||||
|
svc, err := cli.InspectService(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
errCh := make(chan error)
|
||||||
|
|
||||||
|
// Start all containers on all machines that belong to the service.
|
||||||
|
for _, mc := range svc.Containers {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
err := cli.StartContainer(ctx, svc.ID, mc.Container.ID)
|
||||||
|
if err != nil {
|
||||||
|
errCh <- fmt.Errorf("start container '%s': %w", mc.Container.ID, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(errCh)
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = nil
|
||||||
|
for e := range errCh {
|
||||||
|
err = errors.Join(err, e)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// ListServices returns a list of all services and their containers.
|
// ListServices returns a list of all services and their containers.
|
||||||
func (cli *Client) ListServices(ctx context.Context) ([]api.Service, error) {
|
func (cli *Client) ListServices(ctx context.Context) ([]api.Service, error) {
|
||||||
machines, err := cli.ListMachines(ctx, nil)
|
machines, err := cli.ListMachines(ctx, nil)
|
||||||
|
|||||||
Reference in New Issue
Block a user