1package dependency 2 3import ( 4 "encoding/gob" 5 "fmt" 6 "log" 7 "net/url" 8 "regexp" 9 10 "github.com/pkg/errors" 11) 12 13var ( 14 // Ensure implements 15 _ Dependency = (*CatalogServiceQuery)(nil) 16 17 // CatalogServiceQueryRe is the regular expression to use. 18 CatalogServiceQueryRe = regexp.MustCompile(`\A` + tagRe + serviceNameRe + dcRe + nearRe + `\z`) 19) 20 21func init() { 22 gob.Register([]*CatalogSnippet{}) 23} 24 25// CatalogService is a catalog entry in Consul. 26type CatalogService struct { 27 ID string 28 Node string 29 Address string 30 Datacenter string 31 TaggedAddresses map[string]string 32 NodeMeta map[string]string 33 ServiceID string 34 ServiceName string 35 ServiceAddress string 36 ServiceTags ServiceTags 37 ServiceMeta map[string]string 38 ServicePort int 39} 40 41// CatalogServiceQuery is the representation of a requested catalog services 42// dependency from inside a template. 43type CatalogServiceQuery struct { 44 stopCh chan struct{} 45 46 dc string 47 name string 48 near string 49 tag string 50} 51 52// NewCatalogServiceQuery parses a string into a CatalogServiceQuery. 53func NewCatalogServiceQuery(s string) (*CatalogServiceQuery, error) { 54 if !CatalogServiceQueryRe.MatchString(s) { 55 return nil, fmt.Errorf("catalog.service: invalid format: %q", s) 56 } 57 58 m := regexpMatch(CatalogServiceQueryRe, s) 59 return &CatalogServiceQuery{ 60 stopCh: make(chan struct{}, 1), 61 dc: m["dc"], 62 name: m["name"], 63 near: m["near"], 64 tag: m["tag"], 65 }, nil 66} 67 68// Fetch queries the Consul API defined by the given client and returns a slice 69// of CatalogService objects. 70func (d *CatalogServiceQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) { 71 select { 72 case <-d.stopCh: 73 return nil, nil, ErrStopped 74 default: 75 } 76 77 opts = opts.Merge(&QueryOptions{ 78 Datacenter: d.dc, 79 Near: d.near, 80 }) 81 82 u := &url.URL{ 83 Path: "/v1/catalog/service/" + d.name, 84 RawQuery: opts.String(), 85 } 86 if d.tag != "" { 87 q := u.Query() 88 q.Set("tag", d.tag) 89 u.RawQuery = q.Encode() 90 } 91 log.Printf("[TRACE] %s: GET %s", d, u) 92 93 entries, qm, err := clients.Consul().Catalog().Service(d.name, d.tag, opts.ToConsulOpts()) 94 if err != nil { 95 return nil, nil, errors.Wrap(err, d.String()) 96 } 97 98 log.Printf("[TRACE] %s: returned %d results", d, len(entries)) 99 100 var list []*CatalogService 101 for _, s := range entries { 102 list = append(list, &CatalogService{ 103 ID: s.ID, 104 Node: s.Node, 105 Address: s.Address, 106 Datacenter: s.Datacenter, 107 TaggedAddresses: s.TaggedAddresses, 108 NodeMeta: s.NodeMeta, 109 ServiceID: s.ServiceID, 110 ServiceName: s.ServiceName, 111 ServiceAddress: s.ServiceAddress, 112 ServiceTags: ServiceTags(deepCopyAndSortTags(s.ServiceTags)), 113 ServiceMeta: s.ServiceMeta, 114 ServicePort: s.ServicePort, 115 }) 116 } 117 118 rm := &ResponseMetadata{ 119 LastIndex: qm.LastIndex, 120 LastContact: qm.LastContact, 121 } 122 123 return list, rm, nil 124} 125 126// CanShare returns a boolean if this dependency is shareable. 127func (d *CatalogServiceQuery) CanShare() bool { 128 return true 129} 130 131// String returns the human-friendly version of this dependency. 132func (d *CatalogServiceQuery) String() string { 133 name := d.name 134 if d.tag != "" { 135 name = d.tag + "." + name 136 } 137 if d.dc != "" { 138 name = name + "@" + d.dc 139 } 140 if d.near != "" { 141 name = name + "~" + d.near 142 } 143 return fmt.Sprintf("catalog.service(%s)", name) 144} 145 146// Stop halts the dependency's fetch function. 147func (d *CatalogServiceQuery) Stop() { 148 close(d.stopCh) 149} 150 151// Type returns the type of this dependency. 152func (d *CatalogServiceQuery) Type() Type { 153 return TypeConsul 154} 155