mirror of
https://github.com/status-im/consul.git
synced 2025-01-09 21:35:52 +00:00
deb43fea6b
Cancellation channels are often derived from a Context, which returns a directional `<-chan struct{}` from Done(). In order to use this with parts of of the consul API, one is required to create a new channel and dispatch a separate goroutine to watch for context cancellation and close the new channel. Changing the signature for the methods that take cancellation channels will allow easier integration with existing uses of Context. Since the cancellation pattern only reads from these channels, there should be no backwards incompatibility with existing codebases, and most of the methods already accept only the correct type.
476 lines
13 KiB
Go
476 lines
13 KiB
Go
package api
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
)
|
|
|
|
// AgentCheck represents a check known to the agent
|
|
type AgentCheck struct {
|
|
Node string
|
|
CheckID string
|
|
Name string
|
|
Status string
|
|
Notes string
|
|
Output string
|
|
ServiceID string
|
|
ServiceName string
|
|
}
|
|
|
|
// AgentService represents a service known to the agent
|
|
type AgentService struct {
|
|
ID string
|
|
Service string
|
|
Tags []string
|
|
Port int
|
|
Address string
|
|
EnableTagOverride bool
|
|
CreateIndex uint64
|
|
ModifyIndex uint64
|
|
}
|
|
|
|
// AgentMember represents a cluster member known to the agent
|
|
type AgentMember struct {
|
|
Name string
|
|
Addr string
|
|
Port uint16
|
|
Tags map[string]string
|
|
Status int
|
|
ProtocolMin uint8
|
|
ProtocolMax uint8
|
|
ProtocolCur uint8
|
|
DelegateMin uint8
|
|
DelegateMax uint8
|
|
DelegateCur uint8
|
|
}
|
|
|
|
// AgentServiceRegistration is used to register a new service
|
|
type AgentServiceRegistration struct {
|
|
ID string `json:",omitempty"`
|
|
Name string `json:",omitempty"`
|
|
Tags []string `json:",omitempty"`
|
|
Port int `json:",omitempty"`
|
|
Address string `json:",omitempty"`
|
|
EnableTagOverride bool `json:",omitempty"`
|
|
Check *AgentServiceCheck
|
|
Checks AgentServiceChecks
|
|
}
|
|
|
|
// AgentCheckRegistration is used to register a new check
|
|
type AgentCheckRegistration struct {
|
|
ID string `json:",omitempty"`
|
|
Name string `json:",omitempty"`
|
|
Notes string `json:",omitempty"`
|
|
ServiceID string `json:",omitempty"`
|
|
AgentServiceCheck
|
|
}
|
|
|
|
// AgentServiceCheck is used to define a node or service level check
|
|
type AgentServiceCheck struct {
|
|
Script string `json:",omitempty"`
|
|
DockerContainerID string `json:",omitempty"`
|
|
Shell string `json:",omitempty"` // Only supported for Docker.
|
|
Interval string `json:",omitempty"`
|
|
Timeout string `json:",omitempty"`
|
|
TTL string `json:",omitempty"`
|
|
HTTP string `json:",omitempty"`
|
|
Header map[string][]string `json:",omitempty"`
|
|
Method string `json:",omitempty"`
|
|
TCP string `json:",omitempty"`
|
|
Status string `json:",omitempty"`
|
|
Notes string `json:",omitempty"`
|
|
TLSSkipVerify bool `json:",omitempty"`
|
|
|
|
// In Consul 0.7 and later, checks that are associated with a service
|
|
// may also contain this optional DeregisterCriticalServiceAfter field,
|
|
// which is a timeout in the same Go time format as Interval and TTL. If
|
|
// a check is in the critical state for more than this configured value,
|
|
// then its associated service (and all of its associated checks) will
|
|
// automatically be deregistered.
|
|
DeregisterCriticalServiceAfter string `json:",omitempty"`
|
|
}
|
|
type AgentServiceChecks []*AgentServiceCheck
|
|
|
|
// Agent can be used to query the Agent endpoints
|
|
type Agent struct {
|
|
c *Client
|
|
|
|
// cache the node name
|
|
nodeName string
|
|
}
|
|
|
|
// Agent returns a handle to the agent endpoints
|
|
func (c *Client) Agent() *Agent {
|
|
return &Agent{c: c}
|
|
}
|
|
|
|
// Self is used to query the agent we are speaking to for
|
|
// information about itself
|
|
func (a *Agent) Self() (map[string]map[string]interface{}, error) {
|
|
r := a.c.newRequest("GET", "/v1/agent/self")
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var out map[string]map[string]interface{}
|
|
if err := decodeBody(resp, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Reload triggers a configuration reload for the agent we are connected to.
|
|
func (a *Agent) Reload() error {
|
|
r := a.c.newRequest("PUT", "/v1/agent/reload")
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// NodeName is used to get the node name of the agent
|
|
func (a *Agent) NodeName() (string, error) {
|
|
if a.nodeName != "" {
|
|
return a.nodeName, nil
|
|
}
|
|
info, err := a.Self()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
name := info["Config"]["NodeName"].(string)
|
|
a.nodeName = name
|
|
return name, nil
|
|
}
|
|
|
|
// Checks returns the locally registered checks
|
|
func (a *Agent) Checks() (map[string]*AgentCheck, error) {
|
|
r := a.c.newRequest("GET", "/v1/agent/checks")
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var out map[string]*AgentCheck
|
|
if err := decodeBody(resp, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Services returns the locally registered services
|
|
func (a *Agent) Services() (map[string]*AgentService, error) {
|
|
r := a.c.newRequest("GET", "/v1/agent/services")
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var out map[string]*AgentService
|
|
if err := decodeBody(resp, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Members returns the known gossip members. The WAN
|
|
// flag can be used to query a server for WAN members.
|
|
func (a *Agent) Members(wan bool) ([]*AgentMember, error) {
|
|
r := a.c.newRequest("GET", "/v1/agent/members")
|
|
if wan {
|
|
r.params.Set("wan", "1")
|
|
}
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var out []*AgentMember
|
|
if err := decodeBody(resp, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// ServiceRegister is used to register a new service with
|
|
// the local agent
|
|
func (a *Agent) ServiceRegister(service *AgentServiceRegistration) error {
|
|
r := a.c.newRequest("PUT", "/v1/agent/service/register")
|
|
r.obj = service
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// ServiceDeregister is used to deregister a service with
|
|
// the local agent
|
|
func (a *Agent) ServiceDeregister(serviceID string) error {
|
|
r := a.c.newRequest("PUT", "/v1/agent/service/deregister/"+serviceID)
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// PassTTL is used to set a TTL check to the passing state.
|
|
//
|
|
// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
|
|
// The client interface will be removed in 0.8 or changed to use
|
|
// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9.
|
|
func (a *Agent) PassTTL(checkID, note string) error {
|
|
return a.updateTTL(checkID, note, "pass")
|
|
}
|
|
|
|
// WarnTTL is used to set a TTL check to the warning state.
|
|
//
|
|
// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
|
|
// The client interface will be removed in 0.8 or changed to use
|
|
// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9.
|
|
func (a *Agent) WarnTTL(checkID, note string) error {
|
|
return a.updateTTL(checkID, note, "warn")
|
|
}
|
|
|
|
// FailTTL is used to set a TTL check to the failing state.
|
|
//
|
|
// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
|
|
// The client interface will be removed in 0.8 or changed to use
|
|
// UpdateTTL()'s endpoint and the server endpoints will be removed in 0.9.
|
|
func (a *Agent) FailTTL(checkID, note string) error {
|
|
return a.updateTTL(checkID, note, "fail")
|
|
}
|
|
|
|
// updateTTL is used to update the TTL of a check. This is the internal
|
|
// method that uses the old API that's present in Consul versions prior to
|
|
// 0.6.4. Since Consul didn't have an analogous "update" API before it seemed
|
|
// ok to break this (former) UpdateTTL in favor of the new UpdateTTL below,
|
|
// but keep the old Pass/Warn/Fail methods using the old API under the hood.
|
|
//
|
|
// DEPRECATION NOTICE: This interface is deprecated in favor of UpdateTTL().
|
|
// The client interface will be removed in 0.8 and the server endpoints will
|
|
// be removed in 0.9.
|
|
func (a *Agent) updateTTL(checkID, note, status string) error {
|
|
switch status {
|
|
case "pass":
|
|
case "warn":
|
|
case "fail":
|
|
default:
|
|
return fmt.Errorf("Invalid status: %s", status)
|
|
}
|
|
endpoint := fmt.Sprintf("/v1/agent/check/%s/%s", status, checkID)
|
|
r := a.c.newRequest("PUT", endpoint)
|
|
r.params.Set("note", note)
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// checkUpdate is the payload for a PUT for a check update.
|
|
type checkUpdate struct {
|
|
// Status is one of the api.Health* states: HealthPassing
|
|
// ("passing"), HealthWarning ("warning"), or HealthCritical
|
|
// ("critical").
|
|
Status string
|
|
|
|
// Output is the information to post to the UI for operators as the
|
|
// output of the process that decided to hit the TTL check. This is
|
|
// different from the note field that's associated with the check
|
|
// itself.
|
|
Output string
|
|
}
|
|
|
|
// UpdateTTL is used to update the TTL of a check. This uses the newer API
|
|
// that was introduced in Consul 0.6.4 and later. We translate the old status
|
|
// strings for compatibility (though a newer version of Consul will still be
|
|
// required to use this API).
|
|
func (a *Agent) UpdateTTL(checkID, output, status string) error {
|
|
switch status {
|
|
case "pass", HealthPassing:
|
|
status = HealthPassing
|
|
case "warn", HealthWarning:
|
|
status = HealthWarning
|
|
case "fail", HealthCritical:
|
|
status = HealthCritical
|
|
default:
|
|
return fmt.Errorf("Invalid status: %s", status)
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("/v1/agent/check/update/%s", checkID)
|
|
r := a.c.newRequest("PUT", endpoint)
|
|
r.obj = &checkUpdate{
|
|
Status: status,
|
|
Output: output,
|
|
}
|
|
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// CheckRegister is used to register a new check with
|
|
// the local agent
|
|
func (a *Agent) CheckRegister(check *AgentCheckRegistration) error {
|
|
r := a.c.newRequest("PUT", "/v1/agent/check/register")
|
|
r.obj = check
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// CheckDeregister is used to deregister a check with
|
|
// the local agent
|
|
func (a *Agent) CheckDeregister(checkID string) error {
|
|
r := a.c.newRequest("PUT", "/v1/agent/check/deregister/"+checkID)
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// Join is used to instruct the agent to attempt a join to
|
|
// another cluster member
|
|
func (a *Agent) Join(addr string, wan bool) error {
|
|
r := a.c.newRequest("PUT", "/v1/agent/join/"+addr)
|
|
if wan {
|
|
r.params.Set("wan", "1")
|
|
}
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// Leave is used to have the agent gracefully leave the cluster and shutdown
|
|
func (a *Agent) Leave() error {
|
|
r := a.c.newRequest("PUT", "/v1/agent/leave")
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// ForceLeave is used to have the agent eject a failed node
|
|
func (a *Agent) ForceLeave(node string) error {
|
|
r := a.c.newRequest("PUT", "/v1/agent/force-leave/"+node)
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// EnableServiceMaintenance toggles service maintenance mode on
|
|
// for the given service ID.
|
|
func (a *Agent) EnableServiceMaintenance(serviceID, reason string) error {
|
|
r := a.c.newRequest("PUT", "/v1/agent/service/maintenance/"+serviceID)
|
|
r.params.Set("enable", "true")
|
|
r.params.Set("reason", reason)
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// DisableServiceMaintenance toggles service maintenance mode off
|
|
// for the given service ID.
|
|
func (a *Agent) DisableServiceMaintenance(serviceID string) error {
|
|
r := a.c.newRequest("PUT", "/v1/agent/service/maintenance/"+serviceID)
|
|
r.params.Set("enable", "false")
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// EnableNodeMaintenance toggles node maintenance mode on for the
|
|
// agent we are connected to.
|
|
func (a *Agent) EnableNodeMaintenance(reason string) error {
|
|
r := a.c.newRequest("PUT", "/v1/agent/maintenance")
|
|
r.params.Set("enable", "true")
|
|
r.params.Set("reason", reason)
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// DisableNodeMaintenance toggles node maintenance mode off for the
|
|
// agent we are connected to.
|
|
func (a *Agent) DisableNodeMaintenance() error {
|
|
r := a.c.newRequest("PUT", "/v1/agent/maintenance")
|
|
r.params.Set("enable", "false")
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// Monitor returns a channel which will receive streaming logs from the agent
|
|
// Providing a non-nil stopCh can be used to close the connection and stop the
|
|
// log stream
|
|
func (a *Agent) Monitor(loglevel string, stopCh <-chan struct{}, q *QueryOptions) (chan string, error) {
|
|
r := a.c.newRequest("GET", "/v1/agent/monitor")
|
|
r.setQueryOptions(q)
|
|
if loglevel != "" {
|
|
r.params.Add("loglevel", loglevel)
|
|
}
|
|
_, resp, err := requireOK(a.c.doRequest(r))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
logCh := make(chan string, 64)
|
|
go func() {
|
|
defer resp.Body.Close()
|
|
|
|
scanner := bufio.NewScanner(resp.Body)
|
|
for {
|
|
select {
|
|
case <-stopCh:
|
|
close(logCh)
|
|
return
|
|
default:
|
|
}
|
|
if scanner.Scan() {
|
|
logCh <- scanner.Text()
|
|
}
|
|
}
|
|
}()
|
|
|
|
return logCh, nil
|
|
}
|