fix: fail global deployment if no matchine machines found, improve caddy deploy output in particular

This commit is contained in:
Pavel Sviderski
2025-02-17 20:31:37 +10:00
parent e13408d79f
commit 98e1bdc9b5
3 changed files with 31 additions and 5 deletions
+16 -1
View File
@@ -28,7 +28,7 @@ func NewDeployCommand() *cobra.Command {
Use: "deploy", Use: "deploy",
Short: "Deploy or upgrade Caddy reverse proxy across all machines in the cluster.", 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" + 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 { RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI) uncli := cmd.Context().Value("cli").(*cli.CLI)
return deploy(cmd.Context(), uncli, opts) return deploy(cmd.Context(), uncli, opts)
@@ -120,19 +120,34 @@ func deploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
plan, err := d.Plan(ctx) plan, err := d.Plan(ctx)
if err != nil { 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) return fmt.Errorf("plan caddy deployment: %w", err)
} }
if len(plan.SequenceOperation.Operations) == 0 { if len(plan.SequenceOperation.Operations) == 0 {
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) fmt.Printf("%s service is up to date.\n", client.CaddyServiceName)
}
return nil return nil
} }
if svc.ID == "" { if svc.ID == "" {
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.") fmt.Println("This will run a Caddy container on each machine.")
}
} else {
if opts.machine != "" {
fmt.Println("This will perform a rolling update of Caddy containers on selected machines.")
} else { } else {
fmt.Println("This will perform a rolling update of Caddy containers on each machine.") fmt.Println("This will perform a rolling update of Caddy containers on each machine.")
} }
}
fmt.Println() fmt.Println()
fmt.Println("Deployment plan:") fmt.Println("Deployment plan:")
+3 -1
View File
@@ -30,6 +30,8 @@ type Plan struct {
// machines that should be included. // machines that should be included.
type MachineFilter func(m *pb.MachineInfo) bool 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. // NewDeployment creates a new deployment for the given service specification.
// If strategy is nil, a default RollingStrategy will be used. // If strategy is nil, a default RollingStrategy will be used.
func (cli *Client) NewDeployment(spec api.ServiceSpec, strategy Strategy) (*Deployment, error) { 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) plan, err := d.Strategy.Plan(ctx, d.cli, d.Service, d.Spec)
if err != nil { 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 d.plan = &plan
+9
View File
@@ -13,6 +13,8 @@ import (
// Strategy defines how a service should be deployed or updated. Different implementations can provide various // 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. // deployment patterns such as rolling updates, blue/green deployments, etc.
type Strategy interface { 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. // Plan returns the operation to reconcile the service to the desired state.
// If the service does not exist (new deployment), svc will be nil. // 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) Plan(ctx context.Context, cli *Client, svc *api.Service, spec api.ServiceSpec) (Plan, error)
@@ -25,6 +27,10 @@ type RollingStrategy struct {
MachineFilter MachineFilter MachineFilter MachineFilter
} }
func (s *RollingStrategy) Type() string {
return "rolling"
}
func (s *RollingStrategy) Plan( func (s *RollingStrategy) Plan(
ctx context.Context, cli *Client, svc *api.Service, spec api.ServiceSpec, ctx context.Context, cli *Client, svc *api.Service, spec api.ServiceSpec,
) (Plan, error) { ) (Plan, error) {
@@ -81,6 +87,9 @@ func (s *RollingStrategy) planGlobal(
machines = slices.DeleteFunc(machines, func(m *pb.MachineMember) bool { machines = slices.DeleteFunc(machines, func(m *pb.MachineMember) bool {
return !s.MachineFilter(m.Machine) 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? // TODO: figure out how to return a warning if there are machines down. Embed the machinesDown in the plan?