1package command
2
3import (
4	"fmt"
5	"strings"
6
7	"github.com/hashicorp/nomad/api"
8	"github.com/posener/complete"
9)
10
11type OperatorRaftRemoveCommand struct {
12	Meta
13}
14
15func (c *OperatorRaftRemoveCommand) Help() string {
16	helpText := `
17Usage: nomad operator raft remove-peer [options]
18
19  Remove the Nomad server with given -peer-address from the Raft configuration.
20
21  There are rare cases where a peer may be left behind in the Raft quorum even
22  though the server is no longer present and known to the cluster. This command
23  can be used to remove the failed server so that it is no longer affects the
24  Raft quorum. If the server still shows in the output of the "nomad
25  server-members" command, it is preferable to clean up by simply running "nomad
26  server-force-leave" instead of this command.
27
28General Options:
29
30  ` + generalOptionsUsage() + `
31
32Remove Peer Options:
33
34  -peer-address="IP:port"
35	Remove a Nomad server with given address from the Raft configuration.
36
37  -peer-id="id"
38	Remove a Nomad server with the given ID from the Raft configuration.
39`
40	return strings.TrimSpace(helpText)
41}
42
43func (c *OperatorRaftRemoveCommand) AutocompleteFlags() complete.Flags {
44	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
45		complete.Flags{
46			"-peer-address": complete.PredictAnything,
47			"-peer-id":      complete.PredictAnything,
48		})
49}
50
51func (c *OperatorRaftRemoveCommand) AutocompleteArgs() complete.Predictor {
52	return complete.PredictNothing
53}
54
55func (c *OperatorRaftRemoveCommand) Synopsis() string {
56	return "Remove a Nomad server from the Raft configuration"
57}
58
59func (c *OperatorRaftRemoveCommand) Name() string { return "operator raft remove-peer" }
60
61func (c *OperatorRaftRemoveCommand) Run(args []string) int {
62	var peerAddress string
63	var peerID string
64
65	flags := c.Meta.FlagSet("raft", FlagSetClient)
66	flags.Usage = func() { c.Ui.Output(c.Help()) }
67
68	flags.StringVar(&peerAddress, "peer-address", "", "")
69	flags.StringVar(&peerID, "peer-id", "", "")
70	if err := flags.Parse(args); err != nil {
71		c.Ui.Error(fmt.Sprintf("Failed to parse args: %v", err))
72		return 1
73	}
74
75	// Set up a client.
76	client, err := c.Meta.Client()
77	if err != nil {
78		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
79		return 1
80	}
81	operator := client.Operator()
82
83	if err := raftRemovePeers(peerAddress, peerID, operator); err != nil {
84		c.Ui.Error(fmt.Sprintf("Error removing peer: %v", err))
85		return 1
86	}
87	if peerAddress != "" {
88		c.Ui.Output(fmt.Sprintf("Removed peer with address %q", peerAddress))
89	} else {
90		c.Ui.Output(fmt.Sprintf("Removed peer with id %q", peerID))
91	}
92
93	return 0
94}
95
96func raftRemovePeers(address, id string, operator *api.Operator) error {
97	if len(address) == 0 && len(id) == 0 {
98		return fmt.Errorf("an address or id is required for the peer to remove")
99	}
100	if len(address) > 0 && len(id) > 0 {
101		return fmt.Errorf("cannot give both an address and id")
102	}
103
104	// Try to kick the peer.
105	if len(address) > 0 {
106		if err := operator.RaftRemovePeerByAddress(address, nil); err != nil {
107			return err
108		}
109	} else {
110		if err := operator.RaftRemovePeerByID(id, nil); err != nil {
111			return err
112		}
113	}
114
115	return nil
116}
117