diff --git a/cmd/uncloud/caddy/deploy.go b/cmd/uncloud/caddy/deploy.go index 1e799b80..2c855730 100644 --- a/cmd/uncloud/caddy/deploy.go +++ b/cmd/uncloud/caddy/deploy.go @@ -28,7 +28,7 @@ func NewDeployCommand() *cobra.Command { Use: "deploy", Short: "Deploy or upgrade Caddy reverse proxy across all machines in the cluster.", Long: "Deploy or upgrade Caddy reverse proxy across all machines in the cluster.\n" + - "It performs a rolling update if Caddy is already running.", + "A rolling update is performed when updating existing containers to minimise disruption.", RunE: func(cmd *cobra.Command, args []string) error { uncli := cmd.Context().Value("cli").(*cli.CLI) return deploy(cmd.Context(), uncli, opts) @@ -120,18 +120,33 @@ func deploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error { plan, err := d.Plan(ctx) if err != nil { + if errors.Is(err, client.ErrNoMatchingMachines) { + return fmt.Errorf("no machines found matching: %s", opts.machine) + } return fmt.Errorf("plan caddy deployment: %w", err) } if len(plan.SequenceOperation.Operations) == 0 { - fmt.Printf("%s service is up to date.\n", client.CaddyServiceName) + if opts.machine != "" { + fmt.Printf("%s service is up to date on selected machines.\n", client.CaddyServiceName) + } else { + fmt.Printf("%s service is up to date.\n", client.CaddyServiceName) + } return nil } if svc.ID == "" { - fmt.Println("This will run a Caddy container on each machine.") + if opts.machine != "" { + fmt.Println("This will run a Caddy container on selected machines.") + } else { + fmt.Println("This will run a Caddy container on each machine.") + } } else { - fmt.Println("This will perform a rolling update of Caddy containers on each machine.") + if opts.machine != "" { + fmt.Println("This will perform a rolling update of Caddy containers on selected machines.") + } else { + fmt.Println("This will perform a rolling update of Caddy containers on each machine.") + } } fmt.Println() diff --git a/internal/cli/client/deploy.go b/internal/cli/client/deploy.go index 652191b6..7e7ff173 100644 --- a/internal/cli/client/deploy.go +++ b/internal/cli/client/deploy.go @@ -30,6 +30,8 @@ type Plan struct { // machines that should be included. type MachineFilter func(m *pb.MachineInfo) bool +var ErrNoMatchingMachines = errors.New("no machines match the filter") + // NewDeployment creates a new deployment for the given service specification. // If strategy is nil, a default RollingStrategy will be used. func (cli *Client) NewDeployment(spec api.ServiceSpec, strategy Strategy) (*Deployment, error) { @@ -81,7 +83,7 @@ func (d *Deployment) Plan(ctx context.Context) (Plan, error) { plan, err := d.Strategy.Plan(ctx, d.cli, d.Service, d.Spec) if err != nil { - return Plan{}, fmt.Errorf("create plan using %T: %w", d.Strategy, err) + return Plan{}, fmt.Errorf("create plan using %s strategy: %w", d.Strategy.Type(), err) } d.plan = &plan diff --git a/internal/cli/client/strategy.go b/internal/cli/client/strategy.go index 9b577c7c..858d05b7 100644 --- a/internal/cli/client/strategy.go +++ b/internal/cli/client/strategy.go @@ -13,6 +13,8 @@ import ( // Strategy defines how a service should be deployed or updated. Different implementations can provide various // deployment patterns such as rolling updates, blue/green deployments, etc. type Strategy interface { + // Type returns the type of the deployment strategy, e.g. "rolling", "blue-green". + Type() string // Plan returns the operation to reconcile the service to the desired state. // If the service does not exist (new deployment), svc will be nil. Plan(ctx context.Context, cli *Client, svc *api.Service, spec api.ServiceSpec) (Plan, error) @@ -25,6 +27,10 @@ type RollingStrategy struct { MachineFilter MachineFilter } +func (s *RollingStrategy) Type() string { + return "rolling" +} + func (s *RollingStrategy) Plan( ctx context.Context, cli *Client, svc *api.Service, spec api.ServiceSpec, ) (Plan, error) { @@ -81,6 +87,9 @@ func (s *RollingStrategy) planGlobal( machines = slices.DeleteFunc(machines, func(m *pb.MachineMember) bool { return !s.MachineFilter(m.Machine) }) + if len(machines) == 0 { + return plan, ErrNoMatchingMachines + } } // TODO: figure out how to return a warning if there are machines down. Embed the machinesDown in the plan?