go.etcd.io/etcd@v3.3.27+incompatible/etcdctl/ctlv2/command/member_commands.go (about) 1 // Copyright 2015 The etcd Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package command 16 17 import ( 18 "fmt" 19 "os" 20 "strings" 21 22 "github.com/urfave/cli" 23 ) 24 25 func NewMemberCommand() cli.Command { 26 return cli.Command{ 27 Name: "member", 28 Usage: "member add, remove and list subcommands", 29 Subcommands: []cli.Command{ 30 { 31 Name: "list", 32 Usage: "enumerate existing cluster members", 33 ArgsUsage: " ", 34 Action: actionMemberList, 35 }, 36 { 37 Name: "add", 38 Usage: "add a new member to the etcd cluster", 39 ArgsUsage: "<name> <peerURL>", 40 Action: actionMemberAdd, 41 }, 42 { 43 Name: "remove", 44 Usage: "remove an existing member from the etcd cluster", 45 ArgsUsage: "<memberID>", 46 Action: actionMemberRemove, 47 }, 48 { 49 Name: "update", 50 Usage: "update an existing member in the etcd cluster", 51 ArgsUsage: "<memberID> <peerURLs>", 52 Action: actionMemberUpdate, 53 }, 54 }, 55 } 56 } 57 58 func actionMemberList(c *cli.Context) error { 59 if len(c.Args()) != 0 { 60 fmt.Fprintln(os.Stderr, "No arguments accepted") 61 os.Exit(1) 62 } 63 mAPI := mustNewMembersAPI(c) 64 ctx, cancel := contextWithTotalTimeout(c) 65 defer cancel() 66 67 members, err := mAPI.List(ctx) 68 if err != nil { 69 fmt.Fprintln(os.Stderr, err.Error()) 70 os.Exit(1) 71 } 72 leader, err := mAPI.Leader(ctx) 73 if err != nil { 74 fmt.Fprintln(os.Stderr, "Failed to get leader: ", err) 75 os.Exit(1) 76 } 77 78 for _, m := range members { 79 isLeader := false 80 if m.ID == leader.ID { 81 isLeader = true 82 } 83 if len(m.Name) == 0 { 84 fmt.Printf("%s[unstarted]: peerURLs=%s\n", m.ID, strings.Join(m.PeerURLs, ",")) 85 } else { 86 fmt.Printf("%s: name=%s peerURLs=%s clientURLs=%s isLeader=%v\n", m.ID, m.Name, strings.Join(m.PeerURLs, ","), strings.Join(m.ClientURLs, ","), isLeader) 87 } 88 } 89 90 return nil 91 } 92 93 func actionMemberAdd(c *cli.Context) error { 94 args := c.Args() 95 if len(args) != 2 { 96 fmt.Fprintln(os.Stderr, "Provide a name and a single member peerURL") 97 os.Exit(1) 98 } 99 100 mAPI := mustNewMembersAPI(c) 101 102 url := args[1] 103 ctx, cancel := contextWithTotalTimeout(c) 104 defer cancel() 105 106 m, err := mAPI.Add(ctx, url) 107 if err != nil { 108 fmt.Fprintln(os.Stderr, err.Error()) 109 os.Exit(1) 110 } 111 112 newID := m.ID 113 newName := args[0] 114 fmt.Printf("Added member named %s with ID %s to cluster\n", newName, newID) 115 116 members, err := mAPI.List(ctx) 117 if err != nil { 118 fmt.Fprintln(os.Stderr, err.Error()) 119 os.Exit(1) 120 } 121 122 conf := []string{} 123 for _, memb := range members { 124 for _, u := range memb.PeerURLs { 125 n := memb.Name 126 if memb.ID == newID { 127 n = newName 128 } 129 conf = append(conf, fmt.Sprintf("%s=%s", n, u)) 130 } 131 } 132 133 fmt.Print("\n") 134 fmt.Printf("ETCD_NAME=%q\n", newName) 135 fmt.Printf("ETCD_INITIAL_CLUSTER=%q\n", strings.Join(conf, ",")) 136 fmt.Printf("ETCD_INITIAL_CLUSTER_STATE=\"existing\"\n") 137 return nil 138 } 139 140 func actionMemberRemove(c *cli.Context) error { 141 args := c.Args() 142 if len(args) != 1 { 143 fmt.Fprintln(os.Stderr, "Provide a single member ID") 144 os.Exit(1) 145 } 146 removalID := args[0] 147 148 mAPI := mustNewMembersAPI(c) 149 150 ctx, cancel := contextWithTotalTimeout(c) 151 defer cancel() 152 // Get the list of members. 153 members, err := mAPI.List(ctx) 154 if err != nil { 155 fmt.Fprintln(os.Stderr, "Error while verifying ID against known members:", err.Error()) 156 os.Exit(1) 157 } 158 // Sanity check the input. 159 foundID := false 160 for _, m := range members { 161 if m.ID == removalID { 162 foundID = true 163 } 164 if m.Name == removalID { 165 // Note that, so long as it's not ambiguous, we *could* do the right thing by name here. 166 fmt.Fprintf(os.Stderr, "Found a member named %s; if this is correct, please use its ID, eg:\n\tetcdctl member remove %s\n", m.Name, m.ID) 167 fmt.Fprintf(os.Stderr, "For more details, read the documentation at https://github.com/coreos/etcd/blob/master/Documentation/runtime-configuration.md#remove-a-member\n\n") 168 } 169 } 170 if !foundID { 171 fmt.Fprintf(os.Stderr, "Couldn't find a member in the cluster with an ID of %s.\n", removalID) 172 os.Exit(1) 173 } 174 175 // Actually attempt to remove the member. 176 err = mAPI.Remove(ctx, removalID) 177 if err != nil { 178 fmt.Fprintf(os.Stderr, "Received an error trying to remove member %s: %s", removalID, err.Error()) 179 os.Exit(1) 180 } 181 182 fmt.Printf("Removed member %s from cluster\n", removalID) 183 return nil 184 } 185 186 func actionMemberUpdate(c *cli.Context) error { 187 args := c.Args() 188 if len(args) != 2 { 189 fmt.Fprintln(os.Stderr, "Provide an ID and a list of comma separated peerURL (0xabcd http://example.com,http://example1.com)") 190 os.Exit(1) 191 } 192 193 mAPI := mustNewMembersAPI(c) 194 195 mid := args[0] 196 urls := args[1] 197 ctx, cancel := contextWithTotalTimeout(c) 198 err := mAPI.Update(ctx, mid, strings.Split(urls, ",")) 199 cancel() 200 if err != nil { 201 fmt.Fprintln(os.Stderr, err.Error()) 202 os.Exit(1) 203 } 204 205 fmt.Printf("Updated member with ID %s in cluster\n", mid) 206 return nil 207 }