mirror of
				https://github.com/etcd-io/etcd.git
				synced 2024-09-27 06:25:44 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			208 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			208 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 The etcd Authors
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //     http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package command
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/urfave/cli"
 | |
| )
 | |
| 
 | |
| func NewMemberCommand() cli.Command {
 | |
| 	return cli.Command{
 | |
| 		Name:  "member",
 | |
| 		Usage: "member add, remove and list subcommands",
 | |
| 		Subcommands: []cli.Command{
 | |
| 			{
 | |
| 				Name:      "list",
 | |
| 				Usage:     "enumerate existing cluster members",
 | |
| 				ArgsUsage: " ",
 | |
| 				Action:    actionMemberList,
 | |
| 			},
 | |
| 			{
 | |
| 				Name:      "add",
 | |
| 				Usage:     "add a new member to the etcd cluster",
 | |
| 				ArgsUsage: "<name> <peerURL>",
 | |
| 				Action:    actionMemberAdd,
 | |
| 			},
 | |
| 			{
 | |
| 				Name:      "remove",
 | |
| 				Usage:     "remove an existing member from the etcd cluster",
 | |
| 				ArgsUsage: "<memberID>",
 | |
| 				Action:    actionMemberRemove,
 | |
| 			},
 | |
| 			{
 | |
| 				Name:      "update",
 | |
| 				Usage:     "update an existing member in the etcd cluster",
 | |
| 				ArgsUsage: "<memberID> <peerURLs>",
 | |
| 				Action:    actionMemberUpdate,
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func actionMemberList(c *cli.Context) error {
 | |
| 	if len(c.Args()) != 0 {
 | |
| 		fmt.Fprintln(os.Stderr, "No arguments accepted")
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 	mAPI := mustNewMembersAPI(c)
 | |
| 	ctx, cancel := contextWithTotalTimeout(c)
 | |
| 	defer cancel()
 | |
| 
 | |
| 	members, err := mAPI.List(ctx)
 | |
| 	if err != nil {
 | |
| 		fmt.Fprintln(os.Stderr, err.Error())
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 	leader, err := mAPI.Leader(ctx)
 | |
| 	if err != nil {
 | |
| 		fmt.Fprintln(os.Stderr, "Failed to get leader: ", err)
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 
 | |
| 	for _, m := range members {
 | |
| 		isLeader := false
 | |
| 		if m.ID == leader.ID {
 | |
| 			isLeader = true
 | |
| 		}
 | |
| 		if len(m.Name) == 0 {
 | |
| 			fmt.Printf("%s[unstarted]: peerURLs=%s\n", m.ID, strings.Join(m.PeerURLs, ","))
 | |
| 		} else {
 | |
| 			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)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func actionMemberAdd(c *cli.Context) error {
 | |
| 	args := c.Args()
 | |
| 	if len(args) != 2 {
 | |
| 		fmt.Fprintln(os.Stderr, "Provide a name and a single member peerURL")
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 
 | |
| 	mAPI := mustNewMembersAPI(c)
 | |
| 
 | |
| 	url := args[1]
 | |
| 	ctx, cancel := contextWithTotalTimeout(c)
 | |
| 	defer cancel()
 | |
| 
 | |
| 	m, err := mAPI.Add(ctx, url)
 | |
| 	if err != nil {
 | |
| 		fmt.Fprintln(os.Stderr, err.Error())
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 
 | |
| 	newID := m.ID
 | |
| 	newName := args[0]
 | |
| 	fmt.Printf("Added member named %s with ID %s to cluster\n", newName, newID)
 | |
| 
 | |
| 	members, err := mAPI.List(ctx)
 | |
| 	if err != nil {
 | |
| 		fmt.Fprintln(os.Stderr, err.Error())
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 
 | |
| 	conf := []string{}
 | |
| 	for _, memb := range members {
 | |
| 		for _, u := range memb.PeerURLs {
 | |
| 			n := memb.Name
 | |
| 			if memb.ID == newID {
 | |
| 				n = newName
 | |
| 			}
 | |
| 			conf = append(conf, fmt.Sprintf("%s=%s", n, u))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	fmt.Print("\n")
 | |
| 	fmt.Printf("ETCD_NAME=%q\n", newName)
 | |
| 	fmt.Printf("ETCD_INITIAL_CLUSTER=%q\n", strings.Join(conf, ","))
 | |
| 	fmt.Printf("ETCD_INITIAL_CLUSTER_STATE=\"existing\"\n")
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func actionMemberRemove(c *cli.Context) error {
 | |
| 	args := c.Args()
 | |
| 	if len(args) != 1 {
 | |
| 		fmt.Fprintln(os.Stderr, "Provide a single member ID")
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 	removalID := args[0]
 | |
| 
 | |
| 	mAPI := mustNewMembersAPI(c)
 | |
| 
 | |
| 	ctx, cancel := contextWithTotalTimeout(c)
 | |
| 	defer cancel()
 | |
| 	// Get the list of members.
 | |
| 	members, err := mAPI.List(ctx)
 | |
| 	if err != nil {
 | |
| 		fmt.Fprintln(os.Stderr, "Error while verifying ID against known members:", err.Error())
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 	// Sanity check the input.
 | |
| 	foundID := false
 | |
| 	for _, m := range members {
 | |
| 		if m.ID == removalID {
 | |
| 			foundID = true
 | |
| 		}
 | |
| 		if m.Name == removalID {
 | |
| 			// Note that, so long as it's not ambiguous, we *could* do the right thing by name here.
 | |
| 			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)
 | |
| 			fmt.Fprintf(os.Stderr, "For more details, read the documentation at https://github.com/etcd-io/etcd/blob/main/Documentation/runtime-configuration.md#remove-a-member\n\n")
 | |
| 		}
 | |
| 	}
 | |
| 	if !foundID {
 | |
| 		fmt.Fprintf(os.Stderr, "Couldn't find a member in the cluster with an ID of %s.\n", removalID)
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 
 | |
| 	// Actually attempt to remove the member.
 | |
| 	err = mAPI.Remove(ctx, removalID)
 | |
| 	if err != nil {
 | |
| 		fmt.Fprintf(os.Stderr, "Received an error trying to remove member %s: %s", removalID, err.Error())
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 
 | |
| 	fmt.Printf("Removed member %s from cluster\n", removalID)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func actionMemberUpdate(c *cli.Context) error {
 | |
| 	args := c.Args()
 | |
| 	if len(args) != 2 {
 | |
| 		fmt.Fprintln(os.Stderr, "Provide an ID and a list of comma separated peerURL (0xabcd http://example.com,http://example1.com)")
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 
 | |
| 	mAPI := mustNewMembersAPI(c)
 | |
| 
 | |
| 	mid := args[0]
 | |
| 	urls := args[1]
 | |
| 	ctx, cancel := contextWithTotalTimeout(c)
 | |
| 	err := mAPI.Update(ctx, mid, strings.Split(urls, ","))
 | |
| 	cancel()
 | |
| 	if err != nil {
 | |
| 		fmt.Fprintln(os.Stderr, err.Error())
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 
 | |
| 	fmt.Printf("Updated member with ID %s in cluster\n", mid)
 | |
| 	return nil
 | |
| }
 | 
