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

Code before:
		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
	}

	learnerIDUint := strconv.FormatUint(learnerID, 16)

	if !isLearner {
		klog.V(1).Infof("[etcd] Member %s already promoted.", learnerIDUint)
		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", learnerIDUint)
		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 = errors.WithMessagef(err, "failed to get member %s status", learnerIDUint)
					return false, nil
				}
				if !started {
					klog.V(1).Infof("[etcd] Member %s is not started yet", learnerIDUint)
					lastError = errors.Errorf("the etcd member %s is not started", learnerIDUint)
					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", learnerIDUint)

Review comment (suggestion):
```suggestion
```

can be moved on top of the function:

```
func (c *Client) MemberPromote(learnerID uint64) error {
    var (
        lastError error
        learnerIDUint = strconv.FormatUint(learnerID, 16)
    )
```

Code after:
		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
	}