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
84type CompoundServiceName struct {
85	Name string
86
87	// Namespacing is a Consul Enterprise feature.
88	Namespace string `json:",omitempty"`
89}
90
91// GatewayService associates a gateway with a linked service.
92// It also contains service-specific gateway configuration like ingress listener port and protocol.
93type GatewayService struct {
94	Gateway      CompoundServiceName
95	Service      CompoundServiceName
96	GatewayKind  ServiceKind
97	Port         int      `json:",omitempty"`
98	Protocol     string   `json:",omitempty"`
99	Hosts        []string `json:",omitempty"`
100	CAFile       string   `json:",omitempty"`
101	CertFile     string   `json:",omitempty"`
102	KeyFile      string   `json:",omitempty"`
103	SNI          string   `json:",omitempty"`
104	FromWildcard bool     `json:",omitempty"`
105}
106
107// Catalog can be used to query the Catalog endpoints
108type Catalog struct {
109	c *Client
110}
111
112// Catalog returns a handle to the catalog endpoints
113func (c *Client) Catalog() *Catalog {
114	return &Catalog{c}
115}
116
117func (c *Catalog) Register(reg *CatalogRegistration, q *WriteOptions) (*WriteMeta, error) {
118	r := c.c.newRequest("PUT", "/v1/catalog/register")
119	r.setWriteOptions(q)
120	r.obj = reg
121	rtt, resp, err := requireOK(c.c.doRequest(r))
122	if err != nil {
123		return nil, err
124	}
125	closeResponseBody(resp)
126
127	wm := &WriteMeta{}
128	wm.RequestTime = rtt
129
130	return wm, nil
131}
132
133func (c *Catalog) Deregister(dereg *CatalogDeregistration, q *WriteOptions) (*WriteMeta, error) {
134	r := c.c.newRequest("PUT", "/v1/catalog/deregister")
135	r.setWriteOptions(q)
136	r.obj = dereg
137	rtt, resp, err := requireOK(c.c.doRequest(r))
138	if err != nil {
139		return nil, err
140	}
141	closeResponseBody(resp)
142
143	wm := &WriteMeta{}
144	wm.RequestTime = rtt
145
146	return wm, nil
147}
148
149// Datacenters is used to query for all the known datacenters
150func (c *Catalog) Datacenters() ([]string, error) {
151	r := c.c.newRequest("GET", "/v1/catalog/datacenters")
152	_, resp, err := requireOK(c.c.doRequest(r))
153	if err != nil {
154		return nil, err
155	}
156	defer closeResponseBody(resp)
157
158	var out []string
159	if err := decodeBody(resp, &out); err != nil {
160		return nil, err
161	}
162	return out, nil
163}
164
165// Nodes is used to query all the known nodes
166func (c *Catalog) Nodes(q *QueryOptions) ([]*Node, *QueryMeta, error) {
167	r := c.c.newRequest("GET", "/v1/catalog/nodes")
168	r.setQueryOptions(q)
169	rtt, resp, err := requireOK(c.c.doRequest(r))
170	if err != nil {
171		return nil, nil, err
172	}
173	defer closeResponseBody(resp)
174
175	qm := &QueryMeta{}
176	parseQueryMeta(resp, qm)
177	qm.RequestTime = rtt
178
179	var out []*Node
180	if err := decodeBody(resp, &out); err != nil {
181		return nil, nil, err
182	}
183	return out, qm, nil
184}
185
186// Services is used to query for all known services
187func (c *Catalog) Services(q *QueryOptions) (map[string][]string, *QueryMeta, error) {
188	r := c.c.newRequest("GET", "/v1/catalog/services")
189	r.setQueryOptions(q)
190	rtt, resp, err := requireOK(c.c.doRequest(r))
191	if err != nil {
192		return nil, nil, err
193	}
194	defer closeResponseBody(resp)
195
196	qm := &QueryMeta{}
197	parseQueryMeta(resp, qm)
198	qm.RequestTime = rtt
199
200	var out map[string][]string
201	if err := decodeBody(resp, &out); err != nil {
202		return nil, nil, err
203	}
204	return out, qm, nil
205}
206
207// Service is used to query catalog entries for a given service
208func (c *Catalog) Service(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
209	var tags []string
210	if tag != "" {
211		tags = []string{tag}
212	}
213	return c.service(service, tags, q, false)
214}
215
216// Supports multiple tags for filtering
217func (c *Catalog) ServiceMultipleTags(service string, tags []string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
218	return c.service(service, tags, q, false)
219}
220
221// Connect is used to query catalog entries for a given Connect-enabled service
222func (c *Catalog) Connect(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
223	var tags []string
224	if tag != "" {
225		tags = []string{tag}
226	}
227	return c.service(service, tags, q, true)
228}
229
230// Supports multiple tags for filtering
231func (c *Catalog) ConnectMultipleTags(service string, tags []string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
232	return c.service(service, tags, q, true)
233}
234
235func (c *Catalog) service(service string, tags []string, q *QueryOptions, connect bool) ([]*CatalogService, *QueryMeta, error) {
236	path := "/v1/catalog/service/" + service
237	if connect {
238		path = "/v1/catalog/connect/" + service
239	}
240	r := c.c.newRequest("GET", path)
241	r.setQueryOptions(q)
242	if len(tags) > 0 {
243		for _, tag := range tags {
244			r.params.Add("tag", tag)
245		}
246	}
247	rtt, resp, err := requireOK(c.c.doRequest(r))
248	if err != nil {
249		return nil, nil, err
250	}
251	defer closeResponseBody(resp)
252
253	qm := &QueryMeta{}
254	parseQueryMeta(resp, qm)
255	qm.RequestTime = rtt
256
257	var out []*CatalogService
258	if err := decodeBody(resp, &out); err != nil {
259		return nil, nil, err
260	}
261	return out, qm, nil
262}
263
264// Node is used to query for service information about a single node
265func (c *Catalog) Node(node string, q *QueryOptions) (*CatalogNode, *QueryMeta, error) {
266	r := c.c.newRequest("GET", "/v1/catalog/node/"+node)
267	r.setQueryOptions(q)
268	rtt, resp, err := requireOK(c.c.doRequest(r))
269	if err != nil {
270		return nil, nil, err
271	}
272	defer closeResponseBody(resp)
273
274	qm := &QueryMeta{}
275	parseQueryMeta(resp, qm)
276	qm.RequestTime = rtt
277
278	var out *CatalogNode
279	if err := decodeBody(resp, &out); err != nil {
280		return nil, nil, err
281	}
282	return out, qm, nil
283}
284
285// NodeServiceList is used to query for service information about a single node. It differs from
286// the Node function only in its return type which will contain a list of services as opposed to
287// a map of service ids to services. This different structure allows for using the wildcard specifier
288// '*' for the Namespace in the QueryOptions.
289func (c *Catalog) NodeServiceList(node string, q *QueryOptions) (*CatalogNodeServiceList, *QueryMeta, error) {
290	r := c.c.newRequest("GET", "/v1/catalog/node-services/"+node)
291	r.setQueryOptions(q)
292	rtt, resp, err := requireOK(c.c.doRequest(r))
293	if err != nil {
294		return nil, nil, err
295	}
296	defer closeResponseBody(resp)
297
298	qm := &QueryMeta{}
299	parseQueryMeta(resp, qm)
300	qm.RequestTime = rtt
301
302	var out *CatalogNodeServiceList
303	if err := decodeBody(resp, &out); err != nil {
304		return nil, nil, err
305	}
306	return out, qm, nil
307}
308
309// GatewayServices is used to query the services associated with an ingress gateway or terminating gateway.
310func (c *Catalog) GatewayServices(gateway string, q *QueryOptions) ([]*GatewayService, *QueryMeta, error) {
311	r := c.c.newRequest("GET", "/v1/catalog/gateway-services/"+gateway)
312	r.setQueryOptions(q)
313	rtt, resp, err := requireOK(c.c.doRequest(r))
314	if err != nil {
315		return nil, nil, err
316	}
317	defer closeResponseBody(resp)
318
319	qm := &QueryMeta{}
320	parseQueryMeta(resp, qm)
321	qm.RequestTime = rtt
322
323	var out []*GatewayService
324	if err := decodeBody(resp, &out); err != nil {
325		return nil, nil, err
326	}
327	return out, qm, nil
328}
329
330func ParseServiceAddr(addrPort string) (ServiceAddress, error) {
331	port := 0
332	host, portStr, err := net.SplitHostPort(addrPort)
333	if err == nil {
334		port, err = strconv.Atoi(portStr)
335	}
336	return ServiceAddress{Address: host, Port: port}, err
337}
338