func (c *etcdV3Client) Delete(ctx context.Context, k model.Key, revision string) (*model.KVPair, error) {
	logCxt := log.WithFields(log.Fields{"model-etcdKey": k, "rev": revision})
	logCxt.Debug("Processing Delete request")
	key, err := model.KeyToDefaultDeletePath(k)
	if err != nil {
		return nil, err
	}
	logCxt = logCxt.WithField("etcdv3-etcdKey", key)
	conds := []clientv3.Cmp{}
	if len(revision) != 0 {
		rev, err := parseRevision(revision)
		if err != nil {
			return nil, err
		}
		conds = append(conds, clientv3.Compare(clientv3.ModRevision(key), "=", rev))
	}
	logCxt.Debug("Performing etcdv3 transaction for Delete request")
	txnResp, err := c.etcdClient.Txn(ctx).If(
		conds...,
	).Then(
		clientv3.OpDelete(key, clientv3.WithPrevKV()),
	).Else(
		clientv3.OpGet(key),
	).Commit()
	if err != nil {
		logCxt.WithError(err).Warning("Delete failed")
		return nil, cerrors.ErrorDatastoreError{Err: err, Identifier: k}
	}
	if !txnResp.Succeeded {
		logCxt.Debug("Delete transaction failed due to resource update conflict")
		getResp := txnResp.Responses[0].GetResponseRange()
		if len(getResp.Kvs) == 0 {
			logCxt.Debug("Delete transaction failed due to resource not existing")
			return nil, cerrors.ErrorResourceDoesNotExist{Identifier: k}
		}
		latestValue, err := etcdToKVPair(k, getResp.Kvs[0])
		if err != nil {
			return nil, err
		}
		return latestValue, cerrors.ErrorResourceUpdateConflict{Identifier: k}
	}
	delResp := txnResp.Responses[0].GetResponseDeleteRange()
	if delResp.Deleted == 0 {
		logCxt.Debug("Delete transaction failed due to resource not existing")
		return nil, cerrors.ErrorResourceDoesNotExist{Identifier: k}
	}
	previousValue, _ := etcdToKVPair(k, delResp.PrevKvs[0])
	return previousValue, nil
}
Delete an entry in the datastore.  This errors if the entry does not exists.
package etcdv3
import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"
	"crypto/tls"
	log "github.com/sirupsen/logrus"
	"go.etcd.io/etcd/clientv3"
	"go.etcd.io/etcd/pkg/srv"
	"go.etcd.io/etcd/pkg/transport"
	apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3"
	"github.com/projectcalico/libcalico-go/lib/apiconfig"
	"github.com/projectcalico/libcalico-go/lib/backend/api"
	"github.com/projectcalico/libcalico-go/lib/backend/model"
	cerrors "github.com/projectcalico/libcalico-go/lib/errors"
	"github.com/projectcalico/libcalico-go/lib/resources"
)
var (
	clientTimeout                  = 10 * time.Second
	keepaliveTime                  = 30 * time.Second
	keepaliveTimeout               = 10 * time.Second
	defaultAllowProfileResourceKey = model.ResourceKey{Name: "projectcalico-default-allow", Kind: apiv3.KindProfile}
)
const (
	profilesKey            = "/calico/resources/v3/projectcalico.org/profiles/"
	defaultAllowProfileKey = "/calico/resources/v3/projectcalico.org/profiles/projectcalico-default-allow"
)
type etcdV3Client struct {
	etcdClient *clientv3.Client
}
func NewEtcdV3Client(config *apiconfig.EtcdConfig) (api.Client, error) {
	if config.EtcdEndpoints != "" && config.EtcdDiscoverySrv != "" {
		log.Warning("Multiple etcd endpoint discovery methods specified in etcdv3 API config")
		return nil, errors.New("multiple discovery or bootstrap options specified, use either \"etcdEndpoints\" or \"etcdDiscoverySrv\"")
	}
	etcdLocation := []string{}
	if config.EtcdEndpoints != "" {
		etcdLocation = strings.Split(config.EtcdEndpoints, ",")
	}
	if config.EtcdDiscoverySrv != "" {
		srvs, srvErr := srv.GetClient("etcd-client", config.EtcdDiscoverySrv, "")
		if srvErr != nil {
			return nil, fmt.Errorf("failed to discover etcd endpoints through SRV discovery: %v", srvErr)
		}
		etcdLocation = srvs.Endpoints
	}
	if len(etcdLocation) == 0 {
		log.Warning("No etcd endpoints specified in etcdv3 API config")
		return nil, errors.New("no etcd endpoints specified")
	}
	var tls *tls.Config
	var err error
	haveInline := config.EtcdCert != "" || config.EtcdKey != "" || config.EtcdCACert != ""
	haveFiles := config.EtcdCertFile != "" || config.EtcdKeyFile != "" || config.EtcdCACertFile != ""
	if haveInline && haveFiles {
		return nil, fmt.Errorf("Cannot mix inline certificate-key and certificate / key files")
	}
	if haveInline {
		tlsInfo := &TlsInlineCertKey{
			CACert: config.EtcdCACert,
			Cert:   config.EtcdCert,
			Key:    config.EtcdKey,
		}
		tls, err = tlsInfo.ClientConfigInlineCertKey()
	} else {
		tlsInfo := &transport.TLSInfo{
			TrustedCAFile: config.EtcdCACertFile,
			CertFile:      config.EtcdCertFile,
			KeyFile:       config.EtcdKeyFile,
		}
		tls, err = tlsInfo.ClientConfig()
	}
	if err != nil {
		return nil, fmt.Errorf("could not initialize etcdv3 client: %+v", err)
	}
	cfg := clientv3.Config{
		Endpoints:            etcdLocation,
		TLS:                  tls,
		DialTimeout:          clientTimeout,
		DialKeepAliveTime:    keepaliveTime,
		DialKeepAliveTimeout: keepaliveTimeout,
	}
	if config.EtcdUsername != "" && config.EtcdPassword != "" {
		cfg.Username = config.EtcdUsername
		cfg.Password = config.EtcdPassword
	}
	client, err := clientv3.New(cfg)
	if err != nil {
		return nil, err
	}
	return &etcdV3Client{etcdClient: client}, nil
}
func (c *etcdV3Client) Create(ctx context.Context, d *model.KVPair) (*model.KVPair, error) {
	logCxt := log.WithFields(log.Fields{"model-etcdKey": d.Key, "value": d.Value, "ttl": d.TTL, "rev": d.Revision})
	logCxt.Debug("Processing Create request")
	key, value, err := getKeyValueStrings(d)
	if err != nil {
		return nil, err
	}
	logCxt = logCxt.WithField("etcdv3-etcdKey", key)
	putOpts, err := c.getTTLOption(ctx, d)
	if err != nil {
		return nil, err
	}
	logCxt.Debug("Performing etcdv3 transaction for Create request")
	txnResp, err := c.etcdClient.Txn(ctx).If(
		clientv3.Compare(clientv3.Version(key), "=", 0),
	).Then(
		clientv3.OpPut(key, value, putOpts...),
	).Else(
		clientv3.OpGet(key),
	).Commit()
	if err != nil {
		logCxt.WithError(err).Warning("Create failed")
		return nil, cerrors.ErrorDatastoreError{Err: err}
	}
	if !txnResp.Succeeded {
		logCxt.Debug("Create transaction failed due to resource already existing")
		var existing *model.KVPair
		getResp := (*clientv3.GetResponse)(txnResp.Responses[0].GetResponseRange())
		if len(getResp.Kvs) != 0 {
			existing, _ = etcdToKVPair(d.Key, getResp.Kvs[0])
		}
		return existing, cerrors.ErrorResourceAlreadyExists{Identifier: d.Key}
	}
	v, err := model.ParseValue(d.Key, []byte(value))
	if err != nil {
		return nil, cerrors.ErrorPartialFailure{Err: fmt.Errorf("Unexpected error parsing stored datastore entry '%v': %+v", value, err)}
	}
	d.Value = v
	d.Revision = strconv.FormatInt(txnResp.Header.Revision, 10)
	return d, nil
}
func (c *etcdV3Client) Update(ctx context.Context, d *model.KVPair) (*model.KVPair, error) {
	logCxt := log.WithFields(log.Fields{"model-etcdKey": d.Key, "value": d.Value, "ttl": d.TTL, "rev": d.Revision})
	logCxt.Debug("Processing Update request")
	key, value, err := getKeyValueStrings(d)
	if err != nil {
		return nil, err
	}
	logCxt = logCxt.WithField("etcdv3-etcdKey", key)
	opts, err := c.getTTLOption(ctx, d)
	if err != nil {
		return nil, err
	}
	rev, err := parseRevision(d.Revision)
	if err != nil {
		return nil, err
	}
	conds := []clientv3.Cmp{clientv3.Compare(clientv3.ModRevision(key), "=", rev)}
	logCxt.Debug("Performing etcdv3 transaction for Update request")
	txnResp, err := c.etcdClient.Txn(ctx).If(
		conds...,
	).Then(
		clientv3.OpPut(key, value, opts...),
	).Else(
		clientv3.OpGet(key),
	).Commit()
	if err != nil {
		logCxt.WithError(err).Warning("Update failed")
		return nil, cerrors.ErrorDatastoreError{Err: err}
	}
	if !txnResp.Succeeded {
		getResp := (*clientv3.GetResponse)(txnResp.Responses[0].GetResponseRange())
		if len(getResp.Kvs) == 0 {
			logCxt.Debug("Update transaction failed due to resource not existing")
			return nil, cerrors.ErrorResourceDoesNotExist{Identifier: d.Key}
		}
		logCxt.Debug("Update transaction failed due to resource update conflict")
		existing, _ := etcdToKVPair(d.Key, getResp.Kvs[0])
		return existing, cerrors.ErrorResourceUpdateConflict{Identifier: d.Key}
	}
	v, err := model.ParseValue(d.Key, []byte(value))
	cerrors.PanicIfErrored(err, "Unexpected error parsing stored datastore entry: %v", value)
	d.Value = v
	d.Revision = strconv.FormatInt(txnResp.Header.Revision, 10)
	return d, nil
}
func (c *etcdV3Client) Apply(ctx context.Context, d *model.KVPair) (*model.KVPair, error) {
	logCxt := log.WithFields(log.Fields{"etcdKey": d.Key, "value": d.Value, "ttl": d.TTL, "rev": d.Revision})
	logCxt.Debug("Processing Apply request")
	key, value, err := getKeyValueStrings(d)
	if err != nil {
		return nil, err
	}
	putOpts, err := c.getTTLOption(ctx, d)
	if err != nil {
		return nil, err
	}
	logCxt.Debug("Performing etcdv3 Put for Apply request")
	resp, err := c.etcdClient.Put(ctx, key, value, putOpts...)
	if err != nil {
		logCxt.WithError(err).Warning("Apply failed")
		return nil, cerrors.ErrorDatastoreError{Err: err}
	}
	v, err := model.ParseValue(d.Key, []byte(value))
	cerrors.PanicIfErrored(err, "Unexpected error parsing stored datastore entry: %v", value)
	d.Value = v
	d.Revision = strconv.FormatInt(resp.Header.Revision, 10)
	return d, nil
}
func (c *etcdV3Client) DeleteKVP(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) {
	return c.Delete(ctx, kvp.Key, kvp.Revision)
}