2018-06-30 20:49:13 +05:30
|
|
|
// Package etcd provides the etcd version 3 backend plugin.
|
2016-03-20 17:44:58 +00:00
|
|
|
package etcd
|
|
|
|
|
|
|
|
|
|
import (
|
2018-04-22 08:34:35 +01:00
|
|
|
"context"
|
2016-03-20 21:36:55 +00:00
|
|
|
"encoding/json"
|
2018-06-30 20:49:13 +05:30
|
|
|
"errors"
|
2016-06-07 20:57:45 +01:00
|
|
|
"fmt"
|
2016-03-20 21:36:55 +00:00
|
|
|
"strings"
|
2016-03-24 17:46:14 +00:00
|
|
|
"time"
|
2016-03-20 21:36:55 +00:00
|
|
|
|
2017-09-14 09:36:06 +01:00
|
|
|
"github.com/coredns/coredns/plugin"
|
|
|
|
|
"github.com/coredns/coredns/plugin/etcd/msg"
|
2018-01-07 16:32:59 +00:00
|
|
|
"github.com/coredns/coredns/plugin/pkg/fall"
|
2020-01-30 09:19:26 +00:00
|
|
|
"github.com/coredns/coredns/plugin/pkg/upstream"
|
2017-02-21 22:51:47 -08:00
|
|
|
"github.com/coredns/coredns/request"
|
2016-03-20 17:44:58 +00:00
|
|
|
|
2016-10-30 15:54:16 +00:00
|
|
|
"github.com/miekg/dns"
|
2021-05-14 23:55:01 -07:00
|
|
|
"go.etcd.io/etcd/api/v3/mvccpb"
|
|
|
|
|
etcdcv3 "go.etcd.io/etcd/client/v3"
|
2016-03-20 17:44:58 +00:00
|
|
|
)
|
|
|
|
|
|
2018-06-30 20:49:13 +05:30
|
|
|
const (
|
|
|
|
|
priority = 10 // default priority when nothing is set
|
|
|
|
|
ttl = 300 // default ttl when nothing is set
|
|
|
|
|
etcdTimeout = 5 * time.Second
|
|
|
|
|
)
|
|
|
|
|
|
2019-02-27 20:25:02 +07:00
|
|
|
var errKeyNotFound = errors.New("key not found")
|
2018-06-30 20:49:13 +05:30
|
|
|
|
2017-09-14 09:36:06 +01:00
|
|
|
// Etcd is a plugin talks to an etcd cluster.
|
2016-03-22 22:44:50 +00:00
|
|
|
type Etcd struct {
|
2018-01-07 16:32:59 +00:00
|
|
|
Next plugin.Handler
|
2018-01-07 14:51:32 -05:00
|
|
|
Fall fall.F
|
2018-01-07 16:32:59 +00:00
|
|
|
Zones []string
|
|
|
|
|
PathPrefix string
|
2019-01-13 16:54:49 +00:00
|
|
|
Upstream *upstream.Upstream
|
2018-06-30 20:49:13 +05:30
|
|
|
Client *etcdcv3.Client
|
2016-09-26 14:43:38 +01:00
|
|
|
|
|
|
|
|
endpoints []string // Stored here as well, to aid in testing.
|
2016-03-20 17:44:58 +00:00
|
|
|
}
|
2016-03-20 18:17:07 +00:00
|
|
|
|
2016-10-30 15:54:16 +00:00
|
|
|
// Services implements the ServiceBackend interface.
|
2019-03-26 14:37:30 +00:00
|
|
|
func (e *Etcd) Services(ctx context.Context, state request.Request, exact bool, opt plugin.Options) (services []msg.Service, err error) {
|
|
|
|
|
services, err = e.Records(ctx, state, exact)
|
2016-10-30 15:54:16 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
2017-09-12 10:52:43 +01:00
|
|
|
|
2016-10-30 15:54:16 +00:00
|
|
|
services = msg.Group(services)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-05 15:43:27 +00:00
|
|
|
// Reverse implements the ServiceBackend interface.
|
2019-03-26 14:37:30 +00:00
|
|
|
func (e *Etcd) Reverse(ctx context.Context, state request.Request, exact bool, opt plugin.Options) (services []msg.Service, err error) {
|
|
|
|
|
return e.Services(ctx, state, exact, opt)
|
2016-11-05 15:43:27 +00:00
|
|
|
}
|
|
|
|
|
|
2016-10-30 15:54:16 +00:00
|
|
|
// Lookup implements the ServiceBackend interface.
|
2019-03-26 14:37:30 +00:00
|
|
|
func (e *Etcd) Lookup(ctx context.Context, state request.Request, name string, typ uint16) (*dns.Msg, error) {
|
|
|
|
|
return e.Upstream.Lookup(ctx, state, name, typ)
|
2016-10-30 15:54:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IsNameError implements the ServiceBackend interface.
|
|
|
|
|
func (e *Etcd) IsNameError(err error) bool {
|
2018-06-30 20:49:13 +05:30
|
|
|
return err == errKeyNotFound
|
2016-10-30 15:54:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Records looks up records in etcd. If exact is true, it will lookup just this
|
|
|
|
|
// name. This is used when find matches when completing SRV lookups for instance.
|
2019-03-26 14:37:30 +00:00
|
|
|
func (e *Etcd) Records(ctx context.Context, state request.Request, exact bool) ([]msg.Service, error) {
|
2017-08-19 14:03:03 +01:00
|
|
|
name := state.Name()
|
|
|
|
|
|
2016-08-08 19:18:55 -07:00
|
|
|
path, star := msg.PathWithWildcard(name, e.PathPrefix)
|
2019-03-26 14:37:30 +00:00
|
|
|
r, err := e.get(ctx, path, !exact)
|
2016-03-20 21:36:55 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2016-08-08 19:18:55 -07:00
|
|
|
segments := strings.Split(msg.Path(name, e.PathPrefix), "/")
|
2019-01-28 17:38:27 +01:00
|
|
|
return e.loopNodes(r.Kvs, segments, star, state.QType())
|
2016-03-20 21:36:55 +00:00
|
|
|
}
|
|
|
|
|
|
2019-03-26 14:37:30 +00:00
|
|
|
func (e *Etcd) get(ctx context.Context, path string, recursive bool) (*etcdcv3.GetResponse, error) {
|
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, etcdTimeout)
|
2017-11-26 17:21:24 +00:00
|
|
|
defer cancel()
|
2019-09-25 20:23:43 +08:00
|
|
|
if recursive {
|
2018-06-30 20:49:13 +05:30
|
|
|
if !strings.HasSuffix(path, "/") {
|
|
|
|
|
path = path + "/"
|
|
|
|
|
}
|
|
|
|
|
r, err := e.Client.Get(ctx, path, etcdcv3.WithPrefix())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if r.Count == 0 {
|
|
|
|
|
path = strings.TrimSuffix(path, "/")
|
|
|
|
|
r, err = e.Client.Get(ctx, path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if r.Count == 0 {
|
|
|
|
|
return nil, errKeyNotFound
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return r, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r, err := e.Client.Get(ctx, path)
|
2016-03-20 21:36:55 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2018-06-30 20:49:13 +05:30
|
|
|
if r.Count == 0 {
|
|
|
|
|
return nil, errKeyNotFound
|
|
|
|
|
}
|
2017-11-26 17:21:24 +00:00
|
|
|
return r, nil
|
2016-03-20 21:36:55 +00:00
|
|
|
}
|
|
|
|
|
|
2019-01-28 17:38:27 +01:00
|
|
|
func (e *Etcd) loopNodes(kv []*mvccpb.KeyValue, nameParts []string, star bool, qType uint16) (sx []msg.Service, err error) {
|
2018-12-08 06:15:11 -08:00
|
|
|
bx := make(map[msg.Service]struct{})
|
2016-03-20 21:36:55 +00:00
|
|
|
Nodes:
|
2018-06-30 20:49:13 +05:30
|
|
|
for _, n := range kv {
|
2016-03-20 21:36:55 +00:00
|
|
|
if star {
|
2018-06-30 20:49:13 +05:30
|
|
|
s := string(n.Key)
|
|
|
|
|
keyParts := strings.Split(s, "/")
|
2016-03-20 21:36:55 +00:00
|
|
|
for i, n := range nameParts {
|
|
|
|
|
if i > len(keyParts)-1 {
|
|
|
|
|
// name is longer than key
|
|
|
|
|
continue Nodes
|
|
|
|
|
}
|
|
|
|
|
if n == "*" || n == "any" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if keyParts[i] != n {
|
|
|
|
|
continue Nodes
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
serv := new(msg.Service)
|
2018-06-30 20:49:13 +05:30
|
|
|
if err := json.Unmarshal(n.Value, serv); err != nil {
|
2016-06-07 20:57:45 +01:00
|
|
|
return nil, fmt.Errorf("%s: %s", n.Key, err.Error())
|
2016-03-20 21:36:55 +00:00
|
|
|
}
|
2019-01-27 18:54:28 +01:00
|
|
|
serv.Key = string(n.Key)
|
|
|
|
|
if _, ok := bx[*serv]; ok {
|
2016-03-20 21:36:55 +00:00
|
|
|
continue
|
|
|
|
|
}
|
2019-01-27 18:54:28 +01:00
|
|
|
bx[*serv] = struct{}{}
|
2016-03-20 21:36:55 +00:00
|
|
|
|
2016-09-23 09:14:12 +01:00
|
|
|
serv.TTL = e.TTL(n, serv)
|
2016-03-20 21:36:55 +00:00
|
|
|
if serv.Priority == 0 {
|
|
|
|
|
serv.Priority = priority
|
|
|
|
|
}
|
2019-01-28 17:38:27 +01:00
|
|
|
|
|
|
|
|
if shouldInclude(serv, qType) {
|
|
|
|
|
sx = append(sx, *serv)
|
|
|
|
|
}
|
2016-03-20 21:36:55 +00:00
|
|
|
}
|
|
|
|
|
return sx, nil
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-11 08:00:47 +10:00
|
|
|
// TTL returns the smaller of the etcd TTL and the service's
|
2016-03-20 21:36:55 +00:00
|
|
|
// TTL. If neither of these are set (have a zero value), a default is used.
|
2018-06-30 20:49:13 +05:30
|
|
|
func (e *Etcd) TTL(kv *mvccpb.KeyValue, serv *msg.Service) uint32 {
|
|
|
|
|
etcdTTL := uint32(kv.Lease)
|
2016-03-20 21:36:55 +00:00
|
|
|
|
2016-09-23 09:14:12 +01:00
|
|
|
if etcdTTL == 0 && serv.TTL == 0 {
|
2016-03-20 21:36:55 +00:00
|
|
|
return ttl
|
|
|
|
|
}
|
2016-06-11 08:00:47 +10:00
|
|
|
if etcdTTL == 0 {
|
2016-09-23 09:14:12 +01:00
|
|
|
return serv.TTL
|
2016-03-20 21:36:55 +00:00
|
|
|
}
|
2016-09-23 09:14:12 +01:00
|
|
|
if serv.TTL == 0 {
|
2016-06-11 08:00:47 +10:00
|
|
|
return etcdTTL
|
2016-03-20 21:36:55 +00:00
|
|
|
}
|
2016-09-23 09:14:12 +01:00
|
|
|
if etcdTTL < serv.TTL {
|
2016-06-11 08:00:47 +10:00
|
|
|
return etcdTTL
|
2016-03-20 21:36:55 +00:00
|
|
|
}
|
2016-09-23 09:14:12 +01:00
|
|
|
return serv.TTL
|
2016-03-20 18:17:07 +00:00
|
|
|
}
|
2019-01-28 17:38:27 +01:00
|
|
|
|
|
|
|
|
// shouldInclude returns true if the service should be included in a list of records, given the qType. For all the
|
2020-01-04 00:16:04 +11:00
|
|
|
// currently supported lookup types, the only one to allow for an empty Host field in the service are TXT records
|
2020-01-17 16:47:45 +01:00
|
|
|
// which resolve directly. If a TXT record is being resolved by CNAME, then we expect the Host field to have a
|
2020-01-04 00:16:04 +11:00
|
|
|
// value while the TXT field will be empty.
|
2019-01-28 17:38:27 +01:00
|
|
|
func shouldInclude(serv *msg.Service, qType uint16) bool {
|
2020-01-04 00:16:04 +11:00
|
|
|
return (qType == dns.TypeTXT && serv.Text != "") || serv.Host != ""
|
2019-02-17 03:32:28 -05:00
|
|
|
}
|
2024-08-27 21:35:19 +08:00
|
|
|
|
|
|
|
|
// OnShutdown shuts down etcd client when caddy instance restart
|
|
|
|
|
func (e *Etcd) OnShutdown() error {
|
|
|
|
|
if e.Client != nil {
|
|
|
|
|
e.Client.Close()
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|