1package api
2
3import (
4	"bufio"
5	"bytes"
6	"context"
7	"fmt"
8	"io"
9	"net/http"
10	"net/url"
11)
12
13// ServiceKind is the kind of service being registered.
14type ServiceKind string
15
16const (
17	// ServiceKindTypical is a typical, classic Consul service. This is
18	// represented by the absence of a value. This was chosen for ease of
19	// backwards compatibility: existing services in the catalog would
20	// default to the typical service.
21	ServiceKindTypical ServiceKind = ""
22
23	// ServiceKindConnectProxy is a proxy for the Connect feature. This
24	// service proxies another service within Consul and speaks the connect
25	// protocol.
26	ServiceKindConnectProxy ServiceKind = "connect-proxy"
27
28	// ServiceKindMeshGateway is a Mesh Gateway for the Connect feature. This
29	// service will proxy connections based off the SNI header set by other
30	// connect proxies
31	ServiceKindMeshGateway ServiceKind = "mesh-gateway"
32
33	// ServiceKindTerminatingGateway is a Terminating Gateway for the Connect
34	// feature. This service will proxy connections to services outside the mesh.
35	ServiceKindTerminatingGateway ServiceKind = "terminating-gateway"
36
37	// ServiceKindIngressGateway is an Ingress Gateway for the Connect feature.
38	// This service will ingress connections based of configuration defined in
39	// the ingress-gateway config entry.
40	ServiceKindIngressGateway ServiceKind = "ingress-gateway"
41)
42
43// UpstreamDestType is the type of upstream discovery mechanism.
44type UpstreamDestType string
45
46const (
47	// UpstreamDestTypeService discovers instances via healthy service lookup.
48	UpstreamDestTypeService UpstreamDestType = "service"
49
50	// UpstreamDestTypePreparedQuery discovers instances via prepared query
51	// execution.
52	UpstreamDestTypePreparedQuery UpstreamDestType = "prepared_query"
53)
54
55// AgentCheck represents a check known to the agent
56type AgentCheck struct {
57	Node        string
58	CheckID     string
59	Name        string
60	Status      string
61	Notes       string
62	Output      string
63	ServiceID   string
64	ServiceName string
65	Type        string
66	ExposedPort int
67	Definition  HealthCheckDefinition
68	Namespace   string `json:",omitempty"`
69}
70
71// AgentWeights represent optional weights for a service
72type AgentWeights struct {
73	Passing int
74	Warning int
75}
76
77// AgentService represents a service known to the agent
78type AgentService struct {
79	Kind              ServiceKind `json:",omitempty"`
80	ID                string
81	Service           string
82	Tags              []string
83	Meta              map[string]string
84	Port              int
85	Address           string
86	SocketPath        string
87	TaggedAddresses   map[string]ServiceAddress `json:",omitempty"`
88	Weights           AgentWeights
89	EnableTagOverride bool
90	CreateIndex       uint64                          `json:",omitempty" bexpr:"-"`
91	ModifyIndex       uint64                          `json:",omitempty" bexpr:"-"`
92	ContentHash       string                          `json:",omitempty" bexpr:"-"`
93	Proxy             *AgentServiceConnectProxyConfig `json:",omitempty"`
94	Connect           *AgentServiceConnect            `json:",omitempty"`
95	// NOTE: If we ever set the ContentHash outside of singular service lookup then we may need
96	// to include the Namespace in the hash. When we do, then we are in for lots of fun with tests.
97	// For now though, ignoring it works well enough.
98	Namespace string `json:",omitempty" bexpr:"-" hash:"ignore"`
99	// Datacenter is only ever returned and is ignored if presented.
100	Datacenter string `json:",omitempty" bexpr:"-" hash:"ignore"`
101}
102
103// AgentServiceChecksInfo returns information about a Service and its checks
104type AgentServiceChecksInfo struct {
105	AggregatedStatus string
106	Service          *AgentService
107	Checks           HealthChecks
108}
109
110// AgentServiceConnect represents the Connect configuration of a service.
111type AgentServiceConnect struct {
112	Native         bool                      `json:",omitempty"`
113	SidecarService *AgentServiceRegistration `json:",omitempty" bexpr:"-"`
114}
115
116// AgentServiceConnectProxyConfig is the proxy configuration in a connect-proxy
117// ServiceDefinition or response.
118type AgentServiceConnectProxyConfig struct {
119	DestinationServiceName string                  `json:",omitempty"`
120	DestinationServiceID   string                  `json:",omitempty"`
121	LocalServiceAddress    string                  `json:",omitempty"`
122	LocalServicePort       int                     `json:",omitempty"`
123	LocalServiceSocketPath string                  `json:",omitempty"`
124	Mode                   ProxyMode               `json:",omitempty"`
125	TransparentProxy       *TransparentProxyConfig `json:",omitempty"`
126	Config                 map[string]interface{}  `json:",omitempty" bexpr:"-"`
127	Upstreams              []Upstream              `json:",omitempty"`
128	MeshGateway            MeshGatewayConfig       `json:",omitempty"`
129	Expose                 ExposeConfig            `json:",omitempty"`
130}
131
132const (
133	// MemberTagKeyACLMode is the key used to indicate what ACL mode the agent is
134	// operating in. The values of this key will be one of the MemberACLMode constants
135	// with the key not being present indicating ACLModeUnknown.
136	MemberTagKeyACLMode = "acls"
137
138	// MemberTagRole is the key used to indicate that the member is a server or not.
139	MemberTagKeyRole = "role"
140
141	// MemberTagValueRoleServer is the value of the MemberTagKeyRole used to indicate
142	// that the member represents a Consul server.
143	MemberTagValueRoleServer = "consul"
144
145	// MemberTagKeySegment is the key name of the tag used to indicate which network
146	// segment this member is in.
147	// Network Segments are a Consul Enterprise feature.
148	MemberTagKeySegment = "segment"
149
150	// MemberTagKeyBootstrap is the key name of the tag used to indicate whether this
151	// agent was started with the "bootstrap" configuration enabled
152	MemberTagKeyBootstrap = "bootstrap"
153	// MemberTagValueBootstrap is the value of the MemberTagKeyBootstrap key when the
154	// agent was started with the "bootstrap" configuration enabled.
155	MemberTagValueBootstrap = "1"
156
157	// MemberTagKeyBootstrapExpect is the key name of the tag used to indicate whether
158	// this agent was started with the "bootstrap_expect" configuration set to a non-zero
159	// value. The value of this key will be the string for of that configuration value.
160	MemberTagKeyBootstrapExpect = "expect"
161
162	// MemberTagKeyUseTLS is the key name of the tag used to indicate whther this agent
163	// was configured to use TLS.
164	MemberTagKeyUseTLS = "use_tls"
165	// MemberTagValueUseTLS is the value of the MemberTagKeyUseTLS when the agent was
166	// configured to use TLS. Any other value indicates that it was not setup in
167	// that manner.
168	MemberTagValueUseTLS = "1"
169
170	// MemberTagKeyReadReplica is the key used to indicate that the member is a read
171	// replica server (will remain a Raft non-voter).
172	// Read Replicas are a Consul Enterprise feature.
173	MemberTagKeyReadReplica = "read_replica"
174	// MemberTagValueReadReplica is the value of the MemberTagKeyReadReplica key when
175	// the member is in fact a read-replica. Any other value indicates that it is not.
176	// Read Replicas are a Consul Enterprise feature.
177	MemberTagValueReadReplica = "1"
178)
179
180type MemberACLMode string
181
182const (
183	// ACLModeDisables indicates that ACLs are disabled for this agent
184	ACLModeDisabled MemberACLMode = "0"
185	// ACLModeEnabled indicates that ACLs are enabled and operating in new ACL
186	// mode (v1.4.0+ ACLs)
187	ACLModeEnabled MemberACLMode = "1"
188	// ACLModeLegacy indicates that ACLs are enabled and operating in legacy mode.
189	ACLModeLegacy MemberACLMode = "2"
190	// ACLModeUnkown is used to indicate that the AgentMember.Tags didn't advertise
191	// an ACL mode at all. This is the case for Consul versions before v1.4.0 and
192	// should be treated similarly to ACLModeLegacy.
193	ACLModeUnknown MemberACLMode = "3"
194)
195
196// AgentMember represents a cluster member known to the agent
197type AgentMember struct {
198	Name string
199	Addr string
200	Port uint16
201	Tags map[string]string
202	// Status of the Member which corresponds to  github.com/hashicorp/serf/serf.MemberStatus
203	// Value is one of:
204	//
205	// 	  AgentMemberNone    = 0
206	//	  AgentMemberAlive   = 1
207	//	  AgentMemberLeaving = 2
208	//	  AgentMemberLeft    = 3
209	//	  AgentMemberFailed  = 4
210	Status      int
211	ProtocolMin uint8
212	ProtocolMax uint8
213	ProtocolCur uint8
214	DelegateMin uint8
215	DelegateMax uint8
216	DelegateCur uint8
217}
218
219// ACLMode returns the ACL mode this agent is operating in.
220func (m *AgentMember) ACLMode() MemberACLMode {
221	mode := m.Tags[MemberTagKeyACLMode]
222
223	// the key may not have existed but then an
224	// empty string will be returned and we will
225	// handle that in the default case of the switch
226	switch MemberACLMode(mode) {
227	case ACLModeDisabled:
228		return ACLModeDisabled
229	case ACLModeEnabled:
230		return ACLModeEnabled
231	case ACLModeLegacy:
232		return ACLModeLegacy
233	default:
234		return ACLModeUnknown
235	}
236}
237
238// IsConsulServer returns true when this member is a Consul server.
239func (m *AgentMember) IsConsulServer() bool {
240	return m.Tags[MemberTagKeyRole] == MemberTagValueRoleServer
241}
242
243// AllSegments is used to select for all segments in MembersOpts.
244const AllSegments = "_all"
245
246// MembersOpts is used for querying member information.
247type MembersOpts struct {
248	// WAN is whether to show members from the WAN.
249	WAN bool
250
251	// Segment is the LAN segment to show members for. Setting this to the
252	// AllSegments value above will show members in all segments.
253	Segment string
254}
255
256// AgentServiceRegistration is used to register a new service
257type AgentServiceRegistration struct {
258	Kind              ServiceKind               `json:",omitempty"`
259	ID                string                    `json:",omitempty"`
260	Name              string                    `json:",omitempty"`
261	Tags              []string                  `json:",omitempty"`
262	Port              int                       `json:",omitempty"`
263	Address           string                    `json:",omitempty"`
264	TaggedAddresses   map[string]ServiceAddress `json:",omitempty"`
265	EnableTagOverride bool                      `json:",omitempty"`
266	Meta              map[string]string         `json:",omitempty"`
267	Weights           *AgentWeights             `json:",omitempty"`
268	Check             *AgentServiceCheck
269	Checks            AgentServiceChecks
270	Proxy             *AgentServiceConnectProxyConfig `json:",omitempty"`
271	Connect           *AgentServiceConnect            `json:",omitempty"`
272	Namespace         string                          `json:",omitempty" bexpr:"-" hash:"ignore"`
273}
274
275// ServiceRegisterOpts is used to pass extra options to the service register.
276type ServiceRegisterOpts struct {
277	//Missing healthchecks will be deleted from the agent.
278	//Using this parameter allows to idempotently register a service and its checks without
279	//having to manually deregister checks.
280	ReplaceExistingChecks bool
281
282	// ctx is an optional context pass through to the underlying HTTP
283	// request layer. Use WithContext() to set the context.
284	ctx context.Context
285}
286
287// WithContext sets the context to be used for the request on a new ServiceRegisterOpts,
288// and returns the opts.
289func (o ServiceRegisterOpts) WithContext(ctx context.Context) ServiceRegisterOpts {
290	o.ctx = ctx
291	return o
292}
293
294// AgentCheckRegistration is used to register a new check
295type AgentCheckRegistration struct {
296	ID        string `json:",omitempty"`
297	Name      string `json:",omitempty"`
298	Notes     string `json:",omitempty"`
299	ServiceID string `json:",omitempty"`
300	AgentServiceCheck
301	Namespace string `json:",omitempty"`
302}
303
304// AgentServiceCheck is used to define a node or service level check
305type AgentServiceCheck struct {
306	CheckID                string              `json:",omitempty"`
307	Name                   string              `json:",omitempty"`
308	Args                   []string            `json:"ScriptArgs,omitempty"`
309	DockerContainerID      string              `json:",omitempty"`
310	Shell                  string              `json:",omitempty"` // Only supported for Docker.
311	Interval               string              `json:",omitempty"`
312	Timeout                string              `json:",omitempty"`
313	TTL                    string              `json:",omitempty"`
314	HTTP                   string              `json:",omitempty"`
315	Header                 map[string][]string `json:",omitempty"`
316	Method                 string              `json:",omitempty"`
317	Body                   string              `json:",omitempty"`
318	TCP                    string              `json:",omitempty"`
319	Status                 string              `json:",omitempty"`
320	Notes                  string              `json:",omitempty"`
321	TLSServerName          string              `json:",omitempty"`
322	TLSSkipVerify          bool                `json:",omitempty"`
323	GRPC                   string              `json:",omitempty"`
324	GRPCUseTLS             bool                `json:",omitempty"`
325	AliasNode              string              `json:",omitempty"`
326	AliasService           string              `json:",omitempty"`
327	SuccessBeforePassing   int                 `json:",omitempty"`
328	FailuresBeforeCritical int                 `json:",omitempty"`
329
330	// In Consul 0.7 and later, checks that are associated with a service
331	// may also contain this optional DeregisterCriticalServiceAfter field,
332	// which is a timeout in the same Go time format as Interval and TTL. If
333	// a check is in the critical state for more than this configured value,
334	// then its associated service (and all of its associated checks) will
335	// automatically be deregistered.
336	DeregisterCriticalServiceAfter string `json:",omitempty"`
337}
338type AgentServiceChecks []*AgentServiceCheck
339
340// AgentToken is used when updating ACL tokens for an agent.
341type AgentToken struct {
342	Token string
343}
344
345// Metrics info is used to store different types of metric values from the agent.
346type MetricsInfo struct {
347	Timestamp string
348	Gauges    []GaugeValue
349	Points    []PointValue
350	Counters  []SampledValue
351	Samples   []SampledValue
352}
353
354// GaugeValue stores one value that is updated as time goes on, such as
355// the amount of memory allocated.
356type GaugeValue struct {
357	Name   string
358	Value  float32
359	Labels map[string]string
360}
361
362// PointValue holds a series of points for a metric.
363type PointValue struct {
364	Name   string
365	Points []float32
366}
367
368// SampledValue stores info about a metric that is incremented over time,
369// such as the number of requests to an HTTP endpoint.
370type SampledValue struct {
371	Name   string
372	Count  int
373	Sum    float64
374	Min    float64
375	Max    float64
376	Mean   float64
377	Stddev float64
378	Labels map[string]string
379}
380
381// AgentAuthorizeParams are the request parameters for authorizing a request.
382type AgentAuthorizeParams struct {
383	Target           string
384	ClientCertURI    string
385	ClientCertSerial string
386}
387
388// AgentAuthorize is the response structure for Connect authorization.
389type AgentAuthorize struct {
390	Authorized bool
391	Reason     string
392}
393
394// ConnectProxyConfig is the response structure for agent-local proxy
395// configuration.
396type ConnectProxyConfig struct {
397	ProxyServiceID    string
398	TargetServiceID   string
399	TargetServiceName string
400	ContentHash       string
401	Config            map[string]interface{} `bexpr:"-"`
402	Upstreams         []Upstream
403}
404
405// Upstream is the response structure for a proxy upstream configuration.
406type Upstream struct {
407	DestinationType      UpstreamDestType `json:",omitempty"`
408	DestinationNamespace string           `json:",omitempty"`
409	DestinationName      string
410	Datacenter           string                 `json:",omitempty"`
411	LocalBindAddress     string                 `json:",omitempty"`
412	LocalBindPort        int                    `json:",omitempty"`
413	LocalBindSocketPath  string                 `json:",omitempty"`
414	LocalBindSocketMode  string                 `json:",omitempty"`
415	Config               map[string]interface{} `json:",omitempty" bexpr:"-"`
416	MeshGateway          MeshGatewayConfig      `json:",omitempty"`
417	CentrallyConfigured  bool                   `json:",omitempty" bexpr:"-"`
418}
419
420// Agent can be used to query the Agent endpoints
421type Agent struct {
422	c *Client
423
424	// cache the node name
425	nodeName string
426}
427
428// Agent returns a handle to the agent endpoints
429func (c *Client) Agent() *Agent {
430	return &Agent{c: c}
431}
432
433// Self is used to query the agent we are speaking to for
434// information about itself
435func (a *Agent) Self() (map[string]map[string]interface{}, error) {
436	r := a.c.newRequest("GET", "/v1/agent/self")
437	_, resp, err := requireOK(a.c.doRequest(r))
438	if err != nil {
439		return nil, err
440	}
441	defer closeResponseBody(resp)
442
443	var out map[string]map[string]interface{}
444	if err := decodeBody(resp, &out); err != nil {
445		return nil, err
446	}
447	return out, nil
448}
449
450// Host is used to retrieve information about the host the
451// agent is running on such as CPU, memory, and disk. Requires
452// a operator:read ACL token.
453func (a *Agent) Host() (map[string]interface{}, error) {
454	r := a.c.newRequest("GET", "/v1/agent/host")
455	_, resp, err := requireOK(a.c.doRequest(r))
456	if err != nil {
457		return nil, err
458	}
459	defer closeResponseBody(resp)
460
461	var out map[string]interface{}
462	if err := decodeBody(resp, &out); err != nil {
463		return nil, err
464	}
465	return out, nil
466}
467
468// Metrics is used to query the agent we are speaking to for
469// its current internal metric data
470func (a *Agent) Metrics() (*MetricsInfo, error) {
471	r := a.c.newRequest("GET", "/v1/agent/metrics")
472	_, resp, err := requireOK(a.c.doRequest(r))
473	if err != nil {
474		return nil, err
475	}
476	defer closeResponseBody(resp)
477
478	var out *MetricsInfo
479	if err := decodeBody(resp, &out); err != nil {
480		return nil, err
481	}
482	return out, nil
483}
484
485// Reload triggers a configuration reload for the agent we are connected to.
486func (a *Agent) Reload() error {
487	r := a.c.newRequest("PUT", "/v1/agent/reload")
488	_, resp, err := requireOK(a.c.doRequest(r))
489	if err != nil {
490		return err
491	}
492	closeResponseBody(resp)
493	return nil
494}
495
496// NodeName is used to get the node name of the agent
497func (a *Agent) NodeName() (string, error) {
498	if a.nodeName != "" {
499		return a.nodeName, nil
500	}
501	info, err := a.Self()
502	if err != nil {
503		return "", err
504	}
505	name := info["Config"]["NodeName"].(string)
506	a.nodeName = name
507	return name, nil
508}
509
510// Checks returns the locally registered checks
511func (a *Agent) Checks() (map[string]*AgentCheck, error) {
512	return a.ChecksWithFilter("")
513}
514
515// ChecksWithFilter returns a subset of the locally registered checks that match
516// the given filter expression
517func (a *Agent) ChecksWithFilter(filter string) (map[string]*AgentCheck, error) {
518	return a.ChecksWithFilterOpts(filter, nil)
519}
520
521// ChecksWithFilterOpts returns a subset of the locally registered checks that match
522// the given filter expression and QueryOptions.
523func (a *Agent) ChecksWithFilterOpts(filter string, q *QueryOptions) (map[string]*AgentCheck, error) {
524	r := a.c.newRequest("GET", "/v1/agent/checks")
525	r.setQueryOptions(q)
526	r.filterQuery(filter)
527	_, resp, err := requireOK(a.c.doRequest(r))
528	if err != nil {
529		return nil, err
530	}
531	defer closeResponseBody(resp)
532
533	var out map[string]*AgentCheck
534	if err := decodeBody(resp, &out); err != nil {
535		return nil, err
536	}
537	return out, nil
538}
539
540// Services returns the locally registered services
541func (a *Agent) Services() (map[string]*AgentService, error) {
542	return a.ServicesWithFilter("")
543}
544
545// ServicesWithFilter returns a subset of the locally registered services that match
546// the given filter expression
547func (a *Agent) ServicesWithFilter(filter string) (map[string]*AgentService, error) {
548	return a.ServicesWithFilterOpts(filter, nil)
549}
550
551// ServicesWithFilterOpts returns a subset of the locally registered services that match
552// the given filter expression and QueryOptions.
553func (a *Agent) ServicesWithFilterOpts(filter string, q *QueryOptions) (map[string]*AgentService, error) {
554	r := a.c.newRequest("GET", "/v1/agent/services")
555	r.setQueryOptions(q)
556	r.filterQuery(filter)
557	_, resp, err := requireOK(a.c.doRequest(r))
558	if err != nil {
559		return nil, err
560	}
561	defer closeResponseBody(resp)
562
563	var out map[string]*AgentService
564	if err := decodeBody(resp, &out); err != nil {
565		return nil, err
566	}
567
568	return out, nil
569}
570
571// AgentHealthServiceByID returns for a given serviceID: the aggregated health status, the service definition or an error if any
572// - If the service is not found, will return status (critical, nil, nil)
573// - If the service is found, will return (critical|passing|warning), AgentServiceChecksInfo, nil)
574// - In all other cases, will return an error
575func (a *Agent) AgentHealthServiceByID(serviceID string) (string, *AgentServiceChecksInfo, error) {
576	path := fmt.Sprintf("/v1/agent/health/service/id/%v", url.PathEscape(serviceID))
577	r := a.c.newRequest("GET", path)
578	r.params.Add("format", "json")
579	r.header.Set("Accept", "application/json")
580	_, resp, err := a.c.doRequest(r)
581	if err != nil {
582		return "", nil, err
583	}
584	defer closeResponseBody(resp)
585	// Service not Found
586	if resp.StatusCode == http.StatusNotFound {
587		return HealthCritical, nil, nil
588	}
589	var out *AgentServiceChecksInfo
590	if err := decodeBody(resp, &out); err != nil {
591		return HealthCritical, out, err
592	}
593	switch resp.StatusCode {
594	case http.StatusOK:
595		return HealthPassing, out, nil
596	case http.StatusTooManyRequests:
597		return HealthWarning, out, nil
598	case http.StatusServiceUnavailable:
599		return HealthCritical, out, nil
600	}
601	return HealthCritical, out, fmt.Errorf("Unexpected Error Code %v for %s", resp.StatusCode, path)
602}
603
604// AgentHealthServiceByName returns for a given service name: the aggregated health status for all services
605// having the specified name.
606// - If no service is not found, will return status (critical, [], nil)
607// - If the service is found, will return (critical|passing|warning), []api.AgentServiceChecksInfo, nil)
608// - In all other cases, will return an error
609func (a *Agent) AgentHealthServiceByName(service string) (string, []AgentServiceChecksInfo, error) {
610	path := fmt.Sprintf("/v1/agent/health/service/name/%v", url.PathEscape(service))
611	r := a.c.newRequest("GET", path)
612	r.params.Add("format", "json")
613	r.header.Set("Accept", "application/json")
614	_, resp, err := a.c.doRequest(r)
615	if err != nil {
616		return "", nil, err
617	}
618	defer closeResponseBody(resp)
619	// Service not Found
620	if resp.StatusCode == http.StatusNotFound {
621		return HealthCritical, nil, nil
622	}
623	var out []AgentServiceChecksInfo
624	if err := decodeBody(resp, &out); err != nil {
625		return HealthCritical, out, err
626	}
627	switch resp.StatusCode {
628	case http.StatusOK:
629		return HealthPassing, out, nil
630	case http.StatusTooManyRequests:
631		return HealthWarning, out, nil
632	case http.StatusServiceUnavailable:
633		return HealthCritical, out, nil
634	}
635	return HealthCritical, out, fmt.Errorf("Unexpected Error Code %v for %s", resp.StatusCode, path)
636}
637
638// Service returns a locally registered service instance and allows for
639// hash-based blocking.
640//
641// Note that this uses an unconventional blocking mechanism since it's
642// agent-local state. That means there is no persistent raft index so we block
643// based on object hash instead.
644func (a *Agent) Service(serviceID string, q *QueryOptions) (*AgentService, *QueryMeta, error) {
645	r := a.c.newRequest("GET", "/v1/agent/service/"+serviceID)
646	r.setQueryOptions(q)
647	rtt, resp, err := requireOK(a.c.doRequest(r))
648	if err != nil {
649		return nil, nil, err
650	}
651	defer closeResponseBody(resp)
652
653	qm := &QueryMeta{}
654	parseQueryMeta(resp, qm)
655	qm.RequestTime = rtt
656
657	var out *AgentService
658	if err := decodeBody(resp, &out); err != nil {
659		return nil, nil, err
660	}
661
662	return out, qm, nil
663}
664
665// Members returns the known gossip members. The WAN
666// flag can be used to query a server for WAN members.
667func (a *Agent) Members(wan bool) ([]*AgentMember, error) {
668	r := a.c.newRequest("GET", "/v1/agent/members")
669	if wan {
670		r.params.Set("wan", "1")
671	}
672	_, resp, err := requireOK(a.c.doRequest(r))
673	if err != nil {
674		return nil, err
675	}
676	defer closeResponseBody(resp)
677
678	var out []*AgentMember
679	if err := decodeBody(resp, &out); err != nil {
680		return nil, err
681	}
682	return out, nil
683}
684
685// MembersOpts returns the known gossip members and can be passed
686// additional options for WAN/segment filtering.
687func (a *Agent) MembersOpts(opts MembersOpts) ([]*AgentMember, error) {
688	r := a.c.newRequest("GET", "/v1/agent/members")
689	r.params.Set("segment", opts.Segment)
690	if opts.WAN {
691		r.params.Set("wan", "1")
692	}
693
694	_, resp, err := requireOK(a.c.doRequest(r))
695	if err != nil {
696		return nil, err
697	}
698	defer closeResponseBody(resp)
699
700	var out []*AgentMember
701	if err := decodeBody(resp, &out); err != nil {
702		return nil, err
703	}
704	return out, nil
705}
706
707// ServiceRegister is used to register a new service with
708// the local agent
709func (a *Agent) ServiceRegister(service *AgentServiceRegistration) error {
710	opts := ServiceRegisterOpts{
711		ReplaceExistingChecks: false,
712	}
713
714	return a.serviceRegister(service, opts)
715}
716
717// ServiceRegister is used to register a new service with
718// the local agent and can be passed additional options.
719func (a *Agent) ServiceRegisterOpts(service *AgentServiceRegistration, opts ServiceRegisterOpts) error {
720	return a.serviceRegister(service, opts)
721}
722
723func (a *Agent) serviceRegister(service *AgentServiceRegistration, opts ServiceRegisterOpts) error {
724	r := a.c.newRequest("PUT", "/v1/agent/service/register")
725	r.obj = service
726	r.ctx = opts.ctx
727	if opts.ReplaceExistingChecks {
728		r.params.Set("replace-existing-checks", "true")
729	}
730	_, resp, err := requireOK(a.c.doRequest(r))
731	if err != nil {
732		return err
733	}
734	closeResponseBody(resp)
735	return nil
736}
737
738// ServiceDeregister is used to deregister a service with
739// the local agent
740func (a *Agent) ServiceDeregister(serviceID string) error {
741	r := a.c.newRequest("PUT", "/v1/agent/service/deregister/"+serviceID)
742	_, resp, err := requireOK(a.c.doRequest(r))
743	if err != nil {
744		return err
745	}
746	closeResponseBody(resp)
747	return nil
748}
749
750// ServiceDeregisterOpts is used to deregister a service with
751// the local agent with QueryOptions.
752func (a *Agent) ServiceDeregisterOpts(serviceID string, q *QueryOptions) error {
753	r := a.c.newRequest("PUT", "/v1/agent/service/deregister/"+serviceID)
754	r.setQueryOptions(q)
755	_, resp, err := requireOK(a.c.doRequest(r))
756	if err != nil {
757		return err
758	}
759	closeResponseBody(resp)
760	return nil
761}
762
763// PassTTL is used to set a TTL check to the passing state.
764//
765// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
766// The client interface will be removed in 0.8 or changed to use
767// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9.
768func (a *Agent) PassTTL(checkID, note string) error {
769	return a.updateTTL(checkID, note, "pass")
770}
771
772// WarnTTL is used to set a TTL check to the warning state.
773//
774// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
775// The client interface will be removed in 0.8 or changed to use
776// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9.
777func (a *Agent) WarnTTL(checkID, note string) error {
778	return a.updateTTL(checkID, note, "warn")
779}
780
781// FailTTL is used to set a TTL check to the failing state.
782//
783// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
784// The client interface will be removed in 0.8 or changed to use
785// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9.
786func (a *Agent) FailTTL(checkID, note string) error {
787	return a.updateTTL(checkID, note, "fail")
788}
789
790// updateTTL is used to update the TTL of a check. This is the internal
791// method that uses the old API that's present in Consul versions prior to
792// 0.6.4. Since Consul didn't have an analogous "update" API before it seemed
793// ok to break this (former) UpdateTTL in favor of the new UpdateTTL below,
794// but keep the old Pass/Warn/Fail methods using the old API under the hood.
795//
796// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
797// The client interface will be removed in 0.8 and the server endpoints will
798// be removed in 0.9.
799func (a *Agent) updateTTL(checkID, note, status string) error {
800	switch status {
801	case "pass":
802	case "warn":
803	case "fail":
804	default:
805		return fmt.Errorf("Invalid status: %s", status)
806	}
807	endpoint := fmt.Sprintf("/v1/agent/check/%s/%s", status, checkID)
808	r := a.c.newRequest("PUT", endpoint)
809	r.params.Set("note", note)
810	_, resp, err := requireOK(a.c.doRequest(r))
811	if err != nil {
812		return err
813	}
814	closeResponseBody(resp)
815	return nil
816}
817
818// checkUpdate is the payload for a PUT for a check update.
819type checkUpdate struct {
820	// Status is one of the api.Health* states: HealthPassing
821	// ("passing"), HealthWarning ("warning"), or HealthCritical
822	// ("critical").
823	Status string
824
825	// Output is the information to post to the UI for operators as the
826	// output of the process that decided to hit the TTL check. This is
827	// different from the note field that's associated with the check
828	// itself.
829	Output string
830}
831
832// UpdateTTL is used to update the TTL of a check. This uses the newer API
833// that was introduced in Consul 0.6.4 and later. We translate the old status
834// strings for compatibility (though a newer version of Consul will still be
835// required to use this API).
836func (a *Agent) UpdateTTL(checkID, output, status string) error {
837	return a.UpdateTTLOpts(checkID, output, status, nil)
838}
839
840func (a *Agent) UpdateTTLOpts(checkID, output, status string, q *QueryOptions) error {
841	switch status {
842	case "pass", HealthPassing:
843		status = HealthPassing
844	case "warn", HealthWarning:
845		status = HealthWarning
846	case "fail", HealthCritical:
847		status = HealthCritical
848	default:
849		return fmt.Errorf("Invalid status: %s", status)
850	}
851
852	endpoint := fmt.Sprintf("/v1/agent/check/update/%s", checkID)
853	r := a.c.newRequest("PUT", endpoint)
854	r.setQueryOptions(q)
855	r.obj = &checkUpdate{
856		Status: status,
857		Output: output,
858	}
859
860	_, resp, err := requireOK(a.c.doRequest(r))
861	if err != nil {
862		return err
863	}
864	closeResponseBody(resp)
865	return nil
866}
867
868// CheckRegister is used to register a new check with
869// the local agent
870func (a *Agent) CheckRegister(check *AgentCheckRegistration) error {
871	r := a.c.newRequest("PUT", "/v1/agent/check/register")
872	r.obj = check
873	_, resp, err := requireOK(a.c.doRequest(r))
874	if err != nil {
875		return err
876	}
877	closeResponseBody(resp)
878	return nil
879}
880
881// CheckDeregister is used to deregister a check with
882// the local agent
883func (a *Agent) CheckDeregister(checkID string) error {
884	return a.CheckDeregisterOpts(checkID, nil)
885}
886
887// CheckDeregisterOpts is used to deregister a check with
888// the local agent using query options
889func (a *Agent) CheckDeregisterOpts(checkID string, q *QueryOptions) error {
890	r := a.c.newRequest("PUT", "/v1/agent/check/deregister/"+checkID)
891	r.setQueryOptions(q)
892	_, resp, err := requireOK(a.c.doRequest(r))
893	if err != nil {
894		return err
895	}
896	closeResponseBody(resp)
897	return nil
898}
899
900// Join is used to instruct the agent to attempt a join to
901// another cluster member
902func (a *Agent) Join(addr string, wan bool) error {
903	r := a.c.newRequest("PUT", "/v1/agent/join/"+addr)
904	if wan {
905		r.params.Set("wan", "1")
906	}
907	_, resp, err := requireOK(a.c.doRequest(r))
908	if err != nil {
909		return err
910	}
911	closeResponseBody(resp)
912	return nil
913}
914
915// Leave is used to have the agent gracefully leave the cluster and shutdown
916func (a *Agent) Leave() error {
917	r := a.c.newRequest("PUT", "/v1/agent/leave")
918	_, resp, err := requireOK(a.c.doRequest(r))
919	if err != nil {
920		return err
921	}
922	closeResponseBody(resp)
923	return nil
924}
925
926// ForceLeave is used to have the agent eject a failed node
927func (a *Agent) ForceLeave(node string) error {
928	r := a.c.newRequest("PUT", "/v1/agent/force-leave/"+node)
929	_, resp, err := requireOK(a.c.doRequest(r))
930	if err != nil {
931		return err
932	}
933	closeResponseBody(resp)
934	return nil
935}
936
937//ForceLeavePrune is used to have an a failed agent removed
938//from the list of members
939func (a *Agent) ForceLeavePrune(node string) error {
940	r := a.c.newRequest("PUT", "/v1/agent/force-leave/"+node)
941	r.params.Set("prune", "1")
942	_, resp, err := requireOK(a.c.doRequest(r))
943	if err != nil {
944		return err
945	}
946	closeResponseBody(resp)
947	return nil
948}
949
950// ConnectAuthorize is used to authorize an incoming connection
951// to a natively integrated Connect service.
952func (a *Agent) ConnectAuthorize(auth *AgentAuthorizeParams) (*AgentAuthorize, error) {
953	r := a.c.newRequest("POST", "/v1/agent/connect/authorize")
954	r.obj = auth
955	_, resp, err := requireOK(a.c.doRequest(r))
956	if err != nil {
957		return nil, err
958	}
959	defer closeResponseBody(resp)
960
961	var out AgentAuthorize
962	if err := decodeBody(resp, &out); err != nil {
963		return nil, err
964	}
965	return &out, nil
966}
967
968// ConnectCARoots returns the list of roots.
969func (a *Agent) ConnectCARoots(q *QueryOptions) (*CARootList, *QueryMeta, error) {
970	r := a.c.newRequest("GET", "/v1/agent/connect/ca/roots")
971	r.setQueryOptions(q)
972	rtt, resp, err := requireOK(a.c.doRequest(r))
973	if err != nil {
974		return nil, nil, err
975	}
976	defer closeResponseBody(resp)
977
978	qm := &QueryMeta{}
979	parseQueryMeta(resp, qm)
980	qm.RequestTime = rtt
981
982	var out CARootList
983	if err := decodeBody(resp, &out); err != nil {
984		return nil, nil, err
985	}
986	return &out, qm, nil
987}
988
989// ConnectCALeaf gets the leaf certificate for the given service ID.
990func (a *Agent) ConnectCALeaf(serviceID string, q *QueryOptions) (*LeafCert, *QueryMeta, error) {
991	r := a.c.newRequest("GET", "/v1/agent/connect/ca/leaf/"+serviceID)
992	r.setQueryOptions(q)
993	rtt, resp, err := requireOK(a.c.doRequest(r))
994	if err != nil {
995		return nil, nil, err
996	}
997	defer closeResponseBody(resp)
998
999	qm := &QueryMeta{}
1000	parseQueryMeta(resp, qm)
1001	qm.RequestTime = rtt
1002
1003	var out LeafCert
1004	if err := decodeBody(resp, &out); err != nil {
1005		return nil, nil, err
1006	}
1007	return &out, qm, nil
1008}
1009
1010// EnableServiceMaintenance toggles service maintenance mode on
1011// for the given service ID.
1012func (a *Agent) EnableServiceMaintenance(serviceID, reason string) error {
1013	r := a.c.newRequest("PUT", "/v1/agent/service/maintenance/"+serviceID)
1014	r.params.Set("enable", "true")
1015	r.params.Set("reason", reason)
1016	_, resp, err := requireOK(a.c.doRequest(r))
1017	if err != nil {
1018		return err
1019	}
1020	closeResponseBody(resp)
1021	return nil
1022}
1023
1024// DisableServiceMaintenance toggles service maintenance mode off
1025// for the given service ID.
1026func (a *Agent) DisableServiceMaintenance(serviceID string) error {
1027	r := a.c.newRequest("PUT", "/v1/agent/service/maintenance/"+serviceID)
1028	r.params.Set("enable", "false")
1029	_, resp, err := requireOK(a.c.doRequest(r))
1030	if err != nil {
1031		return err
1032	}
1033	closeResponseBody(resp)
1034	return nil
1035}
1036
1037// EnableNodeMaintenance toggles node maintenance mode on for the
1038// agent we are connected to.
1039func (a *Agent) EnableNodeMaintenance(reason string) error {
1040	r := a.c.newRequest("PUT", "/v1/agent/maintenance")
1041	r.params.Set("enable", "true")
1042	r.params.Set("reason", reason)
1043	_, resp, err := requireOK(a.c.doRequest(r))
1044	if err != nil {
1045		return err
1046	}
1047	closeResponseBody(resp)
1048	return nil
1049}
1050
1051// DisableNodeMaintenance toggles node maintenance mode off for the
1052// agent we are connected to.
1053func (a *Agent) DisableNodeMaintenance() error {
1054	r := a.c.newRequest("PUT", "/v1/agent/maintenance")
1055	r.params.Set("enable", "false")
1056	_, resp, err := requireOK(a.c.doRequest(r))
1057	if err != nil {
1058		return err
1059	}
1060	closeResponseBody(resp)
1061	return nil
1062}
1063
1064// Monitor returns a channel which will receive streaming logs from the agent
1065// Providing a non-nil stopCh can be used to close the connection and stop the
1066// log stream. An empty string will be sent down the given channel when there's
1067// nothing left to stream, after which the caller should close the stopCh.
1068func (a *Agent) Monitor(loglevel string, stopCh <-chan struct{}, q *QueryOptions) (chan string, error) {
1069	return a.monitor(loglevel, false, stopCh, q)
1070}
1071
1072// MonitorJSON is like Monitor except it returns logs in JSON format.
1073func (a *Agent) MonitorJSON(loglevel string, stopCh <-chan struct{}, q *QueryOptions) (chan string, error) {
1074	return a.monitor(loglevel, true, stopCh, q)
1075}
1076func (a *Agent) monitor(loglevel string, logJSON bool, stopCh <-chan struct{}, q *QueryOptions) (chan string, error) {
1077	r := a.c.newRequest("GET", "/v1/agent/monitor")
1078	r.setQueryOptions(q)
1079	if loglevel != "" {
1080		r.params.Add("loglevel", loglevel)
1081	}
1082	if logJSON {
1083		r.params.Set("logjson", "true")
1084	}
1085	_, resp, err := requireOK(a.c.doRequest(r))
1086	if err != nil {
1087		return nil, err
1088	}
1089	logCh := make(chan string, 64)
1090	go func() {
1091		defer closeResponseBody(resp)
1092		scanner := bufio.NewScanner(resp.Body)
1093		for {
1094			select {
1095			case <-stopCh:
1096				close(logCh)
1097				return
1098			default:
1099			}
1100			if scanner.Scan() {
1101				// An empty string signals to the caller that
1102				// the scan is done, so make sure we only emit
1103				// that when the scanner says it's done, not if
1104				// we happen to ingest an empty line.
1105				if text := scanner.Text(); text != "" {
1106					logCh <- text
1107				} else {
1108					logCh <- " "
1109				}
1110			} else {
1111				logCh <- ""
1112			}
1113		}
1114	}()
1115	return logCh, nil
1116}
1117
1118// UpdateACLToken updates the agent's "acl_token". See updateToken for more
1119// details.
1120//
1121// DEPRECATED (ACL-Legacy-Compat) - Prefer UpdateDefaultACLToken for v1.4.3 and above
1122func (a *Agent) UpdateACLToken(token string, q *WriteOptions) (*WriteMeta, error) {
1123	return a.updateToken("acl_token", token, q)
1124}
1125
1126// UpdateACLAgentToken updates the agent's "acl_agent_token". See updateToken
1127// for more details.
1128//
1129// DEPRECATED (ACL-Legacy-Compat) - Prefer UpdateAgentACLToken for v1.4.3 and above
1130func (a *Agent) UpdateACLAgentToken(token string, q *WriteOptions) (*WriteMeta, error) {
1131	return a.updateToken("acl_agent_token", token, q)
1132}
1133
1134// UpdateACLAgentMasterToken updates the agent's "acl_agent_master_token". See
1135// updateToken for more details.
1136//
1137// DEPRECATED (ACL-Legacy-Compat) - Prefer UpdateAgentMasterACLToken for v1.4.3 and above
1138func (a *Agent) UpdateACLAgentMasterToken(token string, q *WriteOptions) (*WriteMeta, error) {
1139	return a.updateToken("acl_agent_master_token", token, q)
1140}
1141
1142// UpdateACLReplicationToken updates the agent's "acl_replication_token". See
1143// updateToken for more details.
1144//
1145// DEPRECATED (ACL-Legacy-Compat) - Prefer UpdateReplicationACLToken for v1.4.3 and above
1146func (a *Agent) UpdateACLReplicationToken(token string, q *WriteOptions) (*WriteMeta, error) {
1147	return a.updateToken("acl_replication_token", token, q)
1148}
1149
1150// UpdateDefaultACLToken updates the agent's "default" token. See updateToken
1151// for more details
1152func (a *Agent) UpdateDefaultACLToken(token string, q *WriteOptions) (*WriteMeta, error) {
1153	return a.updateTokenFallback("default", "acl_token", token, q)
1154}
1155
1156// UpdateAgentACLToken updates the agent's "agent" token. See updateToken
1157// for more details
1158func (a *Agent) UpdateAgentACLToken(token string, q *WriteOptions) (*WriteMeta, error) {
1159	return a.updateTokenFallback("agent", "acl_agent_token", token, q)
1160}
1161
1162// UpdateAgentMasterACLToken updates the agent's "agent_master" token. See updateToken
1163// for more details
1164func (a *Agent) UpdateAgentMasterACLToken(token string, q *WriteOptions) (*WriteMeta, error) {
1165	return a.updateTokenFallback("agent_master", "acl_agent_master_token", token, q)
1166}
1167
1168// UpdateReplicationACLToken updates the agent's "replication" token. See updateToken
1169// for more details
1170func (a *Agent) UpdateReplicationACLToken(token string, q *WriteOptions) (*WriteMeta, error) {
1171	return a.updateTokenFallback("replication", "acl_replication_token", token, q)
1172}
1173
1174// updateToken can be used to update one of an agent's ACL tokens after the agent has
1175// started. The tokens are may not be persisted, so will need to be updated again if
1176// the agent is restarted unless the agent is configured to persist them.
1177func (a *Agent) updateToken(target, token string, q *WriteOptions) (*WriteMeta, error) {
1178	meta, _, err := a.updateTokenOnce(target, token, q)
1179	return meta, err
1180}
1181
1182func (a *Agent) updateTokenFallback(target, fallback, token string, q *WriteOptions) (*WriteMeta, error) {
1183	meta, status, err := a.updateTokenOnce(target, token, q)
1184	if err != nil && status == 404 {
1185		meta, _, err = a.updateTokenOnce(fallback, token, q)
1186	}
1187	return meta, err
1188}
1189
1190func (a *Agent) updateTokenOnce(target, token string, q *WriteOptions) (*WriteMeta, int, error) {
1191	r := a.c.newRequest("PUT", fmt.Sprintf("/v1/agent/token/%s", target))
1192	r.setWriteOptions(q)
1193	r.obj = &AgentToken{Token: token}
1194
1195	rtt, resp, err := a.c.doRequest(r)
1196	if err != nil {
1197		return nil, 0, err
1198	}
1199	defer closeResponseBody(resp)
1200
1201	wm := &WriteMeta{RequestTime: rtt}
1202
1203	if resp.StatusCode != 200 {
1204		var buf bytes.Buffer
1205		io.Copy(&buf, resp.Body)
1206		return wm, resp.StatusCode, fmt.Errorf("Unexpected response code: %d (%s)", resp.StatusCode, buf.Bytes())
1207	}
1208
1209	return wm, resp.StatusCode, nil
1210}
1211