add cluster reroute commands, fix usage width bug (#59)

fixes #36
This commit is contained in:
T. von Dein
2026-06-29 13:08:05 +02:00
parent f9eded5f92
commit f5c8a23589
6 changed files with 466 additions and 91 deletions

182
README.md
View File

@@ -474,95 +474,99 @@ constantly as well.
### Command tree: ### Command tree:
```console ```console
api - api access and documentation api - api access and documentation
list - list index of API calls list - list index of API calls
show - show an API doc show - show an API doc
repl - interactive API repl repl - interactive API repl
ccr - manage cross cluster replication ccr - manage cross cluster replication
status - cross cluster replication status (yaml config with 2 clusters required) status - cross cluster replication status (yaml config with 2 clusters required)
pause - pause shard allocation pause - pause shard allocation
resume - resume shard allocation resume - resume shard allocation
follower - manage ccr follower indices follower - manage ccr follower indices
show - show ccr follower index details show - show ccr follower index details
add - add ccr follower index add - add ccr follower index
delete - delete ccr follower index delete - delete ccr follower index
unfollow - unfollow ccr follower index unfollow - unfollow ccr follower index
pause - pause ccr index to follow pause - pause ccr index to follow
resume - resume ccr index to follow resume - resume ccr index to follow
renew - renew ccr follower index renew - renew ccr follower index
info - show ccr remote info info - show ccr remote info
cluster - manage cluster[s] cluster - manage cluster[s]
status - show cluster status status - show cluster status
switch - set current elasticsearch cluster switch - set current elasticsearch cluster
list - list configured clusters list - list configured clusters
settings - cluster settings management settings - cluster settings management
list - show cluster settings list - show cluster settings
set - set|update cluster settings set - set|update cluster settings
datastream - manage data streams reroute - manually change the allocation of individual shards in the cluster.
list - list indicies move - move shard to another node
show - show details about an data stream allocate-replica - allocate-replica replica to another node
create - create a new data stream cancel - cancel a reroute operation
delete - delete a data stream allocate-empty-primary - allocate an empty primary shard to a node
rollover - roll over a data stream allocate-stale-primary - allocate a stale primary shard to a node
doc - manage documents datastream - manage data streams
add - add JSON document index list - list indicies
show - show a JSON document show - show details about an data stream
delete - delete JSON document[s] from index[es] create - create a new data stream
ilm - manage index lifecycle delete - delete a data stream
retry - retry applying an ILM profile to an index rollover - roll over a data stream
status - get the current index lifecycle management status doc - manage documents
list - list index lifecycle policies add - add JSON document index
show - show details about an index lifecycle policy show - show a JSON document
create - create a index lifecycle policy delete - delete JSON document[s] from index[es]
forecast - calculate index phase movements ilm - manage index lifecycle
list - list index rollover config retry - retry applying an ILM profile to an index
show - show rollover forecast over all indices status - get the current index lifecycle management status
index - manage indicies list - list index lifecycle policies
list - list indicies show - show details about an index lifecycle policy
show - show details about an index create - create a new lifecycle policy
create - create a new index update - update an lifecycle policy
modify - modify anindex forecast - calculate index phase movements
delete - delete an index list - list index rollover config
close - close an index show - show rollover forecast over all indices
allocation - explain index allocation explain - explain ilm condition of an index
fields - show info about field capabilities index - manage indicies
ilm - show ilm status list - list indicies
alias - manage index aliases show - show details about an index
create - create an index alias create - create a new index
list - list index aliases update - update an index
delete - delete an index alias delete - delete an index
rollover - roll over an index alias close - close an index
template - manage index templates allocation - explain index allocation
list - list index templates fields - show info about field capabilities
show - show details about an index template ilm - show ilm status
create - create a new index template alias - manage index aliases
modify - modify a new index template create - create an index alias
delete - delete an index template list - list index aliases
node - manage nodes delete - delete an index alias
list - list nodes rollover - roll over an index alias
show - show details about a node template - manage index templates
role - manage roles list - list index templates
list - list roles show - show details about an index template
show - show details about a role create - create a new index template
diff - show differences between roles and CSV baseline update - update a new index template
search - search within an index delete - delete an index template
shard - manage shards node - manage nodes
list - list shards list - list nodes
show - show details about a shard show - show details about a node
snapshot - manage snapshots role - manage roles
list - list snapshots list - list roles
show - show details about a snapshot show - show details about a role
task - manage tasks diff - show differences between roles and CSV baseline
list - list tasks search - search within an index
cancel - cancel running task shard - manage shards
version - show esctl version information list - list shards
debug - developer only show - show details about a shard
help-jsonpath - show jsonpath help snapshot - manage snapshots
completion - Output shell completion script for bash, zsh, fish, or Powershell list - list snapshots
pwsh - Output pwsh completion script show - show details about a snapshot
bash - Output bash completion script task - manage tasks
zsh - Output zsh completion script list - list tasks
fish - Output fish completion script cancel - cancel running task
version - show esctl version information
debug - developer only
help-jsonpath - show jsonpath help
help-command-overview - show overview of all available commands
``` ```
# Development # Development

View File

@@ -37,6 +37,7 @@ func Cluster(conf *cfg.Config) *cli.Command {
ClusterSwitch(conf), ClusterSwitch(conf),
ClusterList(conf), ClusterList(conf),
ClusterSettings(conf), ClusterSettings(conf),
ClusterReroute(conf),
}, },
} }
} }

