etcdctl/ctlv3/command/member_command.go
func memberAddCommandFunc(cmd *cobra.Command, args []string) {
	if len(args) < 1 {
		ExitWithError(ExitBadArgs, errors.New("member name not provided"))
	}
	if len(args) > 1 {
		ev := "too many arguments"
		for _, s := range args {
			if strings.HasPrefix(strings.ToLower(s), "http") {
				ev += fmt.Sprintf(`, did you mean --peer-urls=%s`, s)
			}
		}
		ExitWithError(ExitBadArgs, errors.New(ev))
	}
	newMemberName := args[0]

	if len(memberPeerURLs) == 0 {
		ExitWithError(ExitBadArgs, errors.New("member peer urls not provided"))
	}

	urls := strings.Split(memberPeerURLs, ",")
	ctx, cancel := commandCtx(cmd)
	cli := mustClientFromCmd(cmd)
	resp, err := cli.MemberAdd(ctx, urls)
	cancel()
	if err != nil {
		ExitWithError(ExitError, err)
	}
	newID := resp.Member.ID

	display.MemberAdd(*resp)

	if _, ok := (display).(*simplePrinter); ok {
		ctx, cancel = commandCtx(cmd)
		listResp, err := cli.MemberList(ctx)
		// get latest member list; if there's failover new member might have outdated list
		for {
			if err != nil {
				ExitWithError(ExitError, err)
			}
			if listResp.Header.MemberId == resp.Header.MemberId {
				break
			}
			// quorum get to sync cluster list
			gresp, gerr := cli.Get(ctx, "_")
			if gerr != nil {
				ExitWithError(ExitError, err)
			}
			resp.Header.MemberId = gresp.Header.MemberId
			listResp, err = cli.MemberList(ctx)
		}
		cancel()

		conf := []string{}
		for _, memb := range listResp.Members {
			for _, u := range memb.PeerURLs {
				n := memb.Name
				if memb.ID == newID {
					n = newMemberName
				}
				conf = append(conf, fmt.Sprintf("%s=%s", n, u))
			}
		}

		fmt.Print("\n")
		fmt.Printf("ETCD_NAME=%q\n", newMemberName)
		fmt.Printf("ETCD_INITIAL_CLUSTER=%q\n", strings.Join(conf, ","))
		fmt.Printf("ETCD_INITIAL_ADVERTISE_PEER_URLS=%q\n", memberPeerURLs)
		fmt.Printf("ETCD_INITIAL_CLUSTER_STATE=\"existing\"\n")
	}
}
func memberAddCommandFunc(cmd *cobra.Command, args []string) {
	if len(args) < 1 {
		ExitWithError(ExitBadArgs, errors.New("member name not provided"))
	}
	if len(args) > 1 {
		ev := "too many arguments"
		for _, s := range args {
			if strings.HasPrefix(strings.ToLower(s), "http") {
				ev += fmt.Sprintf(`, did you mean --peer-urls=%s`, s)
			}
		}
		ExitWithError(ExitBadArgs, errors.New(ev))
	}
	newMemberName := args[0]

	if len(memberPeerURLs) == 0 {
		ExitWithError(ExitBadArgs, errors.New("member peer urls not provided"))
	}

	urls := strings.Split(memberPeerURLs, ",")
	ctx, cancel := commandCtx(cmd)
	cli := mustClientFromCmd(cmd)
	resp, err := cli.MemberAdd(ctx, urls)
	cancel()
	if err != nil {
		ExitWithError(ExitError, err)
	}
	newID := resp.Member.ID

	display.MemberAdd(*resp)

	if _, ok := (display).(*simplePrinter); ok {
		ctx, cancel = commandCtx(cmd)
		listResp, err := cli.MemberList(ctx)
		// get latest member list; if there's failover new member might have outdated list
		for {
			if err != nil {
				ExitWithError(ExitError, err)
			}
			if listResp.Header.MemberId == resp.Header.MemberId {
				break
			}
			// quorum get to sync cluster list
			gresp, gerr := cli.Get(ctx, "_")
			if gerr != nil {
				ExitWithError(ExitError, err)
			}
			resp.Header.MemberId = gresp.Header.MemberId
			listResp, err = cli.MemberList(ctx)
		}
		cancel()

		conf := []string{}
		for _, memb := range listResp.Members {
			for _, u := range memb.PeerURLs {
				n := memb.Name
				if memb.ID == newID {
					n = newMemberName
				}
				conf = append(conf, fmt.Sprintf("%s=%s", n, u))
			}
		}

		fmt.Print("\n")
		fmt.Printf("ETCD_NAME=%q\n", newMemberName)
		fmt.Printf("ETCD_INITIAL_CLUSTER=%q\n", strings.Join(conf, ","))
		fmt.Printf("ETCD_INITIAL_ADVERTISE_PEER_URLS=%q\n", memberPeerURLs)
		fmt.Printf("ETCD_INITIAL_CLUSTER_STATE=\"existing\"\n")
	}
}
// memberAddCommandFunc executes the "member add" command.