func (s *Etcd) NewLock(key string, options *store.LockOptions) (lock store.Locker, err error) {
	var value string
	ttl := defaultLockTTL
	renewCh := make(chan struct{})
	if options != nil {
		if options.Value != nil {
			value = string(options.Value)
		}
		if options.TTL != 0 {
			ttl = options.TTL
		}
		if options.RenewLock != nil {
			renewCh = options.RenewLock
		}
	}
	lock = &etcdLock{
		client:    s.client,
		stopRenew: renewCh,
		mutexKey:  s.normalize(key + lockSuffix),
		writeKey:  s.normalize(key),
		value:     value,
		ttl:       ttl,
	}
	return lock, nil
}
NewLock returns a handle to a lock struct which can
be used to provide mutual exclusion on a key
package etcd
import (
	"context"
	"crypto/tls"
	"errors"
	"log"
	"net"
	"net/http"
	"strings"
	"sync"
	"time"
	"github.com/abronan/valkeyrie"
	"github.com/abronan/valkeyrie/store"
	etcd "go.etcd.io/etcd/client/v2"
)
const (
	lockSuffix = "___lock"
)
var (
	ErrAbortTryLock = errors.New("lock operation aborted")
)
type Etcd struct {
	client etcd.KeysAPI
}
type etcdLock struct {
	lock   sync.Mutex
	client etcd.KeysAPI
	stopLock  chan struct{}
	stopRenew chan struct{}
	mutexKey string
	writeKey string
	value    string
	last     *etcd.Response
	ttl      time.Duration
}
const (
	defaultLockTTL    = 20 * time.Second
	defaultUpdateTime = 5 * time.Second
)
func Register() {
	valkeyrie.AddStore(store.ETCD, New)
}
func New(addrs []string, options *store.Config) (store.Store, error) {
	s := &Etcd{}
	var (
		entries []string
		err     error
	)
	entries = store.CreateEndpoints(addrs, "http")
	cfg := &etcd.Config{
		Endpoints:               entries,
		Transport:               etcd.DefaultTransport,
		HeaderTimeoutPerRequest: 3 * time.Second,
	}
	if options != nil {
		if options.TLS != nil {
			setTLS(cfg, options.TLS, addrs)
		}
		if options.ConnectionTimeout != 0 {
			setTimeout(cfg, options.ConnectionTimeout)
		}
		if options.Username != "" {
			setCredentials(cfg, options.Username, options.Password)
		}
	}
	c, err := etcd.New(*cfg)
	if err != nil {
		log.Fatal(err)
	}
	s.client = etcd.NewKeysAPI(c)
	if options != nil && options.SyncPeriod != 0 {
		go func() {
			for {
				_ = c.AutoSync(context.Background(), options.SyncPeriod)
			}
		}()
	}
	return s, nil
}
func setTLS(cfg *etcd.Config, tls *tls.Config, addrs []string) {
	entries := store.CreateEndpoints(addrs, "https")
	cfg.Endpoints = entries
	t := http.Transport{
		DialContext: (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
		}).DialContext,
		TLSHandshakeTimeout: 10 * time.Second,
		TLSClientConfig:     tls,
	}
	cfg.Transport = &t
}
func setTimeout(cfg *etcd.Config, time time.Duration) {
	cfg.HeaderTimeoutPerRequest = time
}
func setCredentials(cfg *etcd.Config, username, password string) {
	cfg.Username = username
	cfg.Password = password
}
func (s *Etcd) normalize(key string) string {
	key = store.Normalize(key)
	return strings.TrimPrefix(key, "/")
}
func keyNotFound(err error) bool {
	if err != nil {
		if etcdError, ok := err.(etcd.Error); ok {
			if etcdError.Code == etcd.ErrorCodeKeyNotFound ||
				etcdError.Code == etcd.ErrorCodeNotFile ||
				etcdError.Code == etcd.ErrorCodeNotDir {
				return true
			}
		}
	}
	return false
}
func (s *Etcd) Get(key string, opts *store.ReadOptions) (pair *store.KVPair, err error) {
	getOpts := &etcd.GetOptions{
		Quorum: true,
	}
	if opts != nil {
		getOpts.Quorum = opts.Consistent
	}
	result, err := s.client.Get(context.Background(), s.normalize(key), getOpts)
	if err != nil {
		if keyNotFound(err) {
			return nil, store.ErrKeyNotFound
		}
		return nil, err
	}
	pair = &store.KVPair{
		Key:       key,
		Value:     []byte(result.Node.Value),
		LastIndex: result.Node.ModifiedIndex,
	}
	return pair, nil
}
func (s *Etcd) Put(key string, value []byte, opts *store.WriteOptions) error {
	setOpts := &etcd.SetOptions{}
	if opts != nil {
		setOpts.Dir = opts.IsDir
		setOpts.TTL = opts.TTL
	}
	_, err := s.client.Set(context.Background(), s.normalize(key), string(value), setOpts)
	return err
}
func (s *Etcd) Delete(key string) error {
	opts := &etcd.DeleteOptions{
		Recursive: false,
	}
	_, err := s.client.Delete(context.Background(), s.normalize(key), opts)
	if keyNotFound(err) {
		return store.ErrKeyNotFound
	}
	return err
}
func (s *Etcd) Exists(key string, opts *store.ReadOptions) (bool, error) {
	_, err := s.Get(key, opts)
	if err != nil {
		if err == store.ErrKeyNotFound {
			return false, nil
		}
		return false, err
	}
	return true, nil
}
func (s *Etcd) Watch(key string, stopCh <-chan struct{}, opts *store.ReadOptions) (<-chan *store.KVPair, error) {
	wopts := &etcd.WatcherOptions{Recursive: false}
	watcher := s.client.Watcher(s.normalize(key), wopts)
	watchCh := make(chan *store.KVPair)
	pair, err := s.Get(key, opts)
	if err != nil {
		return nil, err
	}
	go func() {
		defer close(watchCh)
		watchCh <- pair
		for {
			select {
			case <-stopCh:
				return
			default:
			}
			result, err := watcher.Next(context.Background())
			if err != nil {
				return
			}
			watchCh <- &store.KVPair{
				Key:       key,
				Value:     []byte(result.Node.Value),
				LastIndex: result.Node.ModifiedIndex,
			}
		}
	}()
	return watchCh, nil
}
func (s *Etcd) WatchTree(directory string, stopCh <-chan struct{}, opts *store.ReadOptions) (<-chan []*store.KVPair, error) {
	watchOpts := &etcd.WatcherOptions{Recursive: true}
	watcher := s.client.Watcher(s.normalize(directory), watchOpts)
	watchCh := make(chan []*store.KVPair)
	list, err := s.List(directory, opts)
	if err != nil {
		return nil, err
	}
	go func() {
		defer close(watchCh)
		watchCh <- list
		for {
			select {
			case <-stopCh:
				return
			default:
			}
			_, err := watcher.Next(context.Background())
			if err != nil {
				return
			}
			list, err = s.List(directory, opts)
			if err != nil {
				return
			}
			watchCh <- list
		}
	}()
	return watchCh, nil
}
func (s *Etcd) AtomicPut(key string, value []byte, previous *store.KVPair, opts *store.WriteOptions) (bool, *store.KVPair, error) {
	var (
		meta *etcd.Response
		err  error
	)
	setOpts := &etcd.SetOptions{}
	if previous != nil {
		setOpts.PrevExist = etcd.PrevExist
		setOpts.PrevIndex = previous.LastIndex
		if previous.Value != nil {
			setOpts.PrevValue = string(previous.Value)
		}
	} else {
		setOpts.PrevExist = etcd.PrevNoExist
	}
	if opts != nil {
		if opts.TTL > 0 {
			setOpts.TTL = opts.TTL
		}
	}
	meta, err = s.client.Set(context.Background(), s.normalize(key), string(value), setOpts)
	if err != nil {
		if etcdError, ok := err.(etcd.Error); ok {
			if etcdError.Code == etcd.ErrorCodeTestFailed {
				return false, nil, store.ErrKeyModified
			}
			if etcdError.Code == etcd.ErrorCodeNodeExist {
				return false, nil, store.ErrKeyExists
			}
		}
		return false, nil, err
	}
	updated := &store.KVPair{
		Key:       key,
		Value:     value,
		LastIndex: meta.Node.ModifiedIndex,
	}
	return true, updated, nil
}
func (s *Etcd) AtomicDelete(key string, previous *store.KVPair) (bool, error) {
	if previous == nil {
		return false, store.ErrPreviousNotSpecified
	}
	delOpts := &etcd.DeleteOptions{}
	delOpts.PrevIndex = previous.LastIndex
	if previous.Value != nil {
		delOpts.PrevValue = string(previous.Value)
	}
	_, err := s.client.Delete(context.Background(), s.normalize(key), delOpts)
	if err != nil {
		if etcdError, ok := err.(etcd.Error); ok {
			if etcdError.Code == etcd.ErrorCodeKeyNotFound {
				return false, store.ErrKeyNotFound
			}
			if etcdError.Code == etcd.ErrorCodeTestFailed {
				return false, store.ErrKeyModified
			}
		}
		return false, err
	}
	return true, nil
}
func (s *Etcd) List(directory string, opts *store.ReadOptions) ([]*store.KVPair, error) {
	getOpts := &etcd.GetOptions{
		Quorum:    true,
		Recursive: true,
		Sort:      true,
	}
	if opts != nil {
		getOpts.Quorum = opts.Consistent
	}
	resp, err := s.client.Get(context.Background(), s.normalize(directory), getOpts)
	if err != nil {
		if keyNotFound(err) {
			return nil, store.ErrKeyNotFound
		}
		return nil, err
	}
	kv := []*store.KVPair{}
	for _, n := range resp.Node.Nodes {
		if n.Key == directory {
			continue
		}
		if n.Dir {
			pairs, err := s.List(n.Key, opts)
			if err != nil {
				return nil, err
			}
			kv = append(kv, pairs...)
		}
		if strings.Contains(n.Key, lockSuffix) {
			continue
		}
		kv = append(kv, &store.KVPair{
			Key:       n.Key,
			Value:     []byte(n.Value),
			LastIndex: n.ModifiedIndex,
		})
	}
	return kv, nil
}
func (s *Etcd) DeleteTree(directory string) error {
	delOpts := &etcd.DeleteOptions{
		Recursive: true,
	}
	_, err := s.client.Delete(context.Background(), s.normalize(directory), delOpts)
	if keyNotFound(err) {
		return store.ErrKeyNotFound
	}
	return err
}