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 = (*CatalogServicesQuery)(nil) 17 18 // CatalogServicesQueryRe is the regular expression to use for CatalogNodesQuery. 19 CatalogServicesQueryRe = regexp.MustCompile(`\A` + dcRe + `\z`) 20) 21 22func init() { 23 gob.Register([]*CatalogSnippet{}) 24} 25 26// CatalogSnippet is a catalog entry in Consul. 27type CatalogSnippet struct { 28 Name string 29 Tags ServiceTags 30} 31 32// CatalogServicesQuery is the representation of a requested catalog service 33// dependency from inside a template. 34type CatalogServicesQuery struct { 35 stopCh chan struct{} 36 37 dc string 38} 39 40// NewCatalogServicesQuery parses a string of the format @dc. 41func NewCatalogServicesQuery(s string) (*CatalogServicesQuery, error) { 42 if !CatalogServicesQueryRe.MatchString(s) { 43 return nil, fmt.Errorf("catalog.services: invalid format: %q", s) 44 } 45 46 m := regexpMatch(CatalogServicesQueryRe, s) 47 return &CatalogServicesQuery{ 48 stopCh: make(chan struct{}, 1), 49 dc: m["dc"], 50 }, nil 51} 52 53// Fetch queries the Consul API defined by the given client and returns a slice 54// of CatalogService objects. 55func (d *CatalogServicesQuery) Fetch(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) { 56 select { 57 case <-d.stopCh: 58 return nil, nil, ErrStopped 59 default: 60 } 61 62 opts = opts.Merge(&QueryOptions{ 63 Datacenter: d.dc, 64 }) 65 66 log.Printf("[TRACE] %s: GET %s", d, &url.URL{ 67 Path: "/v1/catalog/services", 68 RawQuery: opts.String(), 69 }) 70 71 entries, qm, err := clients.Consul().Catalog().Services(opts.ToConsulOpts()) 72 if err != nil { 73 return nil, nil, errors.Wrap(err, d.String()) 74 } 75 76 log.Printf("[TRACE] %s: returned %d results", d, len(entries)) 77 78 var catalogServices []*CatalogSnippet 79 for name, tags := range entries { 80 catalogServices = append(catalogServices, &CatalogSnippet{ 81 Name: name, 82 Tags: ServiceTags(deepCopyAndSortTags(tags)), 83 }) 84 } 85 86 sort.Stable(ByName(catalogServices)) 87 88 rm := &ResponseMetadata{ 89 LastIndex: qm.LastIndex, 90 LastContact: qm.LastContact, 91 } 92 93 return catalogServices, rm, nil 94} 95 96// CanShare returns a boolean if this dependency is shareable. 97func (d *CatalogServicesQuery) CanShare() bool { 98 return true 99} 100 101// String returns the human-friendly version of this dependency. 102func (d *CatalogServicesQuery) String() string { 103 if d.dc != "" { 104 return fmt.Sprintf("catalog.services(@%s)", d.dc) 105 } 106 return "catalog.services" 107} 108 109// Stop halts the dependency's fetch function. 110func (d *CatalogServicesQuery) Stop() { 111 close(d.stopCh) 112} 113 114// Type returns the type of this dependency. 115func (d *CatalogServicesQuery) Type() Type { 116 return TypeConsul 117} 118 119// ByName is a sortable slice of CatalogService structs. 120type ByName []*CatalogSnippet 121 122func (s ByName) Len() int { return len(s) } 123func (s ByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 124func (s ByName) Less(i, j int) bool { 125 if s[i].Name <= s[j].Name { 126 return true 127 } 128 return false 129} 130