1package api
2
3import (
4	"net"
5	"strconv"
6)
7
8type Weights struct {
9	Passing int
10	Warning int
11}
12
13type Node struct {
14	ID              string
15	Node            string
16	Address         string
17	Datacenter      string
18	TaggedAddresses map[string]string
19	Meta            map[string]string
20	CreateIndex     uint64
21	ModifyIndex     uint64
22}
23
24type ServiceAddress struct {
25	Address string
26	Port    int
27}
28
29type CatalogService struct {
30	ID                       string
31	Node                     string
32	Address                  string
33	Datacenter               string
34	TaggedAddresses          map[string]string
35	NodeMeta                 map[string]string
36	ServiceID                string
37	ServiceName              string
38	ServiceAddress           string
39	ServiceTaggedAddresses   map[string]ServiceAddress
40	ServiceTags              []string
41	ServiceMeta              map[string]string
42	ServicePort              int
43	ServiceWeights           Weights
44	ServiceEnableTagOverride bool
45	ServiceProxy             *AgentServiceConnectProxyConfig
46	CreateIndex              uint64
47	Checks                   HealthChecks
48	ModifyIndex              uint64
49	Namespace                string `json:",omitempty"`
50}
51
52type CatalogNode struct {
53	Node     *Node
54	Services map[string]*AgentService
55}
56
57type CatalogNodeServiceList struct {
58	Node     *Node
59	Services []*AgentService
60}
61
62type CatalogRegistration struct {
63	ID              string
64	Node            string
65	Address         string
66	TaggedAddresses map[string]string
67	NodeMeta        map[string]string
68	Datacenter      string
69	Service         *AgentService
70	Check           *AgentCheck
71	Checks          HealthChecks
72	SkipNodeUpdate  bool
73}
74
75type CatalogDeregistration struct {
76	Node       string
77	Address    string `json:",omitempty"` // Obsolete.
78	Datacenter string
79	ServiceID  string
80	CheckID    string
81	Namespace  string `json:",omitempty"`
82}
83
84// Catalog can be used to query the Catalog endpoints
85type Catalog struct {
86	c *Client
87}
88
89// Catalog returns a handle to the catalog endpoints
90func (c *Client) Catalog() *Catalog {
91	return &Catalog{c}
92}
93
94func (c *Catalog) Register(reg *CatalogRegistration, q *WriteOptions) (*WriteMeta, error) {
95	r := c.c.newRequest("PUT", "/v1/catalog/register")
96	r.setWriteOptions(q)
97	r.obj = reg
98	rtt, resp, err := requireOK(c.c.doRequest(r))
99	if err != nil {
100		return nil, err
101	}
102	resp.Body.Close()
103
104	wm := &WriteMeta{}
105	wm.RequestTime = rtt
106
107	return wm, nil
108}
109
110func (c *Catalog) Deregister(dereg *CatalogDeregistration, q *WriteOptions) (*WriteMeta, error) {
111	r := c.c.newRequest("PUT", "/v1/catalog/deregister")
112	r.setWriteOptions(q)
113	r.obj = dereg
114	rtt, resp, err := requireOK(c.c.doRequest(r))
115	if err != nil {
116		return nil, err
117	}
118	resp.Body.Close()
119
120	wm := &WriteMeta{}
121	wm.RequestTime = rtt
122
123	return wm, nil
124}
125
126// Datacenters is used to query for all the known datacenters
127func (c *Catalog) Datacenters() ([]string, error) {
128	r := c.c.newRequest("GET", "/v1/catalog/datacenters")
129	_, resp, err := requireOK(c.c.doRequest(r))
130	if err != nil {
131		return nil, err
132	}
133	defer resp.Body.Close()
134
135	var out []string
136	if err := decodeBody(resp, &out); err != nil {
137		return nil, err
138	}
139	return out, nil
140}
141
142// Nodes is used to query all the known nodes
143func (c *Catalog) Nodes(q *QueryOptions) ([]*Node, *QueryMeta, error) {
144	r := c.c.newRequest("GET", "/v1/catalog/nodes")
145	r.setQueryOptions(q)
146	rtt, resp, err := requireOK(c.c.doRequest(r))
147	if err != nil {
148		return nil, nil, err
149	}
150	defer resp.Body.Close()
151
152	qm := &QueryMeta{}
153	parseQueryMeta(resp, qm)
154	qm.RequestTime = rtt
155
156	var out []*Node
157	if err := decodeBody(resp, &out); err != nil {
158		return nil, nil, err
159	}
160	return out, qm, nil
161}
162
163// Services is used to query for all known services
164func (c *Catalog) Services(q *QueryOptions) (map[string][]string, *QueryMeta, error) {
165	r := c.c.newRequest("GET", "/v1/catalog/services")
166	r.setQueryOptions(q)
167	rtt, resp, err := requireOK(c.c.doRequest(r))
168	if err != nil {
169		return nil, nil, err
170	}
171	defer resp.Body.Close()
172
173	qm := &QueryMeta{}
174	parseQueryMeta(resp, qm)
175	qm.RequestTime = rtt
176
177	var out map[string][]string
178	if err := decodeBody(resp, &out); err != nil {
179		return nil, nil, err
180	}
181	return out, qm, nil
182}
183
184// Service is used to query catalog entries for a given service
185func (c *Catalog) Service(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
186	var tags []string
187	if tag != "" {
188		tags = []string{tag}
189	}
190	return c.service(service, tags, q, false)
191}
192
193// Supports multiple tags for filtering
194func (c *Catalog) ServiceMultipleTags(service string, tags []string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
195	return c.service(service, tags, q, false)
196}
197
198// Connect is used to query catalog entries for a given Connect-enabled service
199func (c *Catalog) Connect(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
200	var tags []string
201	if tag != "" {
202		tags = []string{tag}
203	}
204	return c.service(service, tags, q, true)
205}
206
207// Supports multiple tags for filtering
208func (c *Catalog) ConnectMultipleTags(service string, tags []string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
209	return c.service(service, tags, q, true)
210}
211
212func (c *Catalog) service(service string, tags []string, q *QueryOptions, connect bool) ([]*CatalogService, *QueryMeta, error) {
213	path := "/v1/catalog/service/" + service
214	if connect {
215		path = "/v1/catalog/connect/" + service
216	}
217	r := c.c.newRequest("GET", path)
218	r.setQueryOptions(q)
219	if len(tags) > 0 {
220		for _, tag := range tags {
221			r.params.Add("tag", tag)
222		}
223	}
224	rtt, resp, err := requireOK(c.c.doRequest(r))
225	if err != nil {
226		return nil, nil, err
227	}
228	defer resp.Body.Close()
229
230	qm := &QueryMeta{}
231	parseQueryMeta(resp, qm)
232	qm.RequestTime = rtt
233
234	var out []*CatalogService
235	if err := decodeBody(resp, &out); err != nil {
236		return nil, nil, err
237	}
238	return out, qm, nil
239}
240
241// Node is used to query for service information about a single node
242func (c *Catalog) Node(node string, q *QueryOptions) (*CatalogNode, *QueryMeta, error) {
243	r := c.c.newRequest("GET", "/v1/catalog/node/"+node)
244	r.setQueryOptions(q)
245	rtt, resp, err := requireOK(c.c.doRequest(r))
246	if err != nil {
247		return nil, nil, err
248	}
249	defer resp.Body.Close()
250
251	qm := &QueryMeta{}
252	parseQueryMeta(resp, qm)
253	qm.RequestTime = rtt
254
255	var out *CatalogNode
256	if err := decodeBody(resp, &out); err != nil {
257		return nil, nil, err
258	}
259	return out, qm, nil
260}
261
262// NodeServiceList is used to query for service information about a single node. It differs from
263// the Node function only in its return type which will contain a list of services as opposed to
264// a map of service ids to services. This different structure allows for using the wildcard specifier
265// '*' for the Namespace in the QueryOptions.
266func (c *Catalog) NodeServiceList(node string, q *QueryOptions) (*CatalogNodeServiceList, *QueryMeta, error) {
267	r := c.c.newRequest("GET", "/v1/catalog/node-services/"+node)
268	r.setQueryOptions(q)
269	rtt, resp, err := requireOK(c.c.doRequest(r))
270	if err != nil {
271		return nil, nil, err
272	}
273	defer resp.Body.Close()
274
275	qm := &QueryMeta{}
276	parseQueryMeta(resp, qm)
277	qm.RequestTime = rtt
278
279	var out *CatalogNodeServiceList
280	if err := decodeBody(resp, &out); err != nil {
281		return nil, nil, err
282	}
283	return out, qm, nil
284}
285
286func ParseServiceAddr(addrPort string) (ServiceAddress, error) {
287	port := 0
288	host, portStr, err := net.SplitHostPort(addrPort)
289	if err == nil {
290		port, err = strconv.Atoi(portStr)
291	}
292	return ServiceAddress{Address: host, Port: port}, err
293}
294