Repository: kubernetes/kubernetes
File: cmd/kubeadm/app/util/etcd/etcd.go (Go)

Code before:
	started = true
	if len(m.Name) == 0 {
		started = false
	}

	return m.IsLearner, started, nil
}

// MemberPromote promotes a member as a voting member. If the given member ID is already a voting member this method
// will return early and do nothing. It waits for the member to be started before attempting to promote.
func (c *Client) MemberPromote(learnerID uint64) error {
	isLearner, started, err := c.getMemberStatus(learnerID)
	if err != nil {
		return err
	}
	if !isLearner {
		klog.V(1).Infof("[etcd] Member %s already promoted.", strconv.FormatUint(learnerID, 16))
		return nil
	}

	// Wait for the member to be started before promoting
	var (
		lastError error
	)
	if !started {
		klog.V(1).Infof("[etcd] Waiting for member %s to be started before promoting", strconv.FormatUint(learnerID, 16))
		err = wait.PollUntilContextTimeout(context.Background(), constants.EtcdAPICallRetryInterval, kubeadmapi.GetActiveTimeouts().EtcdAPICall.Duration,
			true, func(_ context.Context) (bool, error) {
				var err error
				_, started, err = c.getMemberStatus(learnerID)
				if err != nil {
					lastError = fmt.Errorf("failed to get member %s status: %v", strconv.FormatUint(learnerID, 16), err)
					return false, nil
				}
				if !started {
					klog.V(1).Infof("[etcd] Member %s is not started yet", strconv.FormatUint(learnerID, 16))
					lastError = fmt.Errorf("the etcd member %s is not started", strconv.FormatUint(learnerID, 16))
					return false, nil
				}
				return true, nil
			})
		if err != nil {
			return lastError
		}
	}

	// always reset the last error to nil
	lastError = nil

	klog.V(1).Infof("[etcd] Promoting a learner as a voting member: %s", strconv.FormatUint(learnerID, 16))
	cli, err := c.newEtcdClient(c.Endpoints)

Review comment (refactor):
we should make this a reusable var on top of the function, e.g.
`learnerIDUint := strconv.FormatUint(learnerID, 16)`

Code after:
	var m *etcdserverpb.Member
	for _, member := range resp.Members {
		if member.ID == memberID {
			m = member
			break
		}
	}
	if m == nil {
		return false, false, fmt.Errorf("member %s not found", strconv.FormatUint(memberID, 16))
	}

	started = true
	// There is no field for "started".
	// If the member is not started, the Name and ClientURLs fields are set to their respective zero values.
	if len(m.Name) == 0 {
		started = false
	}

	return m.IsLearner, started, nil
}

// MemberPromote promotes a member as a voting member. If the given member ID is already a voting member this method
// will return early and do nothing. It waits for the member to be started before attempting to promote.
func (c *Client) MemberPromote(learnerID uint64) error {
	var (
		lastError     error
		learnerIDUint = strconv.FormatUint(learnerID, 16)
	)

	klog.V(1).Infof("[etcd] Waiting for a learner to start: %s", learnerIDUint)

	err := wait.PollUntilContextTimeout(context.Background(), constants.EtcdAPICallRetryInterval, kubeadmapi.GetActiveTimeouts().EtcdAPICall.Duration,
		true, func(_ context.Context) (bool, error) {
			isLearner, started, err := c.getMemberStatus(learnerID)
			if err != nil {
				lastError = errors.WithMessagef(err, "failed to get member %s status", learnerIDUint)
				return false, nil
			}
			if !isLearner {
				klog.V(1).Infof("[etcd] Member %s was already promoted.", learnerIDUint)
				return true, nil
			}
			if !started {
				klog.V(1).Infof("[etcd] Member %s is not started yet. Waiting for it to be started.", learnerIDUint)
				lastError = errors.Errorf("the etcd member %s is not started", learnerIDUint)
				return false, nil
			}
			return true, nil
		})
	if err != nil {
		return lastError