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