1package api
2
3import (
4	"fmt"
5)
6
7// AgentCheck represents a check known to the agent
8type AgentCheck struct {
9	Node        string
10	CheckID     string
11	Name        string
12	Status      string
13	Notes       string
14	Output      string
15	ServiceID   string
16	ServiceName string
17}
18
19// AgentService represents a service known to the agent
20type AgentService struct {
21	ID      string
22	Service string
23	Tags    []string
24	Port    int
25	Address string
26}
27
28// AgentMember represents a cluster member known to the agent
29type AgentMember struct {
30	Name        string
31	Addr        string
32	Port        uint16
33	Tags        map[string]string
34	Status      int
35	ProtocolMin uint8
36	ProtocolMax uint8
37	ProtocolCur uint8
38	DelegateMin uint8
39	DelegateMax uint8
40	DelegateCur uint8
41}
42
43// AgentServiceRegistration is used to register a new service
44type AgentServiceRegistration struct {
45	ID      string   `json:",omitempty"`
46	Name    string   `json:",omitempty"`
47	Tags    []string `json:",omitempty"`
48	Port    int      `json:",omitempty"`
49	Address string   `json:",omitempty"`
50	Check   *AgentServiceCheck
51	Checks  AgentServiceChecks
52}
53
54// AgentCheckRegistration is used to register a new check
55type AgentCheckRegistration struct {
56	ID        string `json:",omitempty"`
57	Name      string `json:",omitempty"`
58	Notes     string `json:",omitempty"`
59	ServiceID string `json:",omitempty"`
60	AgentServiceCheck
61}
62
63// AgentServiceCheck is used to create an associated
64// check for a service
65type AgentServiceCheck struct {
66	Script   string `json:",omitempty"`
67	Interval string `json:",omitempty"`
68	Timeout  string `json:",omitempty"`
69	TTL      string `json:",omitempty"`
70	HTTP     string `json:",omitempty"`
71	Status   string `json:",omitempty"`
72}
73type AgentServiceChecks []*AgentServiceCheck
74
75// Agent can be used to query the Agent endpoints
76type Agent struct {
77	c *Client
78
79	// cache the node name
80	nodeName string
81}
82
83// Agent returns a handle to the agent endpoints
84func (c *Client) Agent() *Agent {
85	return &Agent{c: c}
86}
87
88// Self is used to query the agent we are speaking to for
89// information about itself
90func (a *Agent) Self() (map[string]map[string]interface{}, error) {
91	r := a.c.newRequest("GET", "/v1/agent/self")
92	_, resp, err := requireOK(a.c.doRequest(r))
93	if err != nil {
94		return nil, err
95	}
96	defer resp.Body.Close()
97
98	var out map[string]map[string]interface{}
99	if err := decodeBody(resp, &out); err != nil {
100		return nil, err
101	}
102	return out, nil
103}
104
105// NodeName is used to get the node name of the agent
106func (a *Agent) NodeName() (string, error) {
107	if a.nodeName != "" {
108		return a.nodeName, nil
109	}
110	info, err := a.Self()
111	if err != nil {
112		return "", err
113	}
114	name := info["Config"]["NodeName"].(string)
115	a.nodeName = name
116	return name, nil
117}
118
119// Checks returns the locally registered checks
120func (a *Agent) Checks() (map[string]*AgentCheck, error) {
121	r := a.c.newRequest("GET", "/v1/agent/checks")
122	_, resp, err := requireOK(a.c.doRequest(r))
123	if err != nil {
124		return nil, err
125	}
126	defer resp.Body.Close()
127
128	var out map[string]*AgentCheck
129	if err := decodeBody(resp, &out); err != nil {
130		return nil, err
131	}
132	return out, nil
133}
134
135// Services returns the locally registered services
136func (a *Agent) Services() (map[string]*AgentService, error) {
137	r := a.c.newRequest("GET", "/v1/agent/services")
138	_, resp, err := requireOK(a.c.doRequest(r))
139	if err != nil {
140		return nil, err
141	}
142	defer resp.Body.Close()
143
144	var out map[string]*AgentService
145	if err := decodeBody(resp, &out); err != nil {
146		return nil, err
147	}
148	return out, nil
149}
150
151// Members returns the known gossip members. The WAN
152// flag can be used to query a server for WAN members.
153func (a *Agent) Members(wan bool) ([]*AgentMember, error) {
154	r := a.c.newRequest("GET", "/v1/agent/members")
155	if wan {
156		r.params.Set("wan", "1")
157	}
158	_, resp, err := requireOK(a.c.doRequest(r))
159	if err != nil {
160		return nil, err
161	}
162	defer resp.Body.Close()
163
164	var out []*AgentMember
165	if err := decodeBody(resp, &out); err != nil {
166		return nil, err
167	}
168	return out, nil
169}
170
171// ServiceRegister is used to register a new service with
172// the local agent
173func (a *Agent) ServiceRegister(service *AgentServiceRegistration) error {
174	r := a.c.newRequest("PUT", "/v1/agent/service/register")
175	r.obj = service
176	_, resp, err := requireOK(a.c.doRequest(r))
177	if err != nil {
178		return err
179	}
180	resp.Body.Close()
181	return nil
182}
183
184// ServiceDeregister is used to deregister a service with
185// the local agent
186func (a *Agent) ServiceDeregister(serviceID string) error {
187	r := a.c.newRequest("PUT", "/v1/agent/service/deregister/"+serviceID)
188	_, resp, err := requireOK(a.c.doRequest(r))
189	if err != nil {
190		return err
191	}
192	resp.Body.Close()
193	return nil
194}
195
196// PassTTL is used to set a TTL check to the passing state
197func (a *Agent) PassTTL(checkID, note string) error {
198	return a.UpdateTTL(checkID, note, "pass")
199}
200
201// WarnTTL is used to set a TTL check to the warning state
202func (a *Agent) WarnTTL(checkID, note string) error {
203	return a.UpdateTTL(checkID, note, "warn")
204}
205
206// FailTTL is used to set a TTL check to the failing state
207func (a *Agent) FailTTL(checkID, note string) error {
208	return a.UpdateTTL(checkID, note, "fail")
209}
210
211// UpdateTTL is used to update the TTL of a check
212func (a *Agent) UpdateTTL(checkID, note, status string) error {
213	switch status {
214	case "pass":
215	case "warn":
216	case "fail":
217	default:
218		return fmt.Errorf("Invalid status: %s", status)
219	}
220	endpoint := fmt.Sprintf("/v1/agent/check/%s/%s", status, checkID)
221	r := a.c.newRequest("PUT", endpoint)
222	r.params.Set("note", note)
223	_, resp, err := requireOK(a.c.doRequest(r))
224	if err != nil {
225		return err
226	}
227	resp.Body.Close()
228	return nil
229}
230
231// CheckRegister is used to register a new check with
232// the local agent
233func (a *Agent) CheckRegister(check *AgentCheckRegistration) error {
234	r := a.c.newRequest("PUT", "/v1/agent/check/register")
235	r.obj = check
236	_, resp, err := requireOK(a.c.doRequest(r))
237	if err != nil {
238		return err
239	}
240	resp.Body.Close()
241	return nil
242}
243
244// CheckDeregister is used to deregister a check with
245// the local agent
246func (a *Agent) CheckDeregister(checkID string) error {
247	r := a.c.newRequest("PUT", "/v1/agent/check/deregister/"+checkID)
248	_, resp, err := requireOK(a.c.doRequest(r))
249	if err != nil {
250		return err
251	}
252	resp.Body.Close()
253	return nil
254}
255
256// Join is used to instruct the agent to attempt a join to
257// another cluster member
258func (a *Agent) Join(addr string, wan bool) error {
259	r := a.c.newRequest("PUT", "/v1/agent/join/"+addr)
260	if wan {
261		r.params.Set("wan", "1")
262	}
263	_, resp, err := requireOK(a.c.doRequest(r))
264	if err != nil {
265		return err
266	}
267	resp.Body.Close()
268	return nil
269}
270
271// ForceLeave is used to have the agent eject a failed node
272func (a *Agent) ForceLeave(node string) error {
273	r := a.c.newRequest("PUT", "/v1/agent/force-leave/"+node)
274	_, resp, err := requireOK(a.c.doRequest(r))
275	if err != nil {
276		return err
277	}
278	resp.Body.Close()
279	return nil
280}
281
282// EnableServiceMaintenance toggles service maintenance mode on
283// for the given service ID.
284func (a *Agent) EnableServiceMaintenance(serviceID, reason string) error {
285	r := a.c.newRequest("PUT", "/v1/agent/service/maintenance/"+serviceID)
286	r.params.Set("enable", "true")
287	r.params.Set("reason", reason)
288	_, resp, err := requireOK(a.c.doRequest(r))
289	if err != nil {
290		return err
291	}
292	resp.Body.Close()
293	return nil
294}
295
296// DisableServiceMaintenance toggles service maintenance mode off
297// for the given service ID.
298func (a *Agent) DisableServiceMaintenance(serviceID string) error {
299	r := a.c.newRequest("PUT", "/v1/agent/service/maintenance/"+serviceID)
300	r.params.Set("enable", "false")
301	_, resp, err := requireOK(a.c.doRequest(r))
302	if err != nil {
303		return err
304	}
305	resp.Body.Close()
306	return nil
307}
308
309// EnableNodeMaintenance toggles node maintenance mode on for the
310// agent we are connected to.
311func (a *Agent) EnableNodeMaintenance(reason string) error {
312	r := a.c.newRequest("PUT", "/v1/agent/maintenance")
313	r.params.Set("enable", "true")
314	r.params.Set("reason", reason)
315	_, resp, err := requireOK(a.c.doRequest(r))
316	if err != nil {
317		return err
318	}
319	resp.Body.Close()
320	return nil
321}
322
323// DisableNodeMaintenance toggles node maintenance mode off for the
324// agent we are connected to.
325func (a *Agent) DisableNodeMaintenance() error {
326	r := a.c.newRequest("PUT", "/v1/agent/maintenance")
327	r.params.Set("enable", "false")
328	_, resp, err := requireOK(a.c.doRequest(r))
329	if err != nil {
330		return err
331	}
332	resp.Body.Close()
333	return nil
334}
335