| 
									
										
										
										
											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" | 
					
						
							| 
									
										
										
										
											2017-02-21 22:51:47 -08:00
										 |  |  | 	"github.com/coredns/coredns/request" | 
					
						
							| 
									
										
										
										
											2016-03-20 17:44:58 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-14 15:11:26 -05:00
										 |  |  | 	"github.com/coredns/coredns/plugin/pkg/upstream" | 
					
						
							| 
									
										
										
										
											2016-10-30 15:54:16 +00:00
										 |  |  | 	"github.com/miekg/dns" | 
					
						
							| 
									
										
										
										
											2019-08-27 16:58:35 +01:00
										 |  |  | 	etcdcv3 "go.etcd.io/etcd/clientv3" | 
					
						
							|  |  |  | 	"go.etcd.io/etcd/mvcc/mvccpb" | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							|  |  |  | // currently supported lookup types, the only one to allow for an empty Host field in the service are TXT records. | 
					
						
							|  |  |  | // Similarly, the TXT record in turn requires the Text field to be set. | 
					
						
							|  |  |  | func shouldInclude(serv *msg.Service, qType uint16) bool { | 
					
						
							|  |  |  | 	if qType == dns.TypeTXT { | 
					
						
							|  |  |  | 		return serv.Text != "" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return serv.Host != "" | 
					
						
							| 
									
										
										
										
											2019-02-17 03:32:28 -05:00
										 |  |  | } |