225
cmd/cluster_reroute.go Normal file
View File

@@ -0,0 +1,225 @@
/*
Copyright © 2026 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cmd
import (
"context"
"errors"
"codeberg.org/scip/esctl/pkg/cfg"
"codeberg.org/scip/esctl/pkg/es"
"github.com/urfave/cli/v3"
)
func ClusterReroute(conf *cfg.Config) *cli.Command {
return &cli.Command{
Name: "reroute",
Usage: "manually change the allocation of individual shards in the cluster.",
UsageText: "reroute <cluster-name>",
Aliases: []string{"ctx"},
Commands: []*cli.Command{
ClusterRerouteMove(conf),
ClusterRerouteAllocateReplica(conf),
ClusterRerouteCancel(conf),
ClusterRerouteAllocatePrimary(conf, false),
ClusterRerouteAllocatePrimary(conf, true),
},
}
}
func ClusterRerouteMove(conf *cfg.Config) *cli.Command {
return &cli.Command{
Name: "move",
Usage: "move shard to another node",
UsageText: "move [options] <index>",
ShellComplete: func(ctx context.Context, cmd *cli.Command) {
complete(cmd, Cindex)
},
Flags: []cli.Flag{
&cli.IntFlag{
Name: "shard",
Usage: "shard to move",
Destination: &conf.Shards,
Aliases: []string{"s"},
Required: true,
},
&cli.StringFlag{
Name: "from-node",
Usage: "current node",
Destination: &conf.FromNode,
Aliases: []string{"f"},
Required: true,
},
&cli.StringFlag{
Name: "to-node",
Usage: "node to move to",
Destination: &conf.ToNode,
Aliases: []string{"t"},
Required: true,
},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
index := cmd.Args().Get(0)
if index == "" {
return errors.New("no index specified")
}
return es.ClusterRerouteMove(conf, index)
},
}
}
func ClusterRerouteAllocateReplica(conf *cfg.Config) *cli.Command {
return &cli.Command{
Name: "allocate-replica",
Usage: "allocate-replica replica to another node",
UsageText: "allocate-replica [options] <index>",
ShellComplete: func(ctx context.Context, cmd *cli.Command) {
complete(cmd, Cindex)
},
Flags: []cli.Flag{
&cli.IntFlag{
Name: "shard",
Usage: "shard to move",
Destination: &conf.Shards,
Aliases: []string{"s"},
Required: true,
},
&cli.StringFlag{
Name: "to-node",
Usage: "node to move to",
Destination: &conf.ToNode,
Aliases: []string{"t"},
Required: true,
},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
index := cmd.Args().Get(0)
if index == "" {
return errors.New("no index specified")
}
return es.ClusterRerouteAllocateReplica(conf, index)
},
}
}
func ClusterRerouteCancel(conf *cfg.Config) *cli.Command {
return &cli.Command{
Name: "cancel",
Usage: "cancel a reroute operation",
UsageText: "cancel [options] <index>",
ShellComplete: func(ctx context.Context, cmd *cli.Command) {
complete(cmd, Cindex)
},
Flags: []cli.Flag{
&cli.IntFlag{
Name: "shard",
Usage: "shard to move",
Destination: &conf.Shards,
Aliases: []string{"s"},
Required: true,
},
&cli.StringFlag{
Name: "to-node",
Usage: "node to move to",
Destination: &conf.ToNode,
Aliases: []string{"t"},
Required: true,
},
&cli.BoolFlag{
Name: "allow-primary",
Usage: "allow primary shard to cancel",
Destination: &conf.AllowPrimary,
Aliases: []string{"a"},
},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
index := cmd.Args().Get(0)
if index == "" {
return errors.New("no index specified")
}
return es.ClusterRerouteCancel(conf, index)
},
}
}
func ClusterRerouteAllocatePrimary(conf *cfg.Config, stale bool) *cli.Command {
name := "allocate-empty-primary"
usage := "allocate an empty primary shard to a node"
if stale {
name = "allocate-stale-primary"
usage = "allocate a stale primary shard to a node"
}
return &cli.Command{
Name: name,
Usage: usage,
UsageText: name + " [options] <index>",
ShellComplete: func(ctx context.Context, cmd *cli.Command) {
complete(cmd, Cindex)
},
Flags: []cli.Flag{
&cli.IntFlag{
Name: "shard",
Usage: "shard to move",
Destination: &conf.Shards,
Aliases: []string{"s"},
Required: true,
},
&cli.StringFlag{
Name: "to-node",
Usage: "node to move to",
Destination: &conf.ToNode,
Aliases: []string{"t"},
Required: true,
},
&cli.BoolFlag{
Name: "accept-data-loss",
Usage: "",
Destination: &conf.AcceptDataLoss,
Aliases: []string{"a"},
Required: true,
},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
index := cmd.Args().Get(0)
if index == "" {
return errors.New("no index specified")
}
return es.ClusterRerouteAllocatePrimary(conf, index, stale)
},
}
}

View File

@@ -262,8 +262,25 @@ func HelpUsage(conf *cfg.Config) *cli.Command {
}, },
Action: func(ctx context.Context, cmd *cli.Command) error { Action: func(ctx context.Context, cmd *cli.Command) error {
max := 22 maxCommandWidth := 0
// first pass, determine max command width
if err := walkVisible(conf, cmd.Root(), func(cmd *cli.Command) error {
path := cmd.Path()
size := len(path[len(path)-1])
if size > maxCommandWidth {
maxCommandWidth = size
}
return nil
}); err != nil {
return err
}
maxCommandWidth += 4 // account for indent width
// second pass, build tree
return walkVisible(conf, cmd.Root(), func(cmd *cli.Command) error { return walkVisible(conf, cmd.Root(), func(cmd *cli.Command) error {
path := cmd.Path() path := cmd.Path()
command := path[len(path)-1] command := path[len(path)-1]
@@ -273,7 +290,7 @@ func HelpUsage(conf *cfg.Config) *cli.Command {
} }
indent := strings.Repeat(" ", len(path[1:])-1) indent := strings.Repeat(" ", len(path[1:])-1)
space := strings.Repeat(" ", max-(len(command)+len(indent))) space := strings.Repeat(" ", maxCommandWidth-(len(command)+len(indent)))
fmt.Printf("%s%s %s - %s\n", indent, command, space, cmd.Usage) fmt.Printf("%s%s %s - %s\n", indent, command, space, cmd.Usage)

View File

@@ -103,6 +103,9 @@ type Config struct {
Tag string // api ls: -t Tag string // api ls: -t
Ilm Ilm // ilm create Ilm Ilm // ilm create
FromNode, ToNode string // cluster reroute move: -f + -t
AllowPrimary, AcceptDataLoss bool // cluster reroute cancel: -p,-a
} }
func NewConfig() *Config { func NewConfig() *Config {

125
pkg/es/cluster_reroute.go Normal file
View File

@@ -0,0 +1,125 @@
/*
Copyright © 2026 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package es
import (
"context"
"fmt"
"codeberg.org/scip/esctl/pkg/cfg"
"github.com/elastic/go-elasticsearch/v9/typedapi/esdsl"
"github.com/elastic/go-elasticsearch/v9/typedapi/types"
)
func ClusterRerouteMove(conf *cfg.Config, index string) error {
move := conf.DefaultCluster.ES().Cluster.Reroute()
commands := esdsl.NewCommand()
moveCommand := &types.CommandMoveAction{
Shard: conf.Shards,
FromNode: conf.FromNode,
ToNode: conf.ToNode,
Index: index,
}
commands.CommandCaster().Move = moveCommand
move.Commands(commands)
_, err := move.Do(context.Background())
if err != nil {
return fmt.Errorf("failed to reroute move: %w", err)
}
return nil
}
func ClusterRerouteAllocateReplica(conf *cfg.Config, index string) error {
move := conf.DefaultCluster.ES().Cluster.Reroute()
commands := esdsl.NewCommand()
allocCommand := &types.CommandAllocateReplicaAction{
Shard: conf.Shards,
Node: conf.ToNode,
Index: index,
}
commands.CommandCaster().AllocateReplica = allocCommand
move.Commands(commands)
_, err := move.Do(context.Background())
if err != nil {
return fmt.Errorf("failed to allocate a replica shard: %w", err)
}
return nil
}
func ClusterRerouteCancel(conf *cfg.Config, index string) error {
move := conf.DefaultCluster.ES().Cluster.Reroute()
commands := esdsl.NewCommand()
cancelCommand := &types.CommandCancelAction{
Shard: conf.Shards,
Node: conf.ToNode,
Index: index,
AllowPrimary: &conf.AllowPrimary,
}
commands.CommandCaster().Cancel = cancelCommand
move.Commands(commands)
_, err := move.Do(context.Background())
if err != nil {
return fmt.Errorf("failed to cancel a reroute process: %w", err)
}
return nil
}
func ClusterRerouteAllocatePrimary(conf *cfg.Config, index string, stale bool) error {
move := conf.DefaultCluster.ES().Cluster.Reroute()
commands := esdsl.NewCommand()
allocCommand := &types.CommandAllocatePrimaryAction{
Shard: conf.Shards,
Node: conf.ToNode,
Index: index,
AcceptDataLoss: conf.AcceptDataLoss,
}
if stale {
commands.CommandCaster().AllocateStalePrimary = allocCommand
} else {
commands.CommandCaster().AllocateEmptyPrimary = allocCommand
}
move.Commands(commands)
_, err := move.Do(context.Background())
if err != nil {
which := "empty"
if stale {
which = "stale"
}
return fmt.Errorf("failed to allocate %s primary shard: %w", which, err)
}
return nil
}