1package dependency 2 3import ( 4 "encoding/gob" 5 "fmt" 6 "log" 7 "net/url" 8 "regexp" 9 "sort" 10 11 "github.com/pkg/errors" 12) 13 14var ( 15 // Ensure implements 16 _ Dependency = (*CatalogNodeQuery)(nil) 17 18 // CatalogNodeQueryRe is the regular expression to use. 19 CatalogNodeQueryRe = regexp.MustCompile(`\A` + nodeNameRe + dcRe + `\z`) 20) 21 22func init() { 23 gob.Register([]*CatalogNode{}) 24 gob.Register([]*CatalogNodeService{}) 25} 26 27// CatalogNodeQuery represents a single node from the Consul catalog. 28type CatalogNodeQuery struct { 29 stopCh chan struct{} 30 31 dc string 32 name string 33} 34 35// CatalogNode is a wrapper around the node and its services. 36type CatalogNode struct { 37 Node *Node 38 Services []*CatalogNodeService 39} 40 41// CatalogNodeService is a service on a single node. 42type CatalogNodeService struct { 43 ID string 44 Service string 45 Tags ServiceTags 46 Meta map[string]string 47 Port int 48 Address string 49 EnableTagOverride bool 50} 51 52// NewCatalogNodeQuery parses the given string into a dependency. If the name is 53// empty then the name of the local agent is used. 54func NewCatalogNodeQuery(s string) (*CatalogNodeQuery, error) { 55 if s != "" && !CatalogNodeQueryRe.MatchString(s) { 56 return nil, fmt.Errorf("catalog.node: invalid format: %q", s) 57 } 58 59 m := regexpMatch(CatalogNodeQueryRe, s) 60 return &CatalogNodeQuery{ 61 dc: m["dc"], 62 name: m["name"], 63 stopCh: make(chan struct{}, 1), 64 }, nil 65} 66 67// Fetch queries the Consul API defined by the given client and returns a 68// of CatalogNode object. 69func (d *CatalogNodeQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) { 70 select { 71 case <-d.stopCh: 72 return nil, nil, ErrStopped 73 default: 74 } 75 76 opts = opts.Merge(&QueryOptions{ 77 Datacenter: d.dc, 78 }) 79 80 // Grab the name 81 name := d.name 82 83 if name == "" { 84 log.Printf("[TRACE] %s: getting local agent name", d) 85 var err error 86 name, err = clients.Consul().Agent().NodeName() 87 if err != nil { 88 return nil, nil, errors.Wrapf(err, d.String()) 89 } 90 } 91 92 log.Printf("[TRACE] %s: GET %s", d, &url.URL{ 93 Path: "/v1/catalog/node/" + name, 94 RawQuery: opts.String(), 95 }) 96 node, qm, err := clients.Consul().Catalog().Node(name, opts.ToConsulOpts()) 97 if err != nil { 98 return nil, nil, errors.Wrap(err, d.String()) 99 } 100 101 log.Printf("[TRACE] %s: returned response", d) 102 103 rm := &ResponseMetadata{ 104 LastIndex: qm.LastIndex, 105 LastContact: qm.LastContact, 106 } 107 108 if node == nil { 109 log.Printf("[WARN] %s: no node exists with the name %q", d, name) 110 var node CatalogNode 111 return &node, rm, nil 112 } 113 114 services := make([]*CatalogNodeService, 0, len(node.Services)) 115 for _, v := range node.Services { 116 services = append(services, &CatalogNodeService{ 117 ID: v.ID, 118 Service: v.Service, 119 Tags: ServiceTags(deepCopyAndSortTags(v.Tags)), 120 Meta: v.Meta, 121 Port: v.Port, 122 Address: v.Address, 123 EnableTagOverride: v.EnableTagOverride, 124 }) 125 } 126 sort.Stable(ByService(services)) 127 128 detail := &CatalogNode{ 129 Node: &Node{ 130 ID: node.Node.ID, 131 Node: node.Node.Node, 132 Address: node.Node.Address, 133 Datacenter: node.Node.Datacenter, 134 TaggedAddresses: node.Node.TaggedAddresses, 135 Meta: node.Node.Meta, 136 }, 137 Services: services, 138 } 139 140 return detail, rm, nil 141} 142 143// CanShare returns a boolean if this dependency is shareable. 144func (d *CatalogNodeQuery) CanShare() bool { 145 return false 146} 147 148// String returns the human-friendly version of this dependency. 149func (d *CatalogNodeQuery) String() string { 150 name := d.name 151 if d.dc != "" { 152 name = name + "@" + d.dc 153 } 154 155 if name == "" { 156 return "catalog.node" 157 } 158 return fmt.Sprintf("catalog.node(%s)", name) 159} 160 161// Stop halts the dependency's fetch function. 162func (d *CatalogNodeQuery) Stop() { 163 close(d.stopCh) 164} 165 166// Type returns the type of this dependency. 167func (d *CatalogNodeQuery) Type() Type { 168 return TypeConsul 169} 170 171// ByService is a sorter of node services by their service name and then ID. 172type ByService []*CatalogNodeService 173 174func (s ByService) Len() int { return len(s) } 175func (s ByService) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 176func (s ByService) Less(i, j int) bool { 177 if s[i].Service == s[j].Service { 178 return s[i].ID <= s[j].ID 179 } 180 return s[i].Service <= s[j].Service 181} 182