mirror of
https://github.com/status-im/consul.git
synced 2025-01-09 21:35:52 +00:00
remove sdk and api packages (#6214)
This commit is contained in:
parent
3558f9cf6d
commit
3c889b4e65
67
vendor/github.com/hashicorp/consul/api/README.md
generated
vendored
67
vendor/github.com/hashicorp/consul/api/README.md
generated
vendored
@ -1,67 +0,0 @@
|
|||||||
Consul API client
|
|
||||||
=================
|
|
||||||
|
|
||||||
This package provides the `api` package which attempts to
|
|
||||||
provide programmatic access to the full Consul API.
|
|
||||||
|
|
||||||
Currently, all of the Consul APIs included in version 0.6.0 are supported.
|
|
||||||
|
|
||||||
Documentation
|
|
||||||
=============
|
|
||||||
|
|
||||||
The full documentation is available on [Godoc](https://godoc.org/github.com/hashicorp/consul/api)
|
|
||||||
|
|
||||||
Usage
|
|
||||||
=====
|
|
||||||
|
|
||||||
Below is an example of using the Consul client:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "github.com/hashicorp/consul/api"
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Get a new client
|
|
||||||
client, err := api.NewClient(api.DefaultConfig())
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a handle to the KV API
|
|
||||||
kv := client.KV()
|
|
||||||
|
|
||||||
// PUT a new KV pair
|
|
||||||
p := &api.KVPair{Key: "REDIS_MAXCLIENTS", Value: []byte("1000")}
|
|
||||||
_, err = kv.Put(p, nil)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup the pair
|
|
||||||
pair, _, err := kv.Get("REDIS_MAXCLIENTS", nil)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Printf("KV: %v %s\n", pair.Key, pair.Value)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
To run this example, start a Consul server:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
consul agent -dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy the code above into a file such as `main.go`.
|
|
||||||
|
|
||||||
Install and run. You'll see a key (`REDIS_MAXCLIENTS`) and value (`1000`) printed.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ go get
|
|
||||||
$ go run main.go
|
|
||||||
KV: REDIS_MAXCLIENTS 1000
|
|
||||||
```
|
|
||||||
|
|
||||||
After running the code, you can also view the values in the Consul UI on your local machine at http://localhost:8500/ui/dc1/kv
|
|
1116
vendor/github.com/hashicorp/consul/api/acl.go
generated
vendored
1116
vendor/github.com/hashicorp/consul/api/acl.go
generated
vendored
File diff suppressed because it is too large
Load Diff
1035
vendor/github.com/hashicorp/consul/api/agent.go
generated
vendored
1035
vendor/github.com/hashicorp/consul/api/agent.go
generated
vendored
File diff suppressed because it is too large
Load Diff
966
vendor/github.com/hashicorp/consul/api/api.go
generated
vendored
966
vendor/github.com/hashicorp/consul/api/api.go
generated
vendored
@ -1,966 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-cleanhttp"
|
|
||||||
"github.com/hashicorp/go-rootcerts"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// HTTPAddrEnvName defines an environment variable name which sets
|
|
||||||
// the HTTP address if there is no -http-addr specified.
|
|
||||||
HTTPAddrEnvName = "CONSUL_HTTP_ADDR"
|
|
||||||
|
|
||||||
// HTTPTokenEnvName defines an environment variable name which sets
|
|
||||||
// the HTTP token.
|
|
||||||
HTTPTokenEnvName = "CONSUL_HTTP_TOKEN"
|
|
||||||
|
|
||||||
// HTTPTokenFileEnvName defines an environment variable name which sets
|
|
||||||
// the HTTP token file.
|
|
||||||
HTTPTokenFileEnvName = "CONSUL_HTTP_TOKEN_FILE"
|
|
||||||
|
|
||||||
// HTTPAuthEnvName defines an environment variable name which sets
|
|
||||||
// the HTTP authentication header.
|
|
||||||
HTTPAuthEnvName = "CONSUL_HTTP_AUTH"
|
|
||||||
|
|
||||||
// HTTPSSLEnvName defines an environment variable name which sets
|
|
||||||
// whether or not to use HTTPS.
|
|
||||||
HTTPSSLEnvName = "CONSUL_HTTP_SSL"
|
|
||||||
|
|
||||||
// HTTPCAFile defines an environment variable name which sets the
|
|
||||||
// CA file to use for talking to Consul over TLS.
|
|
||||||
HTTPCAFile = "CONSUL_CACERT"
|
|
||||||
|
|
||||||
// HTTPCAPath defines an environment variable name which sets the
|
|
||||||
// path to a directory of CA certs to use for talking to Consul over TLS.
|
|
||||||
HTTPCAPath = "CONSUL_CAPATH"
|
|
||||||
|
|
||||||
// HTTPClientCert defines an environment variable name which sets the
|
|
||||||
// client cert file to use for talking to Consul over TLS.
|
|
||||||
HTTPClientCert = "CONSUL_CLIENT_CERT"
|
|
||||||
|
|
||||||
// HTTPClientKey defines an environment variable name which sets the
|
|
||||||
// client key file to use for talking to Consul over TLS.
|
|
||||||
HTTPClientKey = "CONSUL_CLIENT_KEY"
|
|
||||||
|
|
||||||
// HTTPTLSServerName defines an environment variable name which sets the
|
|
||||||
// server name to use as the SNI host when connecting via TLS
|
|
||||||
HTTPTLSServerName = "CONSUL_TLS_SERVER_NAME"
|
|
||||||
|
|
||||||
// HTTPSSLVerifyEnvName defines an environment variable name which sets
|
|
||||||
// whether or not to disable certificate checking.
|
|
||||||
HTTPSSLVerifyEnvName = "CONSUL_HTTP_SSL_VERIFY"
|
|
||||||
|
|
||||||
// GRPCAddrEnvName defines an environment variable name which sets the gRPC
|
|
||||||
// address for consul connect envoy. Note this isn't actually used by the api
|
|
||||||
// client in this package but is defined here for consistency with all the
|
|
||||||
// other ENV names we use.
|
|
||||||
GRPCAddrEnvName = "CONSUL_GRPC_ADDR"
|
|
||||||
)
|
|
||||||
|
|
||||||
// QueryOptions are used to parameterize a query
|
|
||||||
type QueryOptions struct {
|
|
||||||
// Providing a datacenter overwrites the DC provided
|
|
||||||
// by the Config
|
|
||||||
Datacenter string
|
|
||||||
|
|
||||||
// AllowStale allows any Consul server (non-leader) to service
|
|
||||||
// a read. This allows for lower latency and higher throughput
|
|
||||||
AllowStale bool
|
|
||||||
|
|
||||||
// RequireConsistent forces the read to be fully consistent.
|
|
||||||
// This is more expensive but prevents ever performing a stale
|
|
||||||
// read.
|
|
||||||
RequireConsistent bool
|
|
||||||
|
|
||||||
// UseCache requests that the agent cache results locally. See
|
|
||||||
// https://www.consul.io/api/features/caching.html for more details on the
|
|
||||||
// semantics.
|
|
||||||
UseCache bool
|
|
||||||
|
|
||||||
// MaxAge limits how old a cached value will be returned if UseCache is true.
|
|
||||||
// If there is a cached response that is older than the MaxAge, it is treated
|
|
||||||
// as a cache miss and a new fetch invoked. If the fetch fails, the error is
|
|
||||||
// returned. Clients that wish to allow for stale results on error can set
|
|
||||||
// StaleIfError to a longer duration to change this behavior. It is ignored
|
|
||||||
// if the endpoint supports background refresh caching. See
|
|
||||||
// https://www.consul.io/api/features/caching.html for more details.
|
|
||||||
MaxAge time.Duration
|
|
||||||
|
|
||||||
// StaleIfError specifies how stale the client will accept a cached response
|
|
||||||
// if the servers are unavailable to fetch a fresh one. Only makes sense when
|
|
||||||
// UseCache is true and MaxAge is set to a lower, non-zero value. It is
|
|
||||||
// ignored if the endpoint supports background refresh caching. See
|
|
||||||
// https://www.consul.io/api/features/caching.html for more details.
|
|
||||||
StaleIfError time.Duration
|
|
||||||
|
|
||||||
// WaitIndex is used to enable a blocking query. Waits
|
|
||||||
// until the timeout or the next index is reached
|
|
||||||
WaitIndex uint64
|
|
||||||
|
|
||||||
// WaitHash is used by some endpoints instead of WaitIndex to perform blocking
|
|
||||||
// on state based on a hash of the response rather than a monotonic index.
|
|
||||||
// This is required when the state being blocked on is not stored in Raft, for
|
|
||||||
// example agent-local proxy configuration.
|
|
||||||
WaitHash string
|
|
||||||
|
|
||||||
// WaitTime is used to bound the duration of a wait.
|
|
||||||
// Defaults to that of the Config, but can be overridden.
|
|
||||||
WaitTime time.Duration
|
|
||||||
|
|
||||||
// Token is used to provide a per-request ACL token
|
|
||||||
// which overrides the agent's default token.
|
|
||||||
Token string
|
|
||||||
|
|
||||||
// Near is used to provide a node name that will sort the results
|
|
||||||
// in ascending order based on the estimated round trip time from
|
|
||||||
// that node. Setting this to "_agent" will use the agent's node
|
|
||||||
// for the sort.
|
|
||||||
Near string
|
|
||||||
|
|
||||||
// NodeMeta is used to filter results by nodes with the given
|
|
||||||
// metadata key/value pairs. Currently, only one key/value pair can
|
|
||||||
// be provided for filtering.
|
|
||||||
NodeMeta map[string]string
|
|
||||||
|
|
||||||
// RelayFactor is used in keyring operations to cause responses to be
|
|
||||||
// relayed back to the sender through N other random nodes. Must be
|
|
||||||
// a value from 0 to 5 (inclusive).
|
|
||||||
RelayFactor uint8
|
|
||||||
|
|
||||||
// Connect filters prepared query execution to only include Connect-capable
|
|
||||||
// services. This currently affects prepared query execution.
|
|
||||||
Connect bool
|
|
||||||
|
|
||||||
// ctx is an optional context pass through to the underlying HTTP
|
|
||||||
// request layer. Use Context() and WithContext() to manage this.
|
|
||||||
ctx context.Context
|
|
||||||
|
|
||||||
// Filter requests filtering data prior to it being returned. The string
|
|
||||||
// is a go-bexpr compatible expression.
|
|
||||||
Filter string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *QueryOptions) Context() context.Context {
|
|
||||||
if o != nil && o.ctx != nil {
|
|
||||||
return o.ctx
|
|
||||||
}
|
|
||||||
return context.Background()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *QueryOptions) WithContext(ctx context.Context) *QueryOptions {
|
|
||||||
o2 := new(QueryOptions)
|
|
||||||
if o != nil {
|
|
||||||
*o2 = *o
|
|
||||||
}
|
|
||||||
o2.ctx = ctx
|
|
||||||
return o2
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteOptions are used to parameterize a write
|
|
||||||
type WriteOptions struct {
|
|
||||||
// Providing a datacenter overwrites the DC provided
|
|
||||||
// by the Config
|
|
||||||
Datacenter string
|
|
||||||
|
|
||||||
// Token is used to provide a per-request ACL token
|
|
||||||
// which overrides the agent's default token.
|
|
||||||
Token string
|
|
||||||
|
|
||||||
// RelayFactor is used in keyring operations to cause responses to be
|
|
||||||
// relayed back to the sender through N other random nodes. Must be
|
|
||||||
// a value from 0 to 5 (inclusive).
|
|
||||||
RelayFactor uint8
|
|
||||||
|
|
||||||
// ctx is an optional context pass through to the underlying HTTP
|
|
||||||
// request layer. Use Context() and WithContext() to manage this.
|
|
||||||
ctx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *WriteOptions) Context() context.Context {
|
|
||||||
if o != nil && o.ctx != nil {
|
|
||||||
return o.ctx
|
|
||||||
}
|
|
||||||
return context.Background()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *WriteOptions) WithContext(ctx context.Context) *WriteOptions {
|
|
||||||
o2 := new(WriteOptions)
|
|
||||||
if o != nil {
|
|
||||||
*o2 = *o
|
|
||||||
}
|
|
||||||
o2.ctx = ctx
|
|
||||||
return o2
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryMeta is used to return meta data about a query
|
|
||||||
type QueryMeta struct {
|
|
||||||
// LastIndex. This can be used as a WaitIndex to perform
|
|
||||||
// a blocking query
|
|
||||||
LastIndex uint64
|
|
||||||
|
|
||||||
// LastContentHash. This can be used as a WaitHash to perform a blocking query
|
|
||||||
// for endpoints that support hash-based blocking. Endpoints that do not
|
|
||||||
// support it will return an empty hash.
|
|
||||||
LastContentHash string
|
|
||||||
|
|
||||||
// Time of last contact from the leader for the
|
|
||||||
// server servicing the request
|
|
||||||
LastContact time.Duration
|
|
||||||
|
|
||||||
// Is there a known leader
|
|
||||||
KnownLeader bool
|
|
||||||
|
|
||||||
// How long did the request take
|
|
||||||
RequestTime time.Duration
|
|
||||||
|
|
||||||
// Is address translation enabled for HTTP responses on this agent
|
|
||||||
AddressTranslationEnabled bool
|
|
||||||
|
|
||||||
// CacheHit is true if the result was served from agent-local cache.
|
|
||||||
CacheHit bool
|
|
||||||
|
|
||||||
// CacheAge is set if request was ?cached and indicates how stale the cached
|
|
||||||
// response is.
|
|
||||||
CacheAge time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteMeta is used to return meta data about a write
|
|
||||||
type WriteMeta struct {
|
|
||||||
// How long did the request take
|
|
||||||
RequestTime time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// HttpBasicAuth is used to authenticate http client with HTTP Basic Authentication
|
|
||||||
type HttpBasicAuth struct {
|
|
||||||
// Username to use for HTTP Basic Authentication
|
|
||||||
Username string
|
|
||||||
|
|
||||||
// Password to use for HTTP Basic Authentication
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config is used to configure the creation of a client
|
|
||||||
type Config struct {
|
|
||||||
// Address is the address of the Consul server
|
|
||||||
Address string
|
|
||||||
|
|
||||||
// Scheme is the URI scheme for the Consul server
|
|
||||||
Scheme string
|
|
||||||
|
|
||||||
// Datacenter to use. If not provided, the default agent datacenter is used.
|
|
||||||
Datacenter string
|
|
||||||
|
|
||||||
// Transport is the Transport to use for the http client.
|
|
||||||
Transport *http.Transport
|
|
||||||
|
|
||||||
// HttpClient is the client to use. Default will be
|
|
||||||
// used if not provided.
|
|
||||||
HttpClient *http.Client
|
|
||||||
|
|
||||||
// HttpAuth is the auth info to use for http access.
|
|
||||||
HttpAuth *HttpBasicAuth
|
|
||||||
|
|
||||||
// WaitTime limits how long a Watch will block. If not provided,
|
|
||||||
// the agent default values will be used.
|
|
||||||
WaitTime time.Duration
|
|
||||||
|
|
||||||
// Token is used to provide a per-request ACL token
|
|
||||||
// which overrides the agent's default token.
|
|
||||||
Token string
|
|
||||||
|
|
||||||
// TokenFile is a file containing the current token to use for this client.
|
|
||||||
// If provided it is read once at startup and never again.
|
|
||||||
TokenFile string
|
|
||||||
|
|
||||||
TLSConfig TLSConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// TLSConfig is used to generate a TLSClientConfig that's useful for talking to
|
|
||||||
// Consul using TLS.
|
|
||||||
type TLSConfig struct {
|
|
||||||
// Address is the optional address of the Consul server. The port, if any
|
|
||||||
// will be removed from here and this will be set to the ServerName of the
|
|
||||||
// resulting config.
|
|
||||||
Address string
|
|
||||||
|
|
||||||
// CAFile is the optional path to the CA certificate used for Consul
|
|
||||||
// communication, defaults to the system bundle if not specified.
|
|
||||||
CAFile string
|
|
||||||
|
|
||||||
// CAPath is the optional path to a directory of CA certificates to use for
|
|
||||||
// Consul communication, defaults to the system bundle if not specified.
|
|
||||||
CAPath string
|
|
||||||
|
|
||||||
// CertFile is the optional path to the certificate for Consul
|
|
||||||
// communication. If this is set then you need to also set KeyFile.
|
|
||||||
CertFile string
|
|
||||||
|
|
||||||
// KeyFile is the optional path to the private key for Consul communication.
|
|
||||||
// If this is set then you need to also set CertFile.
|
|
||||||
KeyFile string
|
|
||||||
|
|
||||||
// InsecureSkipVerify if set to true will disable TLS host verification.
|
|
||||||
InsecureSkipVerify bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultConfig returns a default configuration for the client. By default this
|
|
||||||
// will pool and reuse idle connections to Consul. If you have a long-lived
|
|
||||||
// client object, this is the desired behavior and should make the most efficient
|
|
||||||
// use of the connections to Consul. If you don't reuse a client object, which
|
|
||||||
// is not recommended, then you may notice idle connections building up over
|
|
||||||
// time. To avoid this, use the DefaultNonPooledConfig() instead.
|
|
||||||
func DefaultConfig() *Config {
|
|
||||||
return defaultConfig(cleanhttp.DefaultPooledTransport)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultNonPooledConfig returns a default configuration for the client which
|
|
||||||
// does not pool connections. This isn't a recommended configuration because it
|
|
||||||
// will reconnect to Consul on every request, but this is useful to avoid the
|
|
||||||
// accumulation of idle connections if you make many client objects during the
|
|
||||||
// lifetime of your application.
|
|
||||||
func DefaultNonPooledConfig() *Config {
|
|
||||||
return defaultConfig(cleanhttp.DefaultTransport)
|
|
||||||
}
|
|
||||||
|
|
||||||
// defaultConfig returns the default configuration for the client, using the
|
|
||||||
// given function to make the transport.
|
|
||||||
func defaultConfig(transportFn func() *http.Transport) *Config {
|
|
||||||
config := &Config{
|
|
||||||
Address: "127.0.0.1:8500",
|
|
||||||
Scheme: "http",
|
|
||||||
Transport: transportFn(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if addr := os.Getenv(HTTPAddrEnvName); addr != "" {
|
|
||||||
config.Address = addr
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokenFile := os.Getenv(HTTPTokenFileEnvName); tokenFile != "" {
|
|
||||||
config.TokenFile = tokenFile
|
|
||||||
}
|
|
||||||
|
|
||||||
if token := os.Getenv(HTTPTokenEnvName); token != "" {
|
|
||||||
config.Token = token
|
|
||||||
}
|
|
||||||
|
|
||||||
if auth := os.Getenv(HTTPAuthEnvName); auth != "" {
|
|
||||||
var username, password string
|
|
||||||
if strings.Contains(auth, ":") {
|
|
||||||
split := strings.SplitN(auth, ":", 2)
|
|
||||||
username = split[0]
|
|
||||||
password = split[1]
|
|
||||||
} else {
|
|
||||||
username = auth
|
|
||||||
}
|
|
||||||
|
|
||||||
config.HttpAuth = &HttpBasicAuth{
|
|
||||||
Username: username,
|
|
||||||
Password: password,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ssl := os.Getenv(HTTPSSLEnvName); ssl != "" {
|
|
||||||
enabled, err := strconv.ParseBool(ssl)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[WARN] client: could not parse %s: %s", HTTPSSLEnvName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if enabled {
|
|
||||||
config.Scheme = "https"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v := os.Getenv(HTTPTLSServerName); v != "" {
|
|
||||||
config.TLSConfig.Address = v
|
|
||||||
}
|
|
||||||
if v := os.Getenv(HTTPCAFile); v != "" {
|
|
||||||
config.TLSConfig.CAFile = v
|
|
||||||
}
|
|
||||||
if v := os.Getenv(HTTPCAPath); v != "" {
|
|
||||||
config.TLSConfig.CAPath = v
|
|
||||||
}
|
|
||||||
if v := os.Getenv(HTTPClientCert); v != "" {
|
|
||||||
config.TLSConfig.CertFile = v
|
|
||||||
}
|
|
||||||
if v := os.Getenv(HTTPClientKey); v != "" {
|
|
||||||
config.TLSConfig.KeyFile = v
|
|
||||||
}
|
|
||||||
if v := os.Getenv(HTTPSSLVerifyEnvName); v != "" {
|
|
||||||
doVerify, err := strconv.ParseBool(v)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[WARN] client: could not parse %s: %s", HTTPSSLVerifyEnvName, err)
|
|
||||||
}
|
|
||||||
if !doVerify {
|
|
||||||
config.TLSConfig.InsecureSkipVerify = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
// TLSConfig is used to generate a TLSClientConfig that's useful for talking to
|
|
||||||
// Consul using TLS.
|
|
||||||
func SetupTLSConfig(tlsConfig *TLSConfig) (*tls.Config, error) {
|
|
||||||
tlsClientConfig := &tls.Config{
|
|
||||||
InsecureSkipVerify: tlsConfig.InsecureSkipVerify,
|
|
||||||
}
|
|
||||||
|
|
||||||
if tlsConfig.Address != "" {
|
|
||||||
server := tlsConfig.Address
|
|
||||||
hasPort := strings.LastIndex(server, ":") > strings.LastIndex(server, "]")
|
|
||||||
if hasPort {
|
|
||||||
var err error
|
|
||||||
server, _, err = net.SplitHostPort(server)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tlsClientConfig.ServerName = server
|
|
||||||
}
|
|
||||||
|
|
||||||
if tlsConfig.CertFile != "" && tlsConfig.KeyFile != "" {
|
|
||||||
tlsCert, err := tls.LoadX509KeyPair(tlsConfig.CertFile, tlsConfig.KeyFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsClientConfig.Certificates = []tls.Certificate{tlsCert}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tlsConfig.CAFile != "" || tlsConfig.CAPath != "" {
|
|
||||||
rootConfig := &rootcerts.Config{
|
|
||||||
CAFile: tlsConfig.CAFile,
|
|
||||||
CAPath: tlsConfig.CAPath,
|
|
||||||
}
|
|
||||||
if err := rootcerts.ConfigureTLS(tlsClientConfig, rootConfig); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tlsClientConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) GenerateEnv() []string {
|
|
||||||
env := make([]string, 0, 10)
|
|
||||||
|
|
||||||
env = append(env,
|
|
||||||
fmt.Sprintf("%s=%s", HTTPAddrEnvName, c.Address),
|
|
||||||
fmt.Sprintf("%s=%s", HTTPTokenEnvName, c.Token),
|
|
||||||
fmt.Sprintf("%s=%s", HTTPTokenFileEnvName, c.TokenFile),
|
|
||||||
fmt.Sprintf("%s=%t", HTTPSSLEnvName, c.Scheme == "https"),
|
|
||||||
fmt.Sprintf("%s=%s", HTTPCAFile, c.TLSConfig.CAFile),
|
|
||||||
fmt.Sprintf("%s=%s", HTTPCAPath, c.TLSConfig.CAPath),
|
|
||||||
fmt.Sprintf("%s=%s", HTTPClientCert, c.TLSConfig.CertFile),
|
|
||||||
fmt.Sprintf("%s=%s", HTTPClientKey, c.TLSConfig.KeyFile),
|
|
||||||
fmt.Sprintf("%s=%s", HTTPTLSServerName, c.TLSConfig.Address),
|
|
||||||
fmt.Sprintf("%s=%t", HTTPSSLVerifyEnvName, !c.TLSConfig.InsecureSkipVerify))
|
|
||||||
|
|
||||||
if c.HttpAuth != nil {
|
|
||||||
env = append(env, fmt.Sprintf("%s=%s:%s", HTTPAuthEnvName, c.HttpAuth.Username, c.HttpAuth.Password))
|
|
||||||
} else {
|
|
||||||
env = append(env, fmt.Sprintf("%s=", HTTPAuthEnvName))
|
|
||||||
}
|
|
||||||
|
|
||||||
return env
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client provides a client to the Consul API
|
|
||||||
type Client struct {
|
|
||||||
config Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient returns a new client
|
|
||||||
func NewClient(config *Config) (*Client, error) {
|
|
||||||
// bootstrap the config
|
|
||||||
defConfig := DefaultConfig()
|
|
||||||
|
|
||||||
if len(config.Address) == 0 {
|
|
||||||
config.Address = defConfig.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(config.Scheme) == 0 {
|
|
||||||
config.Scheme = defConfig.Scheme
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Transport == nil {
|
|
||||||
config.Transport = defConfig.Transport
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.TLSConfig.Address == "" {
|
|
||||||
config.TLSConfig.Address = defConfig.TLSConfig.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.TLSConfig.CAFile == "" {
|
|
||||||
config.TLSConfig.CAFile = defConfig.TLSConfig.CAFile
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.TLSConfig.CAPath == "" {
|
|
||||||
config.TLSConfig.CAPath = defConfig.TLSConfig.CAPath
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.TLSConfig.CertFile == "" {
|
|
||||||
config.TLSConfig.CertFile = defConfig.TLSConfig.CertFile
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.TLSConfig.KeyFile == "" {
|
|
||||||
config.TLSConfig.KeyFile = defConfig.TLSConfig.KeyFile
|
|
||||||
}
|
|
||||||
|
|
||||||
if !config.TLSConfig.InsecureSkipVerify {
|
|
||||||
config.TLSConfig.InsecureSkipVerify = defConfig.TLSConfig.InsecureSkipVerify
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.HttpClient == nil {
|
|
||||||
var err error
|
|
||||||
config.HttpClient, err = NewHttpClient(config.Transport, config.TLSConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.SplitN(config.Address, "://", 2)
|
|
||||||
if len(parts) == 2 {
|
|
||||||
switch parts[0] {
|
|
||||||
case "http":
|
|
||||||
config.Scheme = "http"
|
|
||||||
case "https":
|
|
||||||
config.Scheme = "https"
|
|
||||||
case "unix":
|
|
||||||
trans := cleanhttp.DefaultTransport()
|
|
||||||
trans.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
|
|
||||||
return net.Dial("unix", parts[1])
|
|
||||||
}
|
|
||||||
config.HttpClient = &http.Client{
|
|
||||||
Transport: trans,
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("Unknown protocol scheme: %s", parts[0])
|
|
||||||
}
|
|
||||||
config.Address = parts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the TokenFile is set, always use that, even if a Token is configured.
|
|
||||||
// This is because when TokenFile is set it is read into the Token field.
|
|
||||||
// We want any derived clients to have to re-read the token file.
|
|
||||||
if config.TokenFile != "" {
|
|
||||||
data, err := ioutil.ReadFile(config.TokenFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Error loading token file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if token := strings.TrimSpace(string(data)); token != "" {
|
|
||||||
config.Token = token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if config.Token == "" {
|
|
||||||
config.Token = defConfig.Token
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Client{config: *config}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHttpClient returns an http client configured with the given Transport and TLS
|
|
||||||
// config.
|
|
||||||
func NewHttpClient(transport *http.Transport, tlsConf TLSConfig) (*http.Client, error) {
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: transport,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO (slackpad) - Once we get some run time on the HTTP/2 support we
|
|
||||||
// should turn it on by default if TLS is enabled. We would basically
|
|
||||||
// just need to call http2.ConfigureTransport(transport) here. We also
|
|
||||||
// don't want to introduce another external dependency on
|
|
||||||
// golang.org/x/net/http2 at this time. For a complete recipe for how
|
|
||||||
// to enable HTTP/2 support on a transport suitable for the API client
|
|
||||||
// library see agent/http_test.go:TestHTTPServer_H2.
|
|
||||||
|
|
||||||
if transport.TLSClientConfig == nil {
|
|
||||||
tlsClientConfig, err := SetupTLSConfig(&tlsConf)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
transport.TLSClientConfig = tlsClientConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// request is used to help build up a request
|
|
||||||
type request struct {
|
|
||||||
config *Config
|
|
||||||
method string
|
|
||||||
url *url.URL
|
|
||||||
params url.Values
|
|
||||||
body io.Reader
|
|
||||||
header http.Header
|
|
||||||
obj interface{}
|
|
||||||
ctx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// setQueryOptions is used to annotate the request with
|
|
||||||
// additional query options
|
|
||||||
func (r *request) setQueryOptions(q *QueryOptions) {
|
|
||||||
if q == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if q.Datacenter != "" {
|
|
||||||
r.params.Set("dc", q.Datacenter)
|
|
||||||
}
|
|
||||||
if q.AllowStale {
|
|
||||||
r.params.Set("stale", "")
|
|
||||||
}
|
|
||||||
if q.RequireConsistent {
|
|
||||||
r.params.Set("consistent", "")
|
|
||||||
}
|
|
||||||
if q.WaitIndex != 0 {
|
|
||||||
r.params.Set("index", strconv.FormatUint(q.WaitIndex, 10))
|
|
||||||
}
|
|
||||||
if q.WaitTime != 0 {
|
|
||||||
r.params.Set("wait", durToMsec(q.WaitTime))
|
|
||||||
}
|
|
||||||
if q.WaitHash != "" {
|
|
||||||
r.params.Set("hash", q.WaitHash)
|
|
||||||
}
|
|
||||||
if q.Token != "" {
|
|
||||||
r.header.Set("X-Consul-Token", q.Token)
|
|
||||||
}
|
|
||||||
if q.Near != "" {
|
|
||||||
r.params.Set("near", q.Near)
|
|
||||||
}
|
|
||||||
if q.Filter != "" {
|
|
||||||
r.params.Set("filter", q.Filter)
|
|
||||||
}
|
|
||||||
if len(q.NodeMeta) > 0 {
|
|
||||||
for key, value := range q.NodeMeta {
|
|
||||||
r.params.Add("node-meta", key+":"+value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if q.RelayFactor != 0 {
|
|
||||||
r.params.Set("relay-factor", strconv.Itoa(int(q.RelayFactor)))
|
|
||||||
}
|
|
||||||
if q.Connect {
|
|
||||||
r.params.Set("connect", "true")
|
|
||||||
}
|
|
||||||
if q.UseCache && !q.RequireConsistent {
|
|
||||||
r.params.Set("cached", "")
|
|
||||||
|
|
||||||
cc := []string{}
|
|
||||||
if q.MaxAge > 0 {
|
|
||||||
cc = append(cc, fmt.Sprintf("max-age=%.0f", q.MaxAge.Seconds()))
|
|
||||||
}
|
|
||||||
if q.StaleIfError > 0 {
|
|
||||||
cc = append(cc, fmt.Sprintf("stale-if-error=%.0f", q.StaleIfError.Seconds()))
|
|
||||||
}
|
|
||||||
if len(cc) > 0 {
|
|
||||||
r.header.Set("Cache-Control", strings.Join(cc, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.ctx = q.ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
// durToMsec converts a duration to a millisecond specified string. If the
|
|
||||||
// user selected a positive value that rounds to 0 ms, then we will use 1 ms
|
|
||||||
// so they get a short delay, otherwise Consul will translate the 0 ms into
|
|
||||||
// a huge default delay.
|
|
||||||
func durToMsec(dur time.Duration) string {
|
|
||||||
ms := dur / time.Millisecond
|
|
||||||
if dur > 0 && ms == 0 {
|
|
||||||
ms = 1
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%dms", ms)
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverError is a string we look for to detect 500 errors.
|
|
||||||
const serverError = "Unexpected response code: 500"
|
|
||||||
|
|
||||||
// IsRetryableError returns true for 500 errors from the Consul servers, and
|
|
||||||
// network connection errors. These are usually retryable at a later time.
|
|
||||||
// This applies to reads but NOT to writes. This may return true for errors
|
|
||||||
// on writes that may have still gone through, so do not use this to retry
|
|
||||||
// any write operations.
|
|
||||||
func IsRetryableError(err error) bool {
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := err.(net.Error); ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO (slackpad) - Make a real error type here instead of using
|
|
||||||
// a string check.
|
|
||||||
return strings.Contains(err.Error(), serverError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setWriteOptions is used to annotate the request with
|
|
||||||
// additional write options
|
|
||||||
func (r *request) setWriteOptions(q *WriteOptions) {
|
|
||||||
if q == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if q.Datacenter != "" {
|
|
||||||
r.params.Set("dc", q.Datacenter)
|
|
||||||
}
|
|
||||||
if q.Token != "" {
|
|
||||||
r.header.Set("X-Consul-Token", q.Token)
|
|
||||||
}
|
|
||||||
if q.RelayFactor != 0 {
|
|
||||||
r.params.Set("relay-factor", strconv.Itoa(int(q.RelayFactor)))
|
|
||||||
}
|
|
||||||
r.ctx = q.ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
// toHTTP converts the request to an HTTP request
|
|
||||||
func (r *request) toHTTP() (*http.Request, error) {
|
|
||||||
// Encode the query parameters
|
|
||||||
r.url.RawQuery = r.params.Encode()
|
|
||||||
|
|
||||||
// Check if we should encode the body
|
|
||||||
if r.body == nil && r.obj != nil {
|
|
||||||
b, err := encodeBody(r.obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r.body = b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the HTTP request
|
|
||||||
req, err := http.NewRequest(r.method, r.url.RequestURI(), r.body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.URL.Host = r.url.Host
|
|
||||||
req.URL.Scheme = r.url.Scheme
|
|
||||||
req.Host = r.url.Host
|
|
||||||
req.Header = r.header
|
|
||||||
|
|
||||||
// Setup auth
|
|
||||||
if r.config.HttpAuth != nil {
|
|
||||||
req.SetBasicAuth(r.config.HttpAuth.Username, r.config.HttpAuth.Password)
|
|
||||||
}
|
|
||||||
if r.ctx != nil {
|
|
||||||
return req.WithContext(r.ctx), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// newRequest is used to create a new request
|
|
||||||
func (c *Client) newRequest(method, path string) *request {
|
|
||||||
r := &request{
|
|
||||||
config: &c.config,
|
|
||||||
method: method,
|
|
||||||
url: &url.URL{
|
|
||||||
Scheme: c.config.Scheme,
|
|
||||||
Host: c.config.Address,
|
|
||||||
Path: path,
|
|
||||||
},
|
|
||||||
params: make(map[string][]string),
|
|
||||||
header: make(http.Header),
|
|
||||||
}
|
|
||||||
if c.config.Datacenter != "" {
|
|
||||||
r.params.Set("dc", c.config.Datacenter)
|
|
||||||
}
|
|
||||||
if c.config.WaitTime != 0 {
|
|
||||||
r.params.Set("wait", durToMsec(r.config.WaitTime))
|
|
||||||
}
|
|
||||||
if c.config.Token != "" {
|
|
||||||
r.header.Set("X-Consul-Token", r.config.Token)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// doRequest runs a request with our client
|
|
||||||
func (c *Client) doRequest(r *request) (time.Duration, *http.Response, error) {
|
|
||||||
req, err := r.toHTTP()
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
start := time.Now()
|
|
||||||
resp, err := c.config.HttpClient.Do(req)
|
|
||||||
diff := time.Since(start)
|
|
||||||
return diff, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query is used to do a GET request against an endpoint
|
|
||||||
// and deserialize the response into an interface using
|
|
||||||
// standard Consul conventions.
|
|
||||||
func (c *Client) query(endpoint string, out interface{}, q *QueryOptions) (*QueryMeta, error) {
|
|
||||||
r := c.newRequest("GET", endpoint)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
if err := decodeBody(resp, out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// write is used to do a PUT request against an endpoint
|
|
||||||
// and serialize/deserialized using the standard Consul conventions.
|
|
||||||
func (c *Client) write(endpoint string, in, out interface{}, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := c.newRequest("PUT", endpoint)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = in
|
|
||||||
rtt, resp, err := requireOK(c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
if out != nil {
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else if _, err := ioutil.ReadAll(resp.Body); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseQueryMeta is used to help parse query meta-data
|
|
||||||
//
|
|
||||||
// TODO(rb): bug? the error from this function is never handled
|
|
||||||
func parseQueryMeta(resp *http.Response, q *QueryMeta) error {
|
|
||||||
header := resp.Header
|
|
||||||
|
|
||||||
// Parse the X-Consul-Index (if it's set - hash based blocking queries don't
|
|
||||||
// set this)
|
|
||||||
if indexStr := header.Get("X-Consul-Index"); indexStr != "" {
|
|
||||||
index, err := strconv.ParseUint(indexStr, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to parse X-Consul-Index: %v", err)
|
|
||||||
}
|
|
||||||
q.LastIndex = index
|
|
||||||
}
|
|
||||||
q.LastContentHash = header.Get("X-Consul-ContentHash")
|
|
||||||
|
|
||||||
// Parse the X-Consul-LastContact
|
|
||||||
last, err := strconv.ParseUint(header.Get("X-Consul-LastContact"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to parse X-Consul-LastContact: %v", err)
|
|
||||||
}
|
|
||||||
q.LastContact = time.Duration(last) * time.Millisecond
|
|
||||||
|
|
||||||
// Parse the X-Consul-KnownLeader
|
|
||||||
switch header.Get("X-Consul-KnownLeader") {
|
|
||||||
case "true":
|
|
||||||
q.KnownLeader = true
|
|
||||||
default:
|
|
||||||
q.KnownLeader = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse X-Consul-Translate-Addresses
|
|
||||||
switch header.Get("X-Consul-Translate-Addresses") {
|
|
||||||
case "true":
|
|
||||||
q.AddressTranslationEnabled = true
|
|
||||||
default:
|
|
||||||
q.AddressTranslationEnabled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse Cache info
|
|
||||||
if cacheStr := header.Get("X-Cache"); cacheStr != "" {
|
|
||||||
q.CacheHit = strings.EqualFold(cacheStr, "HIT")
|
|
||||||
}
|
|
||||||
if ageStr := header.Get("Age"); ageStr != "" {
|
|
||||||
age, err := strconv.ParseUint(ageStr, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to parse Age Header: %v", err)
|
|
||||||
}
|
|
||||||
q.CacheAge = time.Duration(age) * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeBody is used to JSON decode a body
|
|
||||||
func decodeBody(resp *http.Response, out interface{}) error {
|
|
||||||
dec := json.NewDecoder(resp.Body)
|
|
||||||
return dec.Decode(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodeBody is used to encode a request body
|
|
||||||
func encodeBody(obj interface{}) (io.Reader, error) {
|
|
||||||
buf := bytes.NewBuffer(nil)
|
|
||||||
enc := json.NewEncoder(buf)
|
|
||||||
if err := enc.Encode(obj); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// requireOK is used to wrap doRequest and check for a 200
|
|
||||||
func requireOK(d time.Duration, resp *http.Response, e error) (time.Duration, *http.Response, error) {
|
|
||||||
if e != nil {
|
|
||||||
if resp != nil {
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
return d, nil, e
|
|
||||||
}
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return d, nil, generateUnexpectedResponseCodeError(resp)
|
|
||||||
}
|
|
||||||
return d, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (req *request) filterQuery(filter string) {
|
|
||||||
if filter == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req.params.Set("filter", filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateUnexpectedResponseCodeError consumes the rest of the body, closes
|
|
||||||
// the body stream and generates an error indicating the status code was
|
|
||||||
// unexpected.
|
|
||||||
func generateUnexpectedResponseCodeError(resp *http.Response) error {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
io.Copy(&buf, resp.Body)
|
|
||||||
resp.Body.Close()
|
|
||||||
return fmt.Errorf("Unexpected response code: %d (%s)", resp.StatusCode, buf.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
func requireNotFoundOrOK(d time.Duration, resp *http.Response, e error) (bool, time.Duration, *http.Response, error) {
|
|
||||||
if e != nil {
|
|
||||||
if resp != nil {
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
return false, d, nil, e
|
|
||||||
}
|
|
||||||
switch resp.StatusCode {
|
|
||||||
case 200:
|
|
||||||
return true, d, resp, nil
|
|
||||||
case 404:
|
|
||||||
return false, d, resp, nil
|
|
||||||
default:
|
|
||||||
return false, d, nil, generateUnexpectedResponseCodeError(resp)
|
|
||||||
}
|
|
||||||
}
|
|
244
vendor/github.com/hashicorp/consul/api/catalog.go
generated
vendored
244
vendor/github.com/hashicorp/consul/api/catalog.go
generated
vendored
@ -1,244 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
type Weights struct {
|
|
||||||
Passing int
|
|
||||||
Warning int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Node struct {
|
|
||||||
ID string
|
|
||||||
Node string
|
|
||||||
Address string
|
|
||||||
Datacenter string
|
|
||||||
TaggedAddresses map[string]string
|
|
||||||
Meta map[string]string
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type CatalogService struct {
|
|
||||||
ID string
|
|
||||||
Node string
|
|
||||||
Address string
|
|
||||||
Datacenter string
|
|
||||||
TaggedAddresses map[string]string
|
|
||||||
NodeMeta map[string]string
|
|
||||||
ServiceID string
|
|
||||||
ServiceName string
|
|
||||||
ServiceAddress string
|
|
||||||
ServiceTags []string
|
|
||||||
ServiceMeta map[string]string
|
|
||||||
ServicePort int
|
|
||||||
ServiceWeights Weights
|
|
||||||
ServiceEnableTagOverride bool
|
|
||||||
// DEPRECATED (ProxyDestination) - remove the next comment!
|
|
||||||
// We forgot to ever add ServiceProxyDestination here so no need to deprecate!
|
|
||||||
ServiceProxy *AgentServiceConnectProxyConfig
|
|
||||||
CreateIndex uint64
|
|
||||||
Checks HealthChecks
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type CatalogNode struct {
|
|
||||||
Node *Node
|
|
||||||
Services map[string]*AgentService
|
|
||||||
}
|
|
||||||
|
|
||||||
type CatalogRegistration struct {
|
|
||||||
ID string
|
|
||||||
Node string
|
|
||||||
Address string
|
|
||||||
TaggedAddresses map[string]string
|
|
||||||
NodeMeta map[string]string
|
|
||||||
Datacenter string
|
|
||||||
Service *AgentService
|
|
||||||
Check *AgentCheck
|
|
||||||
Checks HealthChecks
|
|
||||||
SkipNodeUpdate bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type CatalogDeregistration struct {
|
|
||||||
Node string
|
|
||||||
Address string // Obsolete.
|
|
||||||
Datacenter string
|
|
||||||
ServiceID string
|
|
||||||
CheckID string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Catalog can be used to query the Catalog endpoints
|
|
||||||
type Catalog struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Catalog returns a handle to the catalog endpoints
|
|
||||||
func (c *Client) Catalog() *Catalog {
|
|
||||||
return &Catalog{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Catalog) Register(reg *CatalogRegistration, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := c.c.newRequest("PUT", "/v1/catalog/register")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = reg
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Catalog) Deregister(dereg *CatalogDeregistration, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := c.c.newRequest("PUT", "/v1/catalog/deregister")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = dereg
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Datacenters is used to query for all the known datacenters
|
|
||||||
func (c *Catalog) Datacenters() ([]string, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/catalog/datacenters")
|
|
||||||
_, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out []string
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nodes is used to query all the known nodes
|
|
||||||
func (c *Catalog) Nodes(q *QueryOptions) ([]*Node, *QueryMeta, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/catalog/nodes")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*Node
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Services is used to query for all known services
|
|
||||||
func (c *Catalog) Services(q *QueryOptions) (map[string][]string, *QueryMeta, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/catalog/services")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out map[string][]string
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Service is used to query catalog entries for a given service
|
|
||||||
func (c *Catalog) Service(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
|
|
||||||
var tags []string
|
|
||||||
if tag != "" {
|
|
||||||
tags = []string{tag}
|
|
||||||
}
|
|
||||||
return c.service(service, tags, q, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Supports multiple tags for filtering
|
|
||||||
func (c *Catalog) ServiceMultipleTags(service string, tags []string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
|
|
||||||
return c.service(service, tags, q, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect is used to query catalog entries for a given Connect-enabled service
|
|
||||||
func (c *Catalog) Connect(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
|
|
||||||
var tags []string
|
|
||||||
if tag != "" {
|
|
||||||
tags = []string{tag}
|
|
||||||
}
|
|
||||||
return c.service(service, tags, q, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Supports multiple tags for filtering
|
|
||||||
func (c *Catalog) ConnectMultipleTags(service string, tags []string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) {
|
|
||||||
return c.service(service, tags, q, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Catalog) service(service string, tags []string, q *QueryOptions, connect bool) ([]*CatalogService, *QueryMeta, error) {
|
|
||||||
path := "/v1/catalog/service/" + service
|
|
||||||
if connect {
|
|
||||||
path = "/v1/catalog/connect/" + service
|
|
||||||
}
|
|
||||||
r := c.c.newRequest("GET", path)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
if len(tags) > 0 {
|
|
||||||
for _, tag := range tags {
|
|
||||||
r.params.Add("tag", tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*CatalogService
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node is used to query for service information about a single node
|
|
||||||
func (c *Catalog) Node(node string, q *QueryOptions) (*CatalogNode, *QueryMeta, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/catalog/node/"+node)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out *CatalogNode
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
255
vendor/github.com/hashicorp/consul/api/config_entry.go
generated
vendored
255
vendor/github.com/hashicorp/consul/api/config_entry.go
generated
vendored
@ -1,255 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ServiceDefaults string = "service-defaults"
|
|
||||||
ProxyDefaults string = "proxy-defaults"
|
|
||||||
ProxyConfigGlobal string = "global"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigEntry interface {
|
|
||||||
GetKind() string
|
|
||||||
GetName() string
|
|
||||||
GetCreateIndex() uint64
|
|
||||||
GetModifyIndex() uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceConfigEntry struct {
|
|
||||||
Kind string
|
|
||||||
Name string
|
|
||||||
Protocol string
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServiceConfigEntry) GetKind() string {
|
|
||||||
return s.Kind
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServiceConfigEntry) GetName() string {
|
|
||||||
return s.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServiceConfigEntry) GetCreateIndex() uint64 {
|
|
||||||
return s.CreateIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServiceConfigEntry) GetModifyIndex() uint64 {
|
|
||||||
return s.ModifyIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProxyConfigEntry struct {
|
|
||||||
Kind string
|
|
||||||
Name string
|
|
||||||
Config map[string]interface{}
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ProxyConfigEntry) GetKind() string {
|
|
||||||
return p.Kind
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ProxyConfigEntry) GetName() string {
|
|
||||||
return p.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ProxyConfigEntry) GetCreateIndex() uint64 {
|
|
||||||
return p.CreateIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ProxyConfigEntry) GetModifyIndex() uint64 {
|
|
||||||
return p.ModifyIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
type rawEntryListResponse struct {
|
|
||||||
kind string
|
|
||||||
Entries []map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeConfigEntry(kind, name string) (ConfigEntry, error) {
|
|
||||||
switch kind {
|
|
||||||
case ServiceDefaults:
|
|
||||||
return &ServiceConfigEntry{Name: name}, nil
|
|
||||||
case ProxyDefaults:
|
|
||||||
return &ProxyConfigEntry{Name: name}, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid config entry kind: %s", kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DecodeConfigEntry(raw map[string]interface{}) (ConfigEntry, error) {
|
|
||||||
var entry ConfigEntry
|
|
||||||
|
|
||||||
kindVal, ok := raw["Kind"]
|
|
||||||
if !ok {
|
|
||||||
kindVal, ok = raw["kind"]
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("Payload does not contain a kind/Kind key at the top level")
|
|
||||||
}
|
|
||||||
|
|
||||||
if kindStr, ok := kindVal.(string); ok {
|
|
||||||
newEntry, err := makeConfigEntry(kindStr, "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
entry = newEntry
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("Kind value in payload is not a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
decodeConf := &mapstructure.DecoderConfig{
|
|
||||||
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
|
||||||
Result: &entry,
|
|
||||||
WeaklyTypedInput: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder, err := mapstructure.NewDecoder(decodeConf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry, decoder.Decode(raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DecodeConfigEntryFromJSON(data []byte) (ConfigEntry, error) {
|
|
||||||
var raw map[string]interface{}
|
|
||||||
if err := json.Unmarshal(data, &raw); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return DecodeConfigEntry(raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config can be used to query the Config endpoints
|
|
||||||
type ConfigEntries struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config returns a handle to the Config endpoints
|
|
||||||
func (c *Client) ConfigEntries() *ConfigEntries {
|
|
||||||
return &ConfigEntries{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *ConfigEntries) Get(kind string, name string, q *QueryOptions) (ConfigEntry, *QueryMeta, error) {
|
|
||||||
if kind == "" || name == "" {
|
|
||||||
return nil, nil, fmt.Errorf("Both kind and name parameters must not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
entry, err := makeConfigEntry(kind, name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r := conf.c.newRequest("GET", fmt.Sprintf("/v1/config/%s/%s", kind, name))
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(conf.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
if err := decodeBody(resp, entry); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *ConfigEntries) List(kind string, q *QueryOptions) ([]ConfigEntry, *QueryMeta, error) {
|
|
||||||
if kind == "" {
|
|
||||||
return nil, nil, fmt.Errorf("The kind parameter must not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
r := conf.c.newRequest("GET", fmt.Sprintf("/v1/config/%s", kind))
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(conf.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var raw []map[string]interface{}
|
|
||||||
if err := decodeBody(resp, &raw); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var entries []ConfigEntry
|
|
||||||
for _, rawEntry := range raw {
|
|
||||||
entry, err := DecodeConfigEntry(rawEntry)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
entries = append(entries, entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *ConfigEntries) Set(entry ConfigEntry, w *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
return conf.set(entry, nil, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *ConfigEntries) CAS(entry ConfigEntry, index uint64, w *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
return conf.set(entry, map[string]string{"cas": strconv.FormatUint(index, 10)}, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *ConfigEntries) set(entry ConfigEntry, params map[string]string, w *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
r := conf.c.newRequest("PUT", "/v1/config")
|
|
||||||
r.setWriteOptions(w)
|
|
||||||
for param, value := range params {
|
|
||||||
r.params.Set(param, value)
|
|
||||||
}
|
|
||||||
r.obj = entry
|
|
||||||
rtt, resp, err := requireOK(conf.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if _, err := io.Copy(&buf, resp.Body); err != nil {
|
|
||||||
return false, nil, fmt.Errorf("Failed to read response: %v", err)
|
|
||||||
}
|
|
||||||
res := strings.Contains(buf.String(), "true")
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
return res, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *ConfigEntries) Delete(kind string, name string, w *WriteOptions) (*WriteMeta, error) {
|
|
||||||
if kind == "" || name == "" {
|
|
||||||
return nil, fmt.Errorf("Both kind and name parameters must not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
r := conf.c.newRequest("DELETE", fmt.Sprintf("/v1/config/%s/%s", kind, name))
|
|
||||||
r.setWriteOptions(w)
|
|
||||||
rtt, resp, err := requireOK(conf.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
12
vendor/github.com/hashicorp/consul/api/connect.go
generated
vendored
12
vendor/github.com/hashicorp/consul/api/connect.go
generated
vendored
@ -1,12 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
// Connect can be used to work with endpoints related to Connect, the
|
|
||||||
// feature for securely connecting services within Consul.
|
|
||||||
type Connect struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect returns a handle to the connect-related endpoints
|
|
||||||
func (c *Client) Connect() *Connect {
|
|
||||||
return &Connect{c}
|
|
||||||
}
|
|
174
vendor/github.com/hashicorp/consul/api/connect_ca.go
generated
vendored
174
vendor/github.com/hashicorp/consul/api/connect_ca.go
generated
vendored
@ -1,174 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CAConfig is the structure for the Connect CA configuration.
|
|
||||||
type CAConfig struct {
|
|
||||||
// Provider is the CA provider implementation to use.
|
|
||||||
Provider string
|
|
||||||
|
|
||||||
// Configuration is arbitrary configuration for the provider. This
|
|
||||||
// should only contain primitive values and containers (such as lists
|
|
||||||
// and maps).
|
|
||||||
Config map[string]interface{}
|
|
||||||
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommonCAProviderConfig is the common options available to all CA providers.
|
|
||||||
type CommonCAProviderConfig struct {
|
|
||||||
LeafCertTTL time.Duration
|
|
||||||
SkipValidate bool
|
|
||||||
CSRMaxPerSecond float32
|
|
||||||
CSRMaxConcurrent int
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConsulCAProviderConfig is the config for the built-in Consul CA provider.
|
|
||||||
type ConsulCAProviderConfig struct {
|
|
||||||
CommonCAProviderConfig `mapstructure:",squash"`
|
|
||||||
|
|
||||||
PrivateKey string
|
|
||||||
RootCert string
|
|
||||||
RotationPeriod time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseConsulCAConfig takes a raw config map and returns a parsed
|
|
||||||
// ConsulCAProviderConfig.
|
|
||||||
func ParseConsulCAConfig(raw map[string]interface{}) (*ConsulCAProviderConfig, error) {
|
|
||||||
var config ConsulCAProviderConfig
|
|
||||||
decodeConf := &mapstructure.DecoderConfig{
|
|
||||||
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
|
||||||
Result: &config,
|
|
||||||
WeaklyTypedInput: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder, err := mapstructure.NewDecoder(decodeConf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := decoder.Decode(raw); err != nil {
|
|
||||||
return nil, fmt.Errorf("error decoding config: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CARootList is the structure for the results of listing roots.
|
|
||||||
type CARootList struct {
|
|
||||||
ActiveRootID string
|
|
||||||
TrustDomain string
|
|
||||||
Roots []*CARoot
|
|
||||||
}
|
|
||||||
|
|
||||||
// CARoot represents a root CA certificate that is trusted.
|
|
||||||
type CARoot struct {
|
|
||||||
// ID is a globally unique ID (UUID) representing this CA root.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// Name is a human-friendly name for this CA root. This value is
|
|
||||||
// opaque to Consul and is not used for anything internally.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// RootCertPEM is the PEM-encoded public certificate.
|
|
||||||
RootCertPEM string `json:"RootCert"`
|
|
||||||
|
|
||||||
// Active is true if this is the current active CA. This must only
|
|
||||||
// be true for exactly one CA. For any method that modifies roots in the
|
|
||||||
// state store, tests should be written to verify that multiple roots
|
|
||||||
// cannot be active.
|
|
||||||
Active bool
|
|
||||||
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// LeafCert is a certificate that has been issued by a Connect CA.
|
|
||||||
type LeafCert struct {
|
|
||||||
// SerialNumber is the unique serial number for this certificate.
|
|
||||||
// This is encoded in standard hex separated by :.
|
|
||||||
SerialNumber string
|
|
||||||
|
|
||||||
// CertPEM and PrivateKeyPEM are the PEM-encoded certificate and private
|
|
||||||
// key for that cert, respectively. This should not be stored in the
|
|
||||||
// state store, but is present in the sign API response.
|
|
||||||
CertPEM string `json:",omitempty"`
|
|
||||||
PrivateKeyPEM string `json:",omitempty"`
|
|
||||||
|
|
||||||
// Service is the name of the service for which the cert was issued.
|
|
||||||
// ServiceURI is the cert URI value.
|
|
||||||
Service string
|
|
||||||
ServiceURI string
|
|
||||||
|
|
||||||
// ValidAfter and ValidBefore are the validity periods for the
|
|
||||||
// certificate.
|
|
||||||
ValidAfter time.Time
|
|
||||||
ValidBefore time.Time
|
|
||||||
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// CARoots queries the list of available roots.
|
|
||||||
func (h *Connect) CARoots(q *QueryOptions) (*CARootList, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/connect/ca/roots")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out CARootList
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return &out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CAGetConfig returns the current CA configuration.
|
|
||||||
func (h *Connect) CAGetConfig(q *QueryOptions) (*CAConfig, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/connect/ca/configuration")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out CAConfig
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return &out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CASetConfig sets the current CA configuration.
|
|
||||||
func (h *Connect) CASetConfig(conf *CAConfig, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := h.c.newRequest("PUT", "/v1/connect/ca/configuration")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = conf
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
302
vendor/github.com/hashicorp/consul/api/connect_intention.go
generated
vendored
302
vendor/github.com/hashicorp/consul/api/connect_intention.go
generated
vendored
@ -1,302 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Intention defines an intention for the Connect Service Graph. This defines
|
|
||||||
// the allowed or denied behavior of a connection between two services using
|
|
||||||
// Connect.
|
|
||||||
type Intention struct {
|
|
||||||
// ID is the UUID-based ID for the intention, always generated by Consul.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// Description is a human-friendly description of this intention.
|
|
||||||
// It is opaque to Consul and is only stored and transferred in API
|
|
||||||
// requests.
|
|
||||||
Description string
|
|
||||||
|
|
||||||
// SourceNS, SourceName are the namespace and name, respectively, of
|
|
||||||
// the source service. Either of these may be the wildcard "*", but only
|
|
||||||
// the full value can be a wildcard. Partial wildcards are not allowed.
|
|
||||||
// The source may also be a non-Consul service, as specified by SourceType.
|
|
||||||
//
|
|
||||||
// DestinationNS, DestinationName is the same, but for the destination
|
|
||||||
// service. The same rules apply. The destination is always a Consul
|
|
||||||
// service.
|
|
||||||
SourceNS, SourceName string
|
|
||||||
DestinationNS, DestinationName string
|
|
||||||
|
|
||||||
// SourceType is the type of the value for the source.
|
|
||||||
SourceType IntentionSourceType
|
|
||||||
|
|
||||||
// Action is whether this is a whitelist or blacklist intention.
|
|
||||||
Action IntentionAction
|
|
||||||
|
|
||||||
// DefaultAddr, DefaultPort of the local listening proxy (if any) to
|
|
||||||
// make this connection.
|
|
||||||
DefaultAddr string
|
|
||||||
DefaultPort int
|
|
||||||
|
|
||||||
// Meta is arbitrary metadata associated with the intention. This is
|
|
||||||
// opaque to Consul but is served in API responses.
|
|
||||||
Meta map[string]string
|
|
||||||
|
|
||||||
// Precedence is the order that the intention will be applied, with
|
|
||||||
// larger numbers being applied first. This is a read-only field, on
|
|
||||||
// any intention update it is updated.
|
|
||||||
Precedence int
|
|
||||||
|
|
||||||
// CreatedAt and UpdatedAt keep track of when this record was created
|
|
||||||
// or modified.
|
|
||||||
CreatedAt, UpdatedAt time.Time
|
|
||||||
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns human-friendly output describing ths intention.
|
|
||||||
func (i *Intention) String() string {
|
|
||||||
return fmt.Sprintf("%s => %s (%s)",
|
|
||||||
i.SourceString(),
|
|
||||||
i.DestinationString(),
|
|
||||||
i.Action)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SourceString returns the namespace/name format for the source, or
|
|
||||||
// just "name" if the namespace is the default namespace.
|
|
||||||
func (i *Intention) SourceString() string {
|
|
||||||
return i.partString(i.SourceNS, i.SourceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DestinationString returns the namespace/name format for the source, or
|
|
||||||
// just "name" if the namespace is the default namespace.
|
|
||||||
func (i *Intention) DestinationString() string {
|
|
||||||
return i.partString(i.DestinationNS, i.DestinationName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Intention) partString(ns, n string) string {
|
|
||||||
// For now we omit the default namespace from the output. In the future
|
|
||||||
// we might want to look at this and show this in a multi-namespace world.
|
|
||||||
if ns != "" && ns != IntentionDefaultNamespace {
|
|
||||||
n = ns + "/" + n
|
|
||||||
}
|
|
||||||
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionDefaultNamespace is the default namespace value.
|
|
||||||
const IntentionDefaultNamespace = "default"
|
|
||||||
|
|
||||||
// IntentionAction is the action that the intention represents. This
|
|
||||||
// can be "allow" or "deny" to whitelist or blacklist intentions.
|
|
||||||
type IntentionAction string
|
|
||||||
|
|
||||||
const (
|
|
||||||
IntentionActionAllow IntentionAction = "allow"
|
|
||||||
IntentionActionDeny IntentionAction = "deny"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IntentionSourceType is the type of the source within an intention.
|
|
||||||
type IntentionSourceType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// IntentionSourceConsul is a service within the Consul catalog.
|
|
||||||
IntentionSourceConsul IntentionSourceType = "consul"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IntentionMatch are the arguments for the intention match API.
|
|
||||||
type IntentionMatch struct {
|
|
||||||
By IntentionMatchType
|
|
||||||
Names []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionMatchType is the target for a match request. For example,
|
|
||||||
// matching by source will look for all intentions that match the given
|
|
||||||
// source value.
|
|
||||||
type IntentionMatchType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
IntentionMatchSource IntentionMatchType = "source"
|
|
||||||
IntentionMatchDestination IntentionMatchType = "destination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IntentionCheck are the arguments for the intention check API. For
|
|
||||||
// more documentation see the IntentionCheck function.
|
|
||||||
type IntentionCheck struct {
|
|
||||||
// Source and Destination are the source and destination values to
|
|
||||||
// check. The destination is always a Consul service, but the source
|
|
||||||
// may be other values as defined by the SourceType.
|
|
||||||
Source, Destination string
|
|
||||||
|
|
||||||
// SourceType is the type of the value for the source.
|
|
||||||
SourceType IntentionSourceType
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intentions returns the list of intentions.
|
|
||||||
func (h *Connect) Intentions(q *QueryOptions) ([]*Intention, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/connect/intentions")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*Intention
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionGet retrieves a single intention.
|
|
||||||
func (h *Connect) IntentionGet(id string, q *QueryOptions) (*Intention, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/connect/intentions/"+id)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := h.c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
if resp.StatusCode == 404 {
|
|
||||||
return nil, qm, nil
|
|
||||||
} else if resp.StatusCode != 200 {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
io.Copy(&buf, resp.Body)
|
|
||||||
return nil, nil, fmt.Errorf(
|
|
||||||
"Unexpected response %d: %s", resp.StatusCode, buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
var out Intention
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return &out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionDelete deletes a single intention.
|
|
||||||
func (h *Connect) IntentionDelete(id string, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := h.c.newRequest("DELETE", "/v1/connect/intentions/"+id)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &WriteMeta{}
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
return qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionMatch returns the list of intentions that match a given source
|
|
||||||
// or destination. The returned intentions are ordered by precedence where
|
|
||||||
// result[0] is the highest precedence (if that matches, then that rule overrides
|
|
||||||
// all other rules).
|
|
||||||
//
|
|
||||||
// Matching can be done for multiple names at the same time. The resulting
|
|
||||||
// map is keyed by the given names. Casing is preserved.
|
|
||||||
func (h *Connect) IntentionMatch(args *IntentionMatch, q *QueryOptions) (map[string][]*Intention, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/connect/intentions/match")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
r.params.Set("by", string(args.By))
|
|
||||||
for _, name := range args.Names {
|
|
||||||
r.params.Add("name", name)
|
|
||||||
}
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out map[string][]*Intention
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionCheck returns whether a given source/destination would be allowed
|
|
||||||
// or not given the current set of intentions and the configuration of Consul.
|
|
||||||
func (h *Connect) IntentionCheck(args *IntentionCheck, q *QueryOptions) (bool, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/connect/intentions/check")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
r.params.Set("source", args.Source)
|
|
||||||
r.params.Set("destination", args.Destination)
|
|
||||||
if args.SourceType != "" {
|
|
||||||
r.params.Set("source-type", string(args.SourceType))
|
|
||||||
}
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out struct{ Allowed bool }
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
return out.Allowed, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionCreate will create a new intention. The ID in the given
|
|
||||||
// structure must be empty and a generate ID will be returned on
|
|
||||||
// success.
|
|
||||||
func (c *Connect) IntentionCreate(ixn *Intention, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
r := c.c.newRequest("POST", "/v1/connect/intentions")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = ixn
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out struct{ ID string }
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return out.ID, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntentionUpdate will update an existing intention. The ID in the given
|
|
||||||
// structure must be non-empty.
|
|
||||||
func (c *Connect) IntentionUpdate(ixn *Intention, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := c.c.newRequest("PUT", "/v1/connect/intentions/"+ixn.ID)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = ixn
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
106
vendor/github.com/hashicorp/consul/api/coordinate.go
generated
vendored
106
vendor/github.com/hashicorp/consul/api/coordinate.go
generated
vendored
@ -1,106 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hashicorp/serf/coordinate"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CoordinateEntry represents a node and its associated network coordinate.
|
|
||||||
type CoordinateEntry struct {
|
|
||||||
Node string
|
|
||||||
Segment string
|
|
||||||
Coord *coordinate.Coordinate
|
|
||||||
}
|
|
||||||
|
|
||||||
// CoordinateDatacenterMap has the coordinates for servers in a given datacenter
|
|
||||||
// and area. Network coordinates are only compatible within the same area.
|
|
||||||
type CoordinateDatacenterMap struct {
|
|
||||||
Datacenter string
|
|
||||||
AreaID string
|
|
||||||
Coordinates []CoordinateEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
// Coordinate can be used to query the coordinate endpoints
|
|
||||||
type Coordinate struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Coordinate returns a handle to the coordinate endpoints
|
|
||||||
func (c *Client) Coordinate() *Coordinate {
|
|
||||||
return &Coordinate{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Datacenters is used to return the coordinates of all the servers in the WAN
|
|
||||||
// pool.
|
|
||||||
func (c *Coordinate) Datacenters() ([]*CoordinateDatacenterMap, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/coordinate/datacenters")
|
|
||||||
_, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out []*CoordinateDatacenterMap
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nodes is used to return the coordinates of all the nodes in the LAN pool.
|
|
||||||
func (c *Coordinate) Nodes(q *QueryOptions) ([]*CoordinateEntry, *QueryMeta, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/coordinate/nodes")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*CoordinateEntry
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update inserts or updates the LAN coordinate of a node.
|
|
||||||
func (c *Coordinate) Update(coord *CoordinateEntry, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := c.c.newRequest("PUT", "/v1/coordinate/update")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = coord
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node is used to return the coordinates of a single in the LAN pool.
|
|
||||||
func (c *Coordinate) Node(node string, q *QueryOptions) ([]*CoordinateEntry, *QueryMeta, error) {
|
|
||||||
r := c.c.newRequest("GET", "/v1/coordinate/node/"+node)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*CoordinateEntry
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
106
vendor/github.com/hashicorp/consul/api/debug.go
generated
vendored
106
vendor/github.com/hashicorp/consul/api/debug.go
generated
vendored
@ -1,106 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Debug can be used to query the /debug/pprof endpoints to gather
|
|
||||||
// profiling information about the target agent.Debug
|
|
||||||
//
|
|
||||||
// The agent must have enable_debug set to true for profiling to be enabled
|
|
||||||
// and for these endpoints to function.
|
|
||||||
type Debug struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug returns a handle that exposes the internal debug endpoints.
|
|
||||||
func (c *Client) Debug() *Debug {
|
|
||||||
return &Debug{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Heap returns a pprof heap dump
|
|
||||||
func (d *Debug) Heap() ([]byte, error) {
|
|
||||||
r := d.c.newRequest("GET", "/debug/pprof/heap")
|
|
||||||
_, resp, err := d.c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error making request: %s", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// We return a raw response because we're just passing through a response
|
|
||||||
// from the pprof handlers
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error decoding body: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Profile returns a pprof CPU profile for the specified number of seconds
|
|
||||||
func (d *Debug) Profile(seconds int) ([]byte, error) {
|
|
||||||
r := d.c.newRequest("GET", "/debug/pprof/profile")
|
|
||||||
|
|
||||||
// Capture a profile for the specified number of seconds
|
|
||||||
r.params.Set("seconds", strconv.Itoa(seconds))
|
|
||||||
|
|
||||||
_, resp, err := d.c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error making request: %s", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// We return a raw response because we're just passing through a response
|
|
||||||
// from the pprof handlers
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error decoding body: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trace returns an execution trace
|
|
||||||
func (d *Debug) Trace(seconds int) ([]byte, error) {
|
|
||||||
r := d.c.newRequest("GET", "/debug/pprof/trace")
|
|
||||||
|
|
||||||
// Capture a trace for the specified number of seconds
|
|
||||||
r.params.Set("seconds", strconv.Itoa(seconds))
|
|
||||||
|
|
||||||
_, resp, err := d.c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error making request: %s", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// We return a raw response because we're just passing through a response
|
|
||||||
// from the pprof handlers
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error decoding body: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Goroutine returns a pprof goroutine profile
|
|
||||||
func (d *Debug) Goroutine() ([]byte, error) {
|
|
||||||
r := d.c.newRequest("GET", "/debug/pprof/goroutine")
|
|
||||||
|
|
||||||
_, resp, err := d.c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error making request: %s", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// We return a raw response because we're just passing through a response
|
|
||||||
// from the pprof handlers
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error decoding body: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return body, nil
|
|
||||||
}
|
|
104
vendor/github.com/hashicorp/consul/api/event.go
generated
vendored
104
vendor/github.com/hashicorp/consul/api/event.go
generated
vendored
@ -1,104 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Event can be used to query the Event endpoints
|
|
||||||
type Event struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserEvent represents an event that was fired by the user
|
|
||||||
type UserEvent struct {
|
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
Payload []byte
|
|
||||||
NodeFilter string
|
|
||||||
ServiceFilter string
|
|
||||||
TagFilter string
|
|
||||||
Version int
|
|
||||||
LTime uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event returns a handle to the event endpoints
|
|
||||||
func (c *Client) Event() *Event {
|
|
||||||
return &Event{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fire is used to fire a new user event. Only the Name, Payload and Filters
|
|
||||||
// are respected. This returns the ID or an associated error. Cross DC requests
|
|
||||||
// are supported.
|
|
||||||
func (e *Event) Fire(params *UserEvent, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
r := e.c.newRequest("PUT", "/v1/event/fire/"+params.Name)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
if params.NodeFilter != "" {
|
|
||||||
r.params.Set("node", params.NodeFilter)
|
|
||||||
}
|
|
||||||
if params.ServiceFilter != "" {
|
|
||||||
r.params.Set("service", params.ServiceFilter)
|
|
||||||
}
|
|
||||||
if params.TagFilter != "" {
|
|
||||||
r.params.Set("tag", params.TagFilter)
|
|
||||||
}
|
|
||||||
if params.Payload != nil {
|
|
||||||
r.body = bytes.NewReader(params.Payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
rtt, resp, err := requireOK(e.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
var out UserEvent
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return out.ID, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List is used to get the most recent events an agent has received.
|
|
||||||
// This list can be optionally filtered by the name. This endpoint supports
|
|
||||||
// quasi-blocking queries. The index is not monotonic, nor does it provide provide
|
|
||||||
// LastContact or KnownLeader.
|
|
||||||
func (e *Event) List(name string, q *QueryOptions) ([]*UserEvent, *QueryMeta, error) {
|
|
||||||
r := e.c.newRequest("GET", "/v1/event/list")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
if name != "" {
|
|
||||||
r.params.Set("name", name)
|
|
||||||
}
|
|
||||||
rtt, resp, err := requireOK(e.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var entries []*UserEvent
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return entries, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDToIndex is a bit of a hack. This simulates the index generation to
|
|
||||||
// convert an event ID into a WaitIndex.
|
|
||||||
func (e *Event) IDToIndex(uuid string) uint64 {
|
|
||||||
lower := uuid[0:8] + uuid[9:13] + uuid[14:18]
|
|
||||||
upper := uuid[19:23] + uuid[24:36]
|
|
||||||
lowVal, err := strconv.ParseUint(lower, 16, 64)
|
|
||||||
if err != nil {
|
|
||||||
panic("Failed to convert " + lower)
|
|
||||||
}
|
|
||||||
highVal, err := strconv.ParseUint(upper, 16, 64)
|
|
||||||
if err != nil {
|
|
||||||
panic("Failed to convert " + upper)
|
|
||||||
}
|
|
||||||
return lowVal ^ highVal
|
|
||||||
}
|
|
16
vendor/github.com/hashicorp/consul/api/go.mod
generated
vendored
16
vendor/github.com/hashicorp/consul/api/go.mod
generated
vendored
@ -1,16 +0,0 @@
|
|||||||
module github.com/hashicorp/consul/api
|
|
||||||
|
|
||||||
go 1.12
|
|
||||||
|
|
||||||
replace github.com/hashicorp/consul/sdk => ../sdk
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/hashicorp/consul/sdk v0.1.1
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.0
|
|
||||||
github.com/hashicorp/go-uuid v1.0.1
|
|
||||||
github.com/hashicorp/serf v0.8.2
|
|
||||||
github.com/mitchellh/mapstructure v1.1.2
|
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c
|
|
||||||
github.com/stretchr/testify v1.3.0
|
|
||||||
)
|
|
76
vendor/github.com/hashicorp/consul/api/go.sum
generated
vendored
76
vendor/github.com/hashicorp/consul/api/go.sum
generated
vendored
@ -1,76 +0,0 @@
|
|||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
|
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
|
||||||
github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
|
|
||||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
|
||||||
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
|
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
|
||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
|
||||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
|
||||||
github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M=
|
|
||||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
|
||||||
github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0=
|
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
|
||||||
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
|
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
|
||||||
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
|
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
|
||||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
|
||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=
|
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3 h1:KYQXGkl6vs02hK7pK4eIbw0NpNPedieTSTEiJ//bwGs=
|
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
|
|
||||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5 h1:x6r4Jo0KNzOOzYd8lbcRsqjuqEASK6ob3auvWYM4/8U=
|
|
||||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
330
vendor/github.com/hashicorp/consul/api/health.go
generated
vendored
330
vendor/github.com/hashicorp/consul/api/health.go
generated
vendored
@ -1,330 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// HealthAny is special, and is used as a wild card,
|
|
||||||
// not as a specific state.
|
|
||||||
HealthAny = "any"
|
|
||||||
HealthPassing = "passing"
|
|
||||||
HealthWarning = "warning"
|
|
||||||
HealthCritical = "critical"
|
|
||||||
HealthMaint = "maintenance"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// NodeMaint is the special key set by a node in maintenance mode.
|
|
||||||
NodeMaint = "_node_maintenance"
|
|
||||||
|
|
||||||
// ServiceMaintPrefix is the prefix for a service in maintenance mode.
|
|
||||||
ServiceMaintPrefix = "_service_maintenance:"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HealthCheck is used to represent a single check
|
|
||||||
type HealthCheck struct {
|
|
||||||
Node string
|
|
||||||
CheckID string
|
|
||||||
Name string
|
|
||||||
Status string
|
|
||||||
Notes string
|
|
||||||
Output string
|
|
||||||
ServiceID string
|
|
||||||
ServiceName string
|
|
||||||
ServiceTags []string
|
|
||||||
|
|
||||||
Definition HealthCheckDefinition
|
|
||||||
|
|
||||||
CreateIndex uint64
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// HealthCheckDefinition is used to store the details about
|
|
||||||
// a health check's execution.
|
|
||||||
type HealthCheckDefinition struct {
|
|
||||||
HTTP string
|
|
||||||
Header map[string][]string
|
|
||||||
Method string
|
|
||||||
TLSSkipVerify bool
|
|
||||||
TCP string
|
|
||||||
IntervalDuration time.Duration `json:"-"`
|
|
||||||
TimeoutDuration time.Duration `json:"-"`
|
|
||||||
DeregisterCriticalServiceAfterDuration time.Duration `json:"-"`
|
|
||||||
|
|
||||||
// DEPRECATED in Consul 1.4.1. Use the above time.Duration fields instead.
|
|
||||||
Interval ReadableDuration
|
|
||||||
Timeout ReadableDuration
|
|
||||||
DeregisterCriticalServiceAfter ReadableDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *HealthCheckDefinition) MarshalJSON() ([]byte, error) {
|
|
||||||
type Alias HealthCheckDefinition
|
|
||||||
out := &struct {
|
|
||||||
Interval string
|
|
||||||
Timeout string
|
|
||||||
DeregisterCriticalServiceAfter string
|
|
||||||
*Alias
|
|
||||||
}{
|
|
||||||
Interval: d.Interval.String(),
|
|
||||||
Timeout: d.Timeout.String(),
|
|
||||||
DeregisterCriticalServiceAfter: d.DeregisterCriticalServiceAfter.String(),
|
|
||||||
Alias: (*Alias)(d),
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.IntervalDuration != 0 {
|
|
||||||
out.Interval = d.IntervalDuration.String()
|
|
||||||
} else if d.Interval != 0 {
|
|
||||||
out.Interval = d.Interval.String()
|
|
||||||
}
|
|
||||||
if d.TimeoutDuration != 0 {
|
|
||||||
out.Timeout = d.TimeoutDuration.String()
|
|
||||||
} else if d.Timeout != 0 {
|
|
||||||
out.Timeout = d.Timeout.String()
|
|
||||||
}
|
|
||||||
if d.DeregisterCriticalServiceAfterDuration != 0 {
|
|
||||||
out.DeregisterCriticalServiceAfter = d.DeregisterCriticalServiceAfterDuration.String()
|
|
||||||
} else if d.DeregisterCriticalServiceAfter != 0 {
|
|
||||||
out.DeregisterCriticalServiceAfter = d.DeregisterCriticalServiceAfter.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *HealthCheckDefinition) UnmarshalJSON(data []byte) error {
|
|
||||||
type Alias HealthCheckDefinition
|
|
||||||
aux := &struct {
|
|
||||||
Interval string
|
|
||||||
Timeout string
|
|
||||||
DeregisterCriticalServiceAfter string
|
|
||||||
*Alias
|
|
||||||
}{
|
|
||||||
Alias: (*Alias)(d),
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &aux); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the values into both the time.Duration and old ReadableDuration fields.
|
|
||||||
var err error
|
|
||||||
if aux.Interval != "" {
|
|
||||||
if d.IntervalDuration, err = time.ParseDuration(aux.Interval); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.Interval = ReadableDuration(d.IntervalDuration)
|
|
||||||
}
|
|
||||||
if aux.Timeout != "" {
|
|
||||||
if d.TimeoutDuration, err = time.ParseDuration(aux.Timeout); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.Timeout = ReadableDuration(d.TimeoutDuration)
|
|
||||||
}
|
|
||||||
if aux.DeregisterCriticalServiceAfter != "" {
|
|
||||||
if d.DeregisterCriticalServiceAfterDuration, err = time.ParseDuration(aux.DeregisterCriticalServiceAfter); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.DeregisterCriticalServiceAfter = ReadableDuration(d.DeregisterCriticalServiceAfterDuration)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HealthChecks is a collection of HealthCheck structs.
|
|
||||||
type HealthChecks []*HealthCheck
|
|
||||||
|
|
||||||
// AggregatedStatus returns the "best" status for the list of health checks.
|
|
||||||
// Because a given entry may have many service and node-level health checks
|
|
||||||
// attached, this function determines the best representative of the status as
|
|
||||||
// as single string using the following heuristic:
|
|
||||||
//
|
|
||||||
// maintenance > critical > warning > passing
|
|
||||||
//
|
|
||||||
func (c HealthChecks) AggregatedStatus() string {
|
|
||||||
var passing, warning, critical, maintenance bool
|
|
||||||
for _, check := range c {
|
|
||||||
id := string(check.CheckID)
|
|
||||||
if id == NodeMaint || strings.HasPrefix(id, ServiceMaintPrefix) {
|
|
||||||
maintenance = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch check.Status {
|
|
||||||
case HealthPassing:
|
|
||||||
passing = true
|
|
||||||
case HealthWarning:
|
|
||||||
warning = true
|
|
||||||
case HealthCritical:
|
|
||||||
critical = true
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case maintenance:
|
|
||||||
return HealthMaint
|
|
||||||
case critical:
|
|
||||||
return HealthCritical
|
|
||||||
case warning:
|
|
||||||
return HealthWarning
|
|
||||||
case passing:
|
|
||||||
return HealthPassing
|
|
||||||
default:
|
|
||||||
return HealthPassing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceEntry is used for the health service endpoint
|
|
||||||
type ServiceEntry struct {
|
|
||||||
Node *Node
|
|
||||||
Service *AgentService
|
|
||||||
Checks HealthChecks
|
|
||||||
}
|
|
||||||
|
|
||||||
// Health can be used to query the Health endpoints
|
|
||||||
type Health struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Health returns a handle to the health endpoints
|
|
||||||
func (c *Client) Health() *Health {
|
|
||||||
return &Health{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node is used to query for checks belonging to a given node
|
|
||||||
func (h *Health) Node(node string, q *QueryOptions) (HealthChecks, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/health/node/"+node)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out HealthChecks
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks is used to return the checks associated with a service
|
|
||||||
func (h *Health) Checks(service string, q *QueryOptions) (HealthChecks, *QueryMeta, error) {
|
|
||||||
r := h.c.newRequest("GET", "/v1/health/checks/"+service)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out HealthChecks
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Service is used to query health information along with service info
|
|
||||||
// for a given service. It can optionally do server-side filtering on a tag
|
|
||||||
// or nodes with passing health checks only.
|
|
||||||
func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
|
|
||||||
var tags []string
|
|
||||||
if tag != "" {
|
|
||||||
tags = []string{tag}
|
|
||||||
}
|
|
||||||
return h.service(service, tags, passingOnly, q, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Health) ServiceMultipleTags(service string, tags []string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
|
|
||||||
return h.service(service, tags, passingOnly, q, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect is equivalent to Service except that it will only return services
|
|
||||||
// which are Connect-enabled and will returns the connection address for Connect
|
|
||||||
// client's to use which may be a proxy in front of the named service. If
|
|
||||||
// passingOnly is true only instances where both the service and any proxy are
|
|
||||||
// healthy will be returned.
|
|
||||||
func (h *Health) Connect(service, tag string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
|
|
||||||
var tags []string
|
|
||||||
if tag != "" {
|
|
||||||
tags = []string{tag}
|
|
||||||
}
|
|
||||||
return h.service(service, tags, passingOnly, q, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Health) ConnectMultipleTags(service string, tags []string, passingOnly bool, q *QueryOptions) ([]*ServiceEntry, *QueryMeta, error) {
|
|
||||||
return h.service(service, tags, passingOnly, q, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Health) service(service string, tags []string, passingOnly bool, q *QueryOptions, connect bool) ([]*ServiceEntry, *QueryMeta, error) {
|
|
||||||
path := "/v1/health/service/" + service
|
|
||||||
if connect {
|
|
||||||
path = "/v1/health/connect/" + service
|
|
||||||
}
|
|
||||||
r := h.c.newRequest("GET", path)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
if len(tags) > 0 {
|
|
||||||
for _, tag := range tags {
|
|
||||||
r.params.Add("tag", tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if passingOnly {
|
|
||||||
r.params.Set(HealthPassing, "1")
|
|
||||||
}
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*ServiceEntry
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// State is used to retrieve all the checks in a given state.
|
|
||||||
// The wildcard "any" state can also be used for all checks.
|
|
||||||
func (h *Health) State(state string, q *QueryOptions) (HealthChecks, *QueryMeta, error) {
|
|
||||||
switch state {
|
|
||||||
case HealthAny:
|
|
||||||
case HealthWarning:
|
|
||||||
case HealthCritical:
|
|
||||||
case HealthPassing:
|
|
||||||
default:
|
|
||||||
return nil, nil, fmt.Errorf("Unsupported state: %v", state)
|
|
||||||
}
|
|
||||||
r := h.c.newRequest("GET", "/v1/health/state/"+state)
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
rtt, resp, err := requireOK(h.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out HealthChecks
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
286
vendor/github.com/hashicorp/consul/api/kv.go
generated
vendored
286
vendor/github.com/hashicorp/consul/api/kv.go
generated
vendored
@ -1,286 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// KVPair is used to represent a single K/V entry
|
|
||||||
type KVPair struct {
|
|
||||||
// Key is the name of the key. It is also part of the URL path when accessed
|
|
||||||
// via the API.
|
|
||||||
Key string
|
|
||||||
|
|
||||||
// CreateIndex holds the index corresponding the creation of this KVPair. This
|
|
||||||
// is a read-only field.
|
|
||||||
CreateIndex uint64
|
|
||||||
|
|
||||||
// ModifyIndex is used for the Check-And-Set operations and can also be fed
|
|
||||||
// back into the WaitIndex of the QueryOptions in order to perform blocking
|
|
||||||
// queries.
|
|
||||||
ModifyIndex uint64
|
|
||||||
|
|
||||||
// LockIndex holds the index corresponding to a lock on this key, if any. This
|
|
||||||
// is a read-only field.
|
|
||||||
LockIndex uint64
|
|
||||||
|
|
||||||
// Flags are any user-defined flags on the key. It is up to the implementer
|
|
||||||
// to check these values, since Consul does not treat them specially.
|
|
||||||
Flags uint64
|
|
||||||
|
|
||||||
// Value is the value for the key. This can be any value, but it will be
|
|
||||||
// base64 encoded upon transport.
|
|
||||||
Value []byte
|
|
||||||
|
|
||||||
// Session is a string representing the ID of the session. Any other
|
|
||||||
// interactions with this key over the same session must specify the same
|
|
||||||
// session ID.
|
|
||||||
Session string
|
|
||||||
}
|
|
||||||
|
|
||||||
// KVPairs is a list of KVPair objects
|
|
||||||
type KVPairs []*KVPair
|
|
||||||
|
|
||||||
// KV is used to manipulate the K/V API
|
|
||||||
type KV struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// KV is used to return a handle to the K/V apis
|
|
||||||
func (c *Client) KV() *KV {
|
|
||||||
return &KV{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get is used to lookup a single key. The returned pointer
|
|
||||||
// to the KVPair will be nil if the key does not exist.
|
|
||||||
func (k *KV) Get(key string, q *QueryOptions) (*KVPair, *QueryMeta, error) {
|
|
||||||
resp, qm, err := k.getInternal(key, nil, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if resp == nil {
|
|
||||||
return nil, qm, nil
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var entries []*KVPair
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if len(entries) > 0 {
|
|
||||||
return entries[0], qm, nil
|
|
||||||
}
|
|
||||||
return nil, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List is used to lookup all keys under a prefix
|
|
||||||
func (k *KV) List(prefix string, q *QueryOptions) (KVPairs, *QueryMeta, error) {
|
|
||||||
resp, qm, err := k.getInternal(prefix, map[string]string{"recurse": ""}, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if resp == nil {
|
|
||||||
return nil, qm, nil
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var entries []*KVPair
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return entries, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keys is used to list all the keys under a prefix. Optionally,
|
|
||||||
// a separator can be used to limit the responses.
|
|
||||||
func (k *KV) Keys(prefix, separator string, q *QueryOptions) ([]string, *QueryMeta, error) {
|
|
||||||
params := map[string]string{"keys": ""}
|
|
||||||
if separator != "" {
|
|
||||||
params["separator"] = separator
|
|
||||||
}
|
|
||||||
resp, qm, err := k.getInternal(prefix, params, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if resp == nil {
|
|
||||||
return nil, qm, nil
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var entries []string
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return entries, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KV) getInternal(key string, params map[string]string, q *QueryOptions) (*http.Response, *QueryMeta, error) {
|
|
||||||
r := k.c.newRequest("GET", "/v1/kv/"+strings.TrimPrefix(key, "/"))
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
for param, val := range params {
|
|
||||||
r.params.Set(param, val)
|
|
||||||
}
|
|
||||||
rtt, resp, err := k.c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
if resp.StatusCode == 404 {
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil, qm, nil
|
|
||||||
} else if resp.StatusCode != 200 {
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil, nil, fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
return resp, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put is used to write a new value. Only the
|
|
||||||
// Key, Flags and Value is respected.
|
|
||||||
func (k *KV) Put(p *KVPair, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
params := make(map[string]string, 1)
|
|
||||||
if p.Flags != 0 {
|
|
||||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
|
||||||
}
|
|
||||||
_, wm, err := k.put(p.Key, params, p.Value, q)
|
|
||||||
return wm, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CAS is used for a Check-And-Set operation. The Key,
|
|
||||||
// ModifyIndex, Flags and Value are respected. Returns true
|
|
||||||
// on success or false on failures.
|
|
||||||
func (k *KV) CAS(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
params := make(map[string]string, 2)
|
|
||||||
if p.Flags != 0 {
|
|
||||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
|
||||||
}
|
|
||||||
params["cas"] = strconv.FormatUint(p.ModifyIndex, 10)
|
|
||||||
return k.put(p.Key, params, p.Value, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Acquire is used for a lock acquisition operation. The Key,
|
|
||||||
// Flags, Value and Session are respected. Returns true
|
|
||||||
// on success or false on failures.
|
|
||||||
func (k *KV) Acquire(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
params := make(map[string]string, 2)
|
|
||||||
if p.Flags != 0 {
|
|
||||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
|
||||||
}
|
|
||||||
params["acquire"] = p.Session
|
|
||||||
return k.put(p.Key, params, p.Value, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release is used for a lock release operation. The Key,
|
|
||||||
// Flags, Value and Session are respected. Returns true
|
|
||||||
// on success or false on failures.
|
|
||||||
func (k *KV) Release(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
params := make(map[string]string, 2)
|
|
||||||
if p.Flags != 0 {
|
|
||||||
params["flags"] = strconv.FormatUint(p.Flags, 10)
|
|
||||||
}
|
|
||||||
params["release"] = p.Session
|
|
||||||
return k.put(p.Key, params, p.Value, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KV) put(key string, params map[string]string, body []byte, q *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
if len(key) > 0 && key[0] == '/' {
|
|
||||||
return false, nil, fmt.Errorf("Invalid key. Key must not begin with a '/': %s", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
r := k.c.newRequest("PUT", "/v1/kv/"+key)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
for param, val := range params {
|
|
||||||
r.params.Set(param, val)
|
|
||||||
}
|
|
||||||
r.body = bytes.NewReader(body)
|
|
||||||
rtt, resp, err := requireOK(k.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &WriteMeta{}
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if _, err := io.Copy(&buf, resp.Body); err != nil {
|
|
||||||
return false, nil, fmt.Errorf("Failed to read response: %v", err)
|
|
||||||
}
|
|
||||||
res := strings.Contains(buf.String(), "true")
|
|
||||||
return res, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete is used to delete a single key
|
|
||||||
func (k *KV) Delete(key string, w *WriteOptions) (*WriteMeta, error) {
|
|
||||||
_, qm, err := k.deleteInternal(key, nil, w)
|
|
||||||
return qm, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteCAS is used for a Delete Check-And-Set operation. The Key
|
|
||||||
// and ModifyIndex are respected. Returns true on success or false on failures.
|
|
||||||
func (k *KV) DeleteCAS(p *KVPair, q *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
params := map[string]string{
|
|
||||||
"cas": strconv.FormatUint(p.ModifyIndex, 10),
|
|
||||||
}
|
|
||||||
return k.deleteInternal(p.Key, params, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteTree is used to delete all keys under a prefix
|
|
||||||
func (k *KV) DeleteTree(prefix string, w *WriteOptions) (*WriteMeta, error) {
|
|
||||||
_, qm, err := k.deleteInternal(prefix, map[string]string{"recurse": ""}, w)
|
|
||||||
return qm, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *KV) deleteInternal(key string, params map[string]string, q *WriteOptions) (bool, *WriteMeta, error) {
|
|
||||||
r := k.c.newRequest("DELETE", "/v1/kv/"+strings.TrimPrefix(key, "/"))
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
for param, val := range params {
|
|
||||||
r.params.Set(param, val)
|
|
||||||
}
|
|
||||||
rtt, resp, err := requireOK(k.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &WriteMeta{}
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if _, err := io.Copy(&buf, resp.Body); err != nil {
|
|
||||||
return false, nil, fmt.Errorf("Failed to read response: %v", err)
|
|
||||||
}
|
|
||||||
res := strings.Contains(buf.String(), "true")
|
|
||||||
return res, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Txn function has been deprecated from the KV object; please see the Txn
|
|
||||||
// object for more information about Transactions.
|
|
||||||
func (k *KV) Txn(txn KVTxnOps, q *QueryOptions) (bool, *KVTxnResponse, *QueryMeta, error) {
|
|
||||||
var ops TxnOps
|
|
||||||
for _, op := range txn {
|
|
||||||
ops = append(ops, &TxnOp{KV: op})
|
|
||||||
}
|
|
||||||
|
|
||||||
respOk, txnResp, qm, err := k.c.txn(ops, q)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert from the internal format.
|
|
||||||
kvResp := KVTxnResponse{
|
|
||||||
Errors: txnResp.Errors,
|
|
||||||
}
|
|
||||||
for _, result := range txnResp.Results {
|
|
||||||
kvResp.Results = append(kvResp.Results, result.KV)
|
|
||||||
}
|
|
||||||
return respOk, &kvResp, qm, nil
|
|
||||||
}
|
|
386
vendor/github.com/hashicorp/consul/api/lock.go
generated
vendored
386
vendor/github.com/hashicorp/consul/api/lock.go
generated
vendored
@ -1,386 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultLockSessionName is the Session Name we assign if none is provided
|
|
||||||
DefaultLockSessionName = "Consul API Lock"
|
|
||||||
|
|
||||||
// DefaultLockSessionTTL is the default session TTL if no Session is provided
|
|
||||||
// when creating a new Lock. This is used because we do not have another
|
|
||||||
// other check to depend upon.
|
|
||||||
DefaultLockSessionTTL = "15s"
|
|
||||||
|
|
||||||
// DefaultLockWaitTime is how long we block for at a time to check if lock
|
|
||||||
// acquisition is possible. This affects the minimum time it takes to cancel
|
|
||||||
// a Lock acquisition.
|
|
||||||
DefaultLockWaitTime = 15 * time.Second
|
|
||||||
|
|
||||||
// DefaultLockRetryTime is how long we wait after a failed lock acquisition
|
|
||||||
// before attempting to do the lock again. This is so that once a lock-delay
|
|
||||||
// is in effect, we do not hot loop retrying the acquisition.
|
|
||||||
DefaultLockRetryTime = 5 * time.Second
|
|
||||||
|
|
||||||
// DefaultMonitorRetryTime is how long we wait after a failed monitor check
|
|
||||||
// of a lock (500 response code). This allows the monitor to ride out brief
|
|
||||||
// periods of unavailability, subject to the MonitorRetries setting in the
|
|
||||||
// lock options which is by default set to 0, disabling this feature. This
|
|
||||||
// affects locks and semaphores.
|
|
||||||
DefaultMonitorRetryTime = 2 * time.Second
|
|
||||||
|
|
||||||
// LockFlagValue is a magic flag we set to indicate a key
|
|
||||||
// is being used for a lock. It is used to detect a potential
|
|
||||||
// conflict with a semaphore.
|
|
||||||
LockFlagValue = 0x2ddccbc058a50c18
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrLockHeld is returned if we attempt to double lock
|
|
||||||
ErrLockHeld = fmt.Errorf("Lock already held")
|
|
||||||
|
|
||||||
// ErrLockNotHeld is returned if we attempt to unlock a lock
|
|
||||||
// that we do not hold.
|
|
||||||
ErrLockNotHeld = fmt.Errorf("Lock not held")
|
|
||||||
|
|
||||||
// ErrLockInUse is returned if we attempt to destroy a lock
|
|
||||||
// that is in use.
|
|
||||||
ErrLockInUse = fmt.Errorf("Lock in use")
|
|
||||||
|
|
||||||
// ErrLockConflict is returned if the flags on a key
|
|
||||||
// used for a lock do not match expectation
|
|
||||||
ErrLockConflict = fmt.Errorf("Existing key does not match lock use")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Lock is used to implement client-side leader election. It is follows the
|
|
||||||
// algorithm as described here: https://www.consul.io/docs/guides/leader-election.html.
|
|
||||||
type Lock struct {
|
|
||||||
c *Client
|
|
||||||
opts *LockOptions
|
|
||||||
|
|
||||||
isHeld bool
|
|
||||||
sessionRenew chan struct{}
|
|
||||||
lockSession string
|
|
||||||
l sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// LockOptions is used to parameterize the Lock behavior.
|
|
||||||
type LockOptions struct {
|
|
||||||
Key string // Must be set and have write permissions
|
|
||||||
Value []byte // Optional, value to associate with the lock
|
|
||||||
Session string // Optional, created if not specified
|
|
||||||
SessionOpts *SessionEntry // Optional, options to use when creating a session
|
|
||||||
SessionName string // Optional, defaults to DefaultLockSessionName (ignored if SessionOpts is given)
|
|
||||||
SessionTTL string // Optional, defaults to DefaultLockSessionTTL (ignored if SessionOpts is given)
|
|
||||||
MonitorRetries int // Optional, defaults to 0 which means no retries
|
|
||||||
MonitorRetryTime time.Duration // Optional, defaults to DefaultMonitorRetryTime
|
|
||||||
LockWaitTime time.Duration // Optional, defaults to DefaultLockWaitTime
|
|
||||||
LockTryOnce bool // Optional, defaults to false which means try forever
|
|
||||||
}
|
|
||||||
|
|
||||||
// LockKey returns a handle to a lock struct which can be used
|
|
||||||
// to acquire and release the mutex. The key used must have
|
|
||||||
// write permissions.
|
|
||||||
func (c *Client) LockKey(key string) (*Lock, error) {
|
|
||||||
opts := &LockOptions{
|
|
||||||
Key: key,
|
|
||||||
}
|
|
||||||
return c.LockOpts(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LockOpts returns a handle to a lock struct which can be used
|
|
||||||
// to acquire and release the mutex. The key used must have
|
|
||||||
// write permissions.
|
|
||||||
func (c *Client) LockOpts(opts *LockOptions) (*Lock, error) {
|
|
||||||
if opts.Key == "" {
|
|
||||||
return nil, fmt.Errorf("missing key")
|
|
||||||
}
|
|
||||||
if opts.SessionName == "" {
|
|
||||||
opts.SessionName = DefaultLockSessionName
|
|
||||||
}
|
|
||||||
if opts.SessionTTL == "" {
|
|
||||||
opts.SessionTTL = DefaultLockSessionTTL
|
|
||||||
} else {
|
|
||||||
if _, err := time.ParseDuration(opts.SessionTTL); err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid SessionTTL: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if opts.MonitorRetryTime == 0 {
|
|
||||||
opts.MonitorRetryTime = DefaultMonitorRetryTime
|
|
||||||
}
|
|
||||||
if opts.LockWaitTime == 0 {
|
|
||||||
opts.LockWaitTime = DefaultLockWaitTime
|
|
||||||
}
|
|
||||||
l := &Lock{
|
|
||||||
c: c,
|
|
||||||
opts: opts,
|
|
||||||
}
|
|
||||||
return l, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock attempts to acquire the lock and blocks while doing so.
|
|
||||||
// Providing a non-nil stopCh can be used to abort the lock attempt.
|
|
||||||
// Returns a channel that is closed if our lock is lost or an error.
|
|
||||||
// This channel could be closed at any time due to session invalidation,
|
|
||||||
// communication errors, operator intervention, etc. It is NOT safe to
|
|
||||||
// assume that the lock is held until Unlock() unless the Session is specifically
|
|
||||||
// created without any associated health checks. By default Consul sessions
|
|
||||||
// prefer liveness over safety and an application must be able to handle
|
|
||||||
// the lock being lost.
|
|
||||||
func (l *Lock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
|
|
||||||
// Hold the lock as we try to acquire
|
|
||||||
l.l.Lock()
|
|
||||||
defer l.l.Unlock()
|
|
||||||
|
|
||||||
// Check if we already hold the lock
|
|
||||||
if l.isHeld {
|
|
||||||
return nil, ErrLockHeld
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we need to create a session first
|
|
||||||
l.lockSession = l.opts.Session
|
|
||||||
if l.lockSession == "" {
|
|
||||||
s, err := l.createSession()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create session: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.sessionRenew = make(chan struct{})
|
|
||||||
l.lockSession = s
|
|
||||||
session := l.c.Session()
|
|
||||||
go session.RenewPeriodic(l.opts.SessionTTL, s, nil, l.sessionRenew)
|
|
||||||
|
|
||||||
// If we fail to acquire the lock, cleanup the session
|
|
||||||
defer func() {
|
|
||||||
if !l.isHeld {
|
|
||||||
close(l.sessionRenew)
|
|
||||||
l.sessionRenew = nil
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the query options
|
|
||||||
kv := l.c.KV()
|
|
||||||
qOpts := &QueryOptions{
|
|
||||||
WaitTime: l.opts.LockWaitTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
attempts := 0
|
|
||||||
WAIT:
|
|
||||||
// Check if we should quit
|
|
||||||
select {
|
|
||||||
case <-stopCh:
|
|
||||||
return nil, nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the one-shot mode.
|
|
||||||
if l.opts.LockTryOnce && attempts > 0 {
|
|
||||||
elapsed := time.Since(start)
|
|
||||||
if elapsed > l.opts.LockWaitTime {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query wait time should not exceed the lock wait time
|
|
||||||
qOpts.WaitTime = l.opts.LockWaitTime - elapsed
|
|
||||||
}
|
|
||||||
attempts++
|
|
||||||
|
|
||||||
// Look for an existing lock, blocking until not taken
|
|
||||||
pair, meta, err := kv.Get(l.opts.Key, qOpts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read lock: %v", err)
|
|
||||||
}
|
|
||||||
if pair != nil && pair.Flags != LockFlagValue {
|
|
||||||
return nil, ErrLockConflict
|
|
||||||
}
|
|
||||||
locked := false
|
|
||||||
if pair != nil && pair.Session == l.lockSession {
|
|
||||||
goto HELD
|
|
||||||
}
|
|
||||||
if pair != nil && pair.Session != "" {
|
|
||||||
qOpts.WaitIndex = meta.LastIndex
|
|
||||||
goto WAIT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to acquire the lock
|
|
||||||
pair = l.lockEntry(l.lockSession)
|
|
||||||
locked, _, err = kv.Acquire(pair, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to acquire lock: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the case of not getting the lock
|
|
||||||
if !locked {
|
|
||||||
// Determine why the lock failed
|
|
||||||
qOpts.WaitIndex = 0
|
|
||||||
pair, meta, err = kv.Get(l.opts.Key, qOpts)
|
|
||||||
if pair != nil && pair.Session != "" {
|
|
||||||
//If the session is not null, this means that a wait can safely happen
|
|
||||||
//using a long poll
|
|
||||||
qOpts.WaitIndex = meta.LastIndex
|
|
||||||
goto WAIT
|
|
||||||
} else {
|
|
||||||
// If the session is empty and the lock failed to acquire, then it means
|
|
||||||
// a lock-delay is in effect and a timed wait must be used
|
|
||||||
select {
|
|
||||||
case <-time.After(DefaultLockRetryTime):
|
|
||||||
goto WAIT
|
|
||||||
case <-stopCh:
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HELD:
|
|
||||||
// Watch to ensure we maintain leadership
|
|
||||||
leaderCh := make(chan struct{})
|
|
||||||
go l.monitorLock(l.lockSession, leaderCh)
|
|
||||||
|
|
||||||
// Set that we own the lock
|
|
||||||
l.isHeld = true
|
|
||||||
|
|
||||||
// Locked! All done
|
|
||||||
return leaderCh, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlock released the lock. It is an error to call this
|
|
||||||
// if the lock is not currently held.
|
|
||||||
func (l *Lock) Unlock() error {
|
|
||||||
// Hold the lock as we try to release
|
|
||||||
l.l.Lock()
|
|
||||||
defer l.l.Unlock()
|
|
||||||
|
|
||||||
// Ensure the lock is actually held
|
|
||||||
if !l.isHeld {
|
|
||||||
return ErrLockNotHeld
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set that we no longer own the lock
|
|
||||||
l.isHeld = false
|
|
||||||
|
|
||||||
// Stop the session renew
|
|
||||||
if l.sessionRenew != nil {
|
|
||||||
defer func() {
|
|
||||||
close(l.sessionRenew)
|
|
||||||
l.sessionRenew = nil
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the lock entry, and clear the lock session
|
|
||||||
lockEnt := l.lockEntry(l.lockSession)
|
|
||||||
l.lockSession = ""
|
|
||||||
|
|
||||||
// Release the lock explicitly
|
|
||||||
kv := l.c.KV()
|
|
||||||
_, _, err := kv.Release(lockEnt, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to release lock: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy is used to cleanup the lock entry. It is not necessary
|
|
||||||
// to invoke. It will fail if the lock is in use.
|
|
||||||
func (l *Lock) Destroy() error {
|
|
||||||
// Hold the lock as we try to release
|
|
||||||
l.l.Lock()
|
|
||||||
defer l.l.Unlock()
|
|
||||||
|
|
||||||
// Check if we already hold the lock
|
|
||||||
if l.isHeld {
|
|
||||||
return ErrLockHeld
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for an existing lock
|
|
||||||
kv := l.c.KV()
|
|
||||||
pair, _, err := kv.Get(l.opts.Key, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read lock: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nothing to do if the lock does not exist
|
|
||||||
if pair == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for possible flag conflict
|
|
||||||
if pair.Flags != LockFlagValue {
|
|
||||||
return ErrLockConflict
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if it is in use
|
|
||||||
if pair.Session != "" {
|
|
||||||
return ErrLockInUse
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt the delete
|
|
||||||
didRemove, _, err := kv.DeleteCAS(pair, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to remove lock: %v", err)
|
|
||||||
}
|
|
||||||
if !didRemove {
|
|
||||||
return ErrLockInUse
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createSession is used to create a new managed session
|
|
||||||
func (l *Lock) createSession() (string, error) {
|
|
||||||
session := l.c.Session()
|
|
||||||
se := l.opts.SessionOpts
|
|
||||||
if se == nil {
|
|
||||||
se = &SessionEntry{
|
|
||||||
Name: l.opts.SessionName,
|
|
||||||
TTL: l.opts.SessionTTL,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
id, _, err := session.Create(se, nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// lockEntry returns a formatted KVPair for the lock
|
|
||||||
func (l *Lock) lockEntry(session string) *KVPair {
|
|
||||||
return &KVPair{
|
|
||||||
Key: l.opts.Key,
|
|
||||||
Value: l.opts.Value,
|
|
||||||
Session: session,
|
|
||||||
Flags: LockFlagValue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// monitorLock is a long running routine to monitor a lock ownership
|
|
||||||
// It closes the stopCh if we lose our leadership.
|
|
||||||
func (l *Lock) monitorLock(session string, stopCh chan struct{}) {
|
|
||||||
defer close(stopCh)
|
|
||||||
kv := l.c.KV()
|
|
||||||
opts := &QueryOptions{RequireConsistent: true}
|
|
||||||
WAIT:
|
|
||||||
retries := l.opts.MonitorRetries
|
|
||||||
RETRY:
|
|
||||||
pair, meta, err := kv.Get(l.opts.Key, opts)
|
|
||||||
if err != nil {
|
|
||||||
// If configured we can try to ride out a brief Consul unavailability
|
|
||||||
// by doing retries. Note that we have to attempt the retry in a non-
|
|
||||||
// blocking fashion so that we have a clean place to reset the retry
|
|
||||||
// counter if service is restored.
|
|
||||||
if retries > 0 && IsRetryableError(err) {
|
|
||||||
time.Sleep(l.opts.MonitorRetryTime)
|
|
||||||
retries--
|
|
||||||
opts.WaitIndex = 0
|
|
||||||
goto RETRY
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if pair != nil && pair.Session == session {
|
|
||||||
opts.WaitIndex = meta.LastIndex
|
|
||||||
goto WAIT
|
|
||||||
}
|
|
||||||
}
|
|
11
vendor/github.com/hashicorp/consul/api/operator.go
generated
vendored
11
vendor/github.com/hashicorp/consul/api/operator.go
generated
vendored
@ -1,11 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
// Operator can be used to perform low-level operator tasks for Consul.
|
|
||||||
type Operator struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Operator returns a handle to the operator endpoints.
|
|
||||||
func (c *Client) Operator() *Operator {
|
|
||||||
return &Operator{c}
|
|
||||||
}
|
|
194
vendor/github.com/hashicorp/consul/api/operator_area.go
generated
vendored
194
vendor/github.com/hashicorp/consul/api/operator_area.go
generated
vendored
@ -1,194 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
// The /v1/operator/area endpoints are available only in Consul Enterprise and
|
|
||||||
// interact with its network area subsystem. Network areas are used to link
|
|
||||||
// together Consul servers in different Consul datacenters. With network areas,
|
|
||||||
// Consul datacenters can be linked together in ways other than a fully-connected
|
|
||||||
// mesh, as is required for Consul's WAN.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Area defines a network area.
|
|
||||||
type Area struct {
|
|
||||||
// ID is this identifier for an area (a UUID). This must be left empty
|
|
||||||
// when creating a new area.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// PeerDatacenter is the peer Consul datacenter that will make up the
|
|
||||||
// other side of this network area. Network areas always involve a pair
|
|
||||||
// of datacenters: the datacenter where the area was created, and the
|
|
||||||
// peer datacenter. This is required.
|
|
||||||
PeerDatacenter string
|
|
||||||
|
|
||||||
// RetryJoin specifies the address of Consul servers to join to, such as
|
|
||||||
// an IPs or hostnames with an optional port number. This is optional.
|
|
||||||
RetryJoin []string
|
|
||||||
|
|
||||||
// UseTLS specifies whether gossip over this area should be encrypted with TLS
|
|
||||||
// if possible.
|
|
||||||
UseTLS bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaJoinResponse is returned when a join occurs and gives the result for each
|
|
||||||
// address.
|
|
||||||
type AreaJoinResponse struct {
|
|
||||||
// The address that was joined.
|
|
||||||
Address string
|
|
||||||
|
|
||||||
// Whether or not the join was a success.
|
|
||||||
Joined bool
|
|
||||||
|
|
||||||
// If we couldn't join, this is the message with information.
|
|
||||||
Error string
|
|
||||||
}
|
|
||||||
|
|
||||||
// SerfMember is a generic structure for reporting information about members in
|
|
||||||
// a Serf cluster. This is only used by the area endpoints right now, but this
|
|
||||||
// could be expanded to other endpoints in the future.
|
|
||||||
type SerfMember struct {
|
|
||||||
// ID is the node identifier (a UUID).
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// Name is the node name.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Addr has the IP address.
|
|
||||||
Addr net.IP
|
|
||||||
|
|
||||||
// Port is the RPC port.
|
|
||||||
Port uint16
|
|
||||||
|
|
||||||
// Datacenter is the DC name.
|
|
||||||
Datacenter string
|
|
||||||
|
|
||||||
// Role is "client", "server", or "unknown".
|
|
||||||
Role string
|
|
||||||
|
|
||||||
// Build has the version of the Consul agent.
|
|
||||||
Build string
|
|
||||||
|
|
||||||
// Protocol is the protocol of the Consul agent.
|
|
||||||
Protocol int
|
|
||||||
|
|
||||||
// Status is the Serf health status "none", "alive", "leaving", "left",
|
|
||||||
// or "failed".
|
|
||||||
Status string
|
|
||||||
|
|
||||||
// RTT is the estimated round trip time from the server handling the
|
|
||||||
// request to the this member. This will be negative if no RTT estimate
|
|
||||||
// is available.
|
|
||||||
RTT time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaCreate will create a new network area. The ID in the given structure must
|
|
||||||
// be empty and a generated ID will be returned on success.
|
|
||||||
func (op *Operator) AreaCreate(area *Area, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
r := op.c.newRequest("POST", "/v1/operator/area")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = area
|
|
||||||
rtt, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out struct{ ID string }
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return out.ID, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaUpdate will update the configuration of the network area with the given ID.
|
|
||||||
func (op *Operator) AreaUpdate(areaID string, area *Area, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
r := op.c.newRequest("PUT", "/v1/operator/area/"+areaID)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = area
|
|
||||||
rtt, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out struct{ ID string }
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return out.ID, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaGet returns a single network area.
|
|
||||||
func (op *Operator) AreaGet(areaID string, q *QueryOptions) ([]*Area, *QueryMeta, error) {
|
|
||||||
var out []*Area
|
|
||||||
qm, err := op.c.query("/v1/operator/area/"+areaID, &out, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaList returns all the available network areas.
|
|
||||||
func (op *Operator) AreaList(q *QueryOptions) ([]*Area, *QueryMeta, error) {
|
|
||||||
var out []*Area
|
|
||||||
qm, err := op.c.query("/v1/operator/area", &out, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaDelete deletes the given network area.
|
|
||||||
func (op *Operator) AreaDelete(areaID string, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := op.c.newRequest("DELETE", "/v1/operator/area/"+areaID)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
rtt, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaJoin attempts to join the given set of join addresses to the given
|
|
||||||
// network area. See the Area structure for details about join addresses.
|
|
||||||
func (op *Operator) AreaJoin(areaID string, addresses []string, q *WriteOptions) ([]*AreaJoinResponse, *WriteMeta, error) {
|
|
||||||
r := op.c.newRequest("PUT", "/v1/operator/area/"+areaID+"/join")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = addresses
|
|
||||||
rtt, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out []*AreaJoinResponse
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AreaMembers lists the Serf information about the members in the given area.
|
|
||||||
func (op *Operator) AreaMembers(areaID string, q *QueryOptions) ([]*SerfMember, *QueryMeta, error) {
|
|
||||||
var out []*SerfMember
|
|
||||||
qm, err := op.c.query("/v1/operator/area/"+areaID+"/members", &out, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
219
vendor/github.com/hashicorp/consul/api/operator_autopilot.go
generated
vendored
219
vendor/github.com/hashicorp/consul/api/operator_autopilot.go
generated
vendored
@ -1,219 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AutopilotConfiguration is used for querying/setting the Autopilot configuration.
|
|
||||||
// Autopilot helps manage operator tasks related to Consul servers like removing
|
|
||||||
// failed servers from the Raft quorum.
|
|
||||||
type AutopilotConfiguration struct {
|
|
||||||
// CleanupDeadServers controls whether to remove dead servers from the Raft
|
|
||||||
// peer list when a new server joins
|
|
||||||
CleanupDeadServers bool
|
|
||||||
|
|
||||||
// LastContactThreshold is the limit on the amount of time a server can go
|
|
||||||
// without leader contact before being considered unhealthy.
|
|
||||||
LastContactThreshold *ReadableDuration
|
|
||||||
|
|
||||||
// MaxTrailingLogs is the amount of entries in the Raft Log that a server can
|
|
||||||
// be behind before being considered unhealthy.
|
|
||||||
MaxTrailingLogs uint64
|
|
||||||
|
|
||||||
// ServerStabilizationTime is the minimum amount of time a server must be
|
|
||||||
// in a stable, healthy state before it can be added to the cluster. Only
|
|
||||||
// applicable with Raft protocol version 3 or higher.
|
|
||||||
ServerStabilizationTime *ReadableDuration
|
|
||||||
|
|
||||||
// (Enterprise-only) RedundancyZoneTag is the node tag to use for separating
|
|
||||||
// servers into zones for redundancy. If left blank, this feature will be disabled.
|
|
||||||
RedundancyZoneTag string
|
|
||||||
|
|
||||||
// (Enterprise-only) DisableUpgradeMigration will disable Autopilot's upgrade migration
|
|
||||||
// strategy of waiting until enough newer-versioned servers have been added to the
|
|
||||||
// cluster before promoting them to voters.
|
|
||||||
DisableUpgradeMigration bool
|
|
||||||
|
|
||||||
// (Enterprise-only) UpgradeVersionTag is the node tag to use for version info when
|
|
||||||
// performing upgrade migrations. If left blank, the Consul version will be used.
|
|
||||||
UpgradeVersionTag string
|
|
||||||
|
|
||||||
// CreateIndex holds the index corresponding the creation of this configuration.
|
|
||||||
// This is a read-only field.
|
|
||||||
CreateIndex uint64
|
|
||||||
|
|
||||||
// ModifyIndex will be set to the index of the last update when retrieving the
|
|
||||||
// Autopilot configuration. Resubmitting a configuration with
|
|
||||||
// AutopilotCASConfiguration will perform a check-and-set operation which ensures
|
|
||||||
// there hasn't been a subsequent update since the configuration was retrieved.
|
|
||||||
ModifyIndex uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServerHealth is the health (from the leader's point of view) of a server.
|
|
||||||
type ServerHealth struct {
|
|
||||||
// ID is the raft ID of the server.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// Name is the node name of the server.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Address is the address of the server.
|
|
||||||
Address string
|
|
||||||
|
|
||||||
// The status of the SerfHealth check for the server.
|
|
||||||
SerfStatus string
|
|
||||||
|
|
||||||
// Version is the Consul version of the server.
|
|
||||||
Version string
|
|
||||||
|
|
||||||
// Leader is whether this server is currently the leader.
|
|
||||||
Leader bool
|
|
||||||
|
|
||||||
// LastContact is the time since this node's last contact with the leader.
|
|
||||||
LastContact *ReadableDuration
|
|
||||||
|
|
||||||
// LastTerm is the highest leader term this server has a record of in its Raft log.
|
|
||||||
LastTerm uint64
|
|
||||||
|
|
||||||
// LastIndex is the last log index this server has a record of in its Raft log.
|
|
||||||
LastIndex uint64
|
|
||||||
|
|
||||||
// Healthy is whether or not the server is healthy according to the current
|
|
||||||
// Autopilot config.
|
|
||||||
Healthy bool
|
|
||||||
|
|
||||||
// Voter is whether this is a voting server.
|
|
||||||
Voter bool
|
|
||||||
|
|
||||||
// StableSince is the last time this server's Healthy value changed.
|
|
||||||
StableSince time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// OperatorHealthReply is a representation of the overall health of the cluster
|
|
||||||
type OperatorHealthReply struct {
|
|
||||||
// Healthy is true if all the servers in the cluster are healthy.
|
|
||||||
Healthy bool
|
|
||||||
|
|
||||||
// FailureTolerance is the number of healthy servers that could be lost without
|
|
||||||
// an outage occurring.
|
|
||||||
FailureTolerance int
|
|
||||||
|
|
||||||
// Servers holds the health of each server.
|
|
||||||
Servers []ServerHealth
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadableDuration is a duration type that is serialized to JSON in human readable format.
|
|
||||||
type ReadableDuration time.Duration
|
|
||||||
|
|
||||||
func NewReadableDuration(dur time.Duration) *ReadableDuration {
|
|
||||||
d := ReadableDuration(dur)
|
|
||||||
return &d
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ReadableDuration) String() string {
|
|
||||||
return d.Duration().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ReadableDuration) Duration() time.Duration {
|
|
||||||
if d == nil {
|
|
||||||
return time.Duration(0)
|
|
||||||
}
|
|
||||||
return time.Duration(*d)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ReadableDuration) MarshalJSON() ([]byte, error) {
|
|
||||||
return []byte(fmt.Sprintf(`"%s"`, d.Duration().String())), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ReadableDuration) UnmarshalJSON(raw []byte) error {
|
|
||||||
if d == nil {
|
|
||||||
return fmt.Errorf("cannot unmarshal to nil pointer")
|
|
||||||
}
|
|
||||||
|
|
||||||
str := string(raw)
|
|
||||||
if len(str) < 2 || str[0] != '"' || str[len(str)-1] != '"' {
|
|
||||||
return fmt.Errorf("must be enclosed with quotes: %s", str)
|
|
||||||
}
|
|
||||||
dur, err := time.ParseDuration(str[1 : len(str)-1])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*d = ReadableDuration(dur)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AutopilotGetConfiguration is used to query the current Autopilot configuration.
|
|
||||||
func (op *Operator) AutopilotGetConfiguration(q *QueryOptions) (*AutopilotConfiguration, error) {
|
|
||||||
r := op.c.newRequest("GET", "/v1/operator/autopilot/configuration")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out AutopilotConfiguration
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AutopilotSetConfiguration is used to set the current Autopilot configuration.
|
|
||||||
func (op *Operator) AutopilotSetConfiguration(conf *AutopilotConfiguration, q *WriteOptions) error {
|
|
||||||
r := op.c.newRequest("PUT", "/v1/operator/autopilot/configuration")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = conf
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AutopilotCASConfiguration is used to perform a Check-And-Set update on the
|
|
||||||
// Autopilot configuration. The ModifyIndex value will be respected. Returns
|
|
||||||
// true on success or false on failures.
|
|
||||||
func (op *Operator) AutopilotCASConfiguration(conf *AutopilotConfiguration, q *WriteOptions) (bool, error) {
|
|
||||||
r := op.c.newRequest("PUT", "/v1/operator/autopilot/configuration")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.params.Set("cas", strconv.FormatUint(conf.ModifyIndex, 10))
|
|
||||||
r.obj = conf
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if _, err := io.Copy(&buf, resp.Body); err != nil {
|
|
||||||
return false, fmt.Errorf("Failed to read response: %v", err)
|
|
||||||
}
|
|
||||||
res := strings.Contains(buf.String(), "true")
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AutopilotServerHealth
|
|
||||||
func (op *Operator) AutopilotServerHealth(q *QueryOptions) (*OperatorHealthReply, error) {
|
|
||||||
r := op.c.newRequest("GET", "/v1/operator/autopilot/health")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out OperatorHealthReply
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &out, nil
|
|
||||||
}
|
|
89
vendor/github.com/hashicorp/consul/api/operator_keyring.go
generated
vendored
89
vendor/github.com/hashicorp/consul/api/operator_keyring.go
generated
vendored
@ -1,89 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
// keyringRequest is used for performing Keyring operations
|
|
||||||
type keyringRequest struct {
|
|
||||||
Key string
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyringResponse is returned when listing the gossip encryption keys
|
|
||||||
type KeyringResponse struct {
|
|
||||||
// Whether this response is for a WAN ring
|
|
||||||
WAN bool
|
|
||||||
|
|
||||||
// The datacenter name this request corresponds to
|
|
||||||
Datacenter string
|
|
||||||
|
|
||||||
// Segment has the network segment this request corresponds to.
|
|
||||||
Segment string
|
|
||||||
|
|
||||||
// Messages has information or errors from serf
|
|
||||||
Messages map[string]string `json:",omitempty"`
|
|
||||||
|
|
||||||
// A map of the encryption keys to the number of nodes they're installed on
|
|
||||||
Keys map[string]int
|
|
||||||
|
|
||||||
// The total number of nodes in this ring
|
|
||||||
NumNodes int
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyringInstall is used to install a new gossip encryption key into the cluster
|
|
||||||
func (op *Operator) KeyringInstall(key string, q *WriteOptions) error {
|
|
||||||
r := op.c.newRequest("POST", "/v1/operator/keyring")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = keyringRequest{
|
|
||||||
Key: key,
|
|
||||||
}
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyringList is used to list the gossip keys installed in the cluster
|
|
||||||
func (op *Operator) KeyringList(q *QueryOptions) ([]*KeyringResponse, error) {
|
|
||||||
r := op.c.newRequest("GET", "/v1/operator/keyring")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out []*KeyringResponse
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyringRemove is used to remove a gossip encryption key from the cluster
|
|
||||||
func (op *Operator) KeyringRemove(key string, q *WriteOptions) error {
|
|
||||||
r := op.c.newRequest("DELETE", "/v1/operator/keyring")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = keyringRequest{
|
|
||||||
Key: key,
|
|
||||||
}
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyringUse is used to change the active gossip encryption key
|
|
||||||
func (op *Operator) KeyringUse(key string, q *WriteOptions) error {
|
|
||||||
r := op.c.newRequest("PUT", "/v1/operator/keyring")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = keyringRequest{
|
|
||||||
Key: key,
|
|
||||||
}
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
89
vendor/github.com/hashicorp/consul/api/operator_raft.go
generated
vendored
89
vendor/github.com/hashicorp/consul/api/operator_raft.go
generated
vendored
@ -1,89 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
// RaftServer has information about a server in the Raft configuration.
|
|
||||||
type RaftServer struct {
|
|
||||||
// ID is the unique ID for the server. These are currently the same
|
|
||||||
// as the address, but they will be changed to a real GUID in a future
|
|
||||||
// release of Consul.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// Node is the node name of the server, as known by Consul, or this
|
|
||||||
// will be set to "(unknown)" otherwise.
|
|
||||||
Node string
|
|
||||||
|
|
||||||
// Address is the IP:port of the server, used for Raft communications.
|
|
||||||
Address string
|
|
||||||
|
|
||||||
// Leader is true if this server is the current cluster leader.
|
|
||||||
Leader bool
|
|
||||||
|
|
||||||
// Protocol version is the raft protocol version used by the server
|
|
||||||
ProtocolVersion string
|
|
||||||
|
|
||||||
// Voter is true if this server has a vote in the cluster. This might
|
|
||||||
// be false if the server is staging and still coming online, or if
|
|
||||||
// it's a non-voting server, which will be added in a future release of
|
|
||||||
// Consul.
|
|
||||||
Voter bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// RaftConfiguration is returned when querying for the current Raft configuration.
|
|
||||||
type RaftConfiguration struct {
|
|
||||||
// Servers has the list of servers in the Raft configuration.
|
|
||||||
Servers []*RaftServer
|
|
||||||
|
|
||||||
// Index has the Raft index of this configuration.
|
|
||||||
Index uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// RaftGetConfiguration is used to query the current Raft peer set.
|
|
||||||
func (op *Operator) RaftGetConfiguration(q *QueryOptions) (*RaftConfiguration, error) {
|
|
||||||
r := op.c.newRequest("GET", "/v1/operator/raft/configuration")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var out RaftConfiguration
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RaftRemovePeerByAddress is used to kick a stale peer (one that it in the Raft
|
|
||||||
// quorum but no longer known to Serf or the catalog) by address in the form of
|
|
||||||
// "IP:port".
|
|
||||||
func (op *Operator) RaftRemovePeerByAddress(address string, q *WriteOptions) error {
|
|
||||||
r := op.c.newRequest("DELETE", "/v1/operator/raft/peer")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
|
|
||||||
r.params.Set("address", string(address))
|
|
||||||
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RaftRemovePeerByID is used to kick a stale peer (one that it in the Raft
|
|
||||||
// quorum but no longer known to Serf or the catalog) by ID.
|
|
||||||
func (op *Operator) RaftRemovePeerByID(id string, q *WriteOptions) error {
|
|
||||||
r := op.c.newRequest("DELETE", "/v1/operator/raft/peer")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
|
|
||||||
r.params.Set("id", string(id))
|
|
||||||
|
|
||||||
_, resp, err := requireOK(op.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
11
vendor/github.com/hashicorp/consul/api/operator_segment.go
generated
vendored
11
vendor/github.com/hashicorp/consul/api/operator_segment.go
generated
vendored
@ -1,11 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
// SegmentList returns all the available LAN segments.
|
|
||||||
func (op *Operator) SegmentList(q *QueryOptions) ([]string, *QueryMeta, error) {
|
|
||||||
var out []string
|
|
||||||
qm, err := op.c.query("/v1/operator/segment", &out, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
217
vendor/github.com/hashicorp/consul/api/prepared_query.go
generated
vendored
217
vendor/github.com/hashicorp/consul/api/prepared_query.go
generated
vendored
@ -1,217 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
// QueryDatacenterOptions sets options about how we fail over if there are no
|
|
||||||
// healthy nodes in the local datacenter.
|
|
||||||
type QueryDatacenterOptions struct {
|
|
||||||
// NearestN is set to the number of remote datacenters to try, based on
|
|
||||||
// network coordinates.
|
|
||||||
NearestN int
|
|
||||||
|
|
||||||
// Datacenters is a fixed list of datacenters to try after NearestN. We
|
|
||||||
// never try a datacenter multiple times, so those are subtracted from
|
|
||||||
// this list before proceeding.
|
|
||||||
Datacenters []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryDNSOptions controls settings when query results are served over DNS.
|
|
||||||
type QueryDNSOptions struct {
|
|
||||||
// TTL is the time to live for the served DNS results.
|
|
||||||
TTL string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceQuery is used to query for a set of healthy nodes offering a specific
|
|
||||||
// service.
|
|
||||||
type ServiceQuery struct {
|
|
||||||
// Service is the service to query.
|
|
||||||
Service string
|
|
||||||
|
|
||||||
// Near allows baking in the name of a node to automatically distance-
|
|
||||||
// sort from. The magic "_agent" value is supported, which sorts near
|
|
||||||
// the agent which initiated the request by default.
|
|
||||||
Near string
|
|
||||||
|
|
||||||
// Failover controls what we do if there are no healthy nodes in the
|
|
||||||
// local datacenter.
|
|
||||||
Failover QueryDatacenterOptions
|
|
||||||
|
|
||||||
// IgnoreCheckIDs is an optional list of health check IDs to ignore when
|
|
||||||
// considering which nodes are healthy. It is useful as an emergency measure
|
|
||||||
// to temporarily override some health check that is producing false negatives
|
|
||||||
// for example.
|
|
||||||
IgnoreCheckIDs []string
|
|
||||||
|
|
||||||
// If OnlyPassing is true then we will only include nodes with passing
|
|
||||||
// health checks (critical AND warning checks will cause a node to be
|
|
||||||
// discarded)
|
|
||||||
OnlyPassing bool
|
|
||||||
|
|
||||||
// Tags are a set of required and/or disallowed tags. If a tag is in
|
|
||||||
// this list it must be present. If the tag is preceded with "!" then
|
|
||||||
// it is disallowed.
|
|
||||||
Tags []string
|
|
||||||
|
|
||||||
// NodeMeta is a map of required node metadata fields. If a key/value
|
|
||||||
// pair is in this map it must be present on the node in order for the
|
|
||||||
// service entry to be returned.
|
|
||||||
NodeMeta map[string]string
|
|
||||||
|
|
||||||
// ServiceMeta is a map of required service metadata fields. If a key/value
|
|
||||||
// pair is in this map it must be present on the node in order for the
|
|
||||||
// service entry to be returned.
|
|
||||||
ServiceMeta map[string]string
|
|
||||||
|
|
||||||
// Connect if true will filter the prepared query results to only
|
|
||||||
// include Connect-capable services. These include both native services
|
|
||||||
// and proxies for matching services. Note that if a proxy matches,
|
|
||||||
// the constraints in the query above (Near, OnlyPassing, etc.) apply
|
|
||||||
// to the _proxy_ and not the service being proxied. In practice, proxies
|
|
||||||
// should be directly next to their services so this isn't an issue.
|
|
||||||
Connect bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryTemplate carries the arguments for creating a templated query.
|
|
||||||
type QueryTemplate struct {
|
|
||||||
// Type specifies the type of the query template. Currently only
|
|
||||||
// "name_prefix_match" is supported. This field is required.
|
|
||||||
Type string
|
|
||||||
|
|
||||||
// Regexp allows specifying a regex pattern to match against the name
|
|
||||||
// of the query being executed.
|
|
||||||
Regexp string
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreparedQueryDefinition defines a complete prepared query.
|
|
||||||
type PreparedQueryDefinition struct {
|
|
||||||
// ID is this UUID-based ID for the query, always generated by Consul.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// Name is an optional friendly name for the query supplied by the
|
|
||||||
// user. NOTE - if this feature is used then it will reduce the security
|
|
||||||
// of any read ACL associated with this query/service since this name
|
|
||||||
// can be used to locate nodes with supplying any ACL.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Session is an optional session to tie this query's lifetime to. If
|
|
||||||
// this is omitted then the query will not expire.
|
|
||||||
Session string
|
|
||||||
|
|
||||||
// Token is the ACL token used when the query was created, and it is
|
|
||||||
// used when a query is subsequently executed. This token, or a token
|
|
||||||
// with management privileges, must be used to change the query later.
|
|
||||||
Token string
|
|
||||||
|
|
||||||
// Service defines a service query (leaving things open for other types
|
|
||||||
// later).
|
|
||||||
Service ServiceQuery
|
|
||||||
|
|
||||||
// DNS has options that control how the results of this query are
|
|
||||||
// served over DNS.
|
|
||||||
DNS QueryDNSOptions
|
|
||||||
|
|
||||||
// Template is used to pass through the arguments for creating a
|
|
||||||
// prepared query with an attached template. If a template is given,
|
|
||||||
// interpolations are possible in other struct fields.
|
|
||||||
Template QueryTemplate
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreparedQueryExecuteResponse has the results of executing a query.
|
|
||||||
type PreparedQueryExecuteResponse struct {
|
|
||||||
// Service is the service that was queried.
|
|
||||||
Service string
|
|
||||||
|
|
||||||
// Nodes has the nodes that were output by the query.
|
|
||||||
Nodes []ServiceEntry
|
|
||||||
|
|
||||||
// DNS has the options for serving these results over DNS.
|
|
||||||
DNS QueryDNSOptions
|
|
||||||
|
|
||||||
// Datacenter is the datacenter that these results came from.
|
|
||||||
Datacenter string
|
|
||||||
|
|
||||||
// Failovers is a count of how many times we had to query a remote
|
|
||||||
// datacenter.
|
|
||||||
Failovers int
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreparedQuery can be used to query the prepared query endpoints.
|
|
||||||
type PreparedQuery struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreparedQuery returns a handle to the prepared query endpoints.
|
|
||||||
func (c *Client) PreparedQuery() *PreparedQuery {
|
|
||||||
return &PreparedQuery{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create makes a new prepared query. The ID of the new query is returned.
|
|
||||||
func (c *PreparedQuery) Create(query *PreparedQueryDefinition, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
r := c.c.newRequest("POST", "/v1/query")
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
r.obj = query
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
|
|
||||||
var out struct{ ID string }
|
|
||||||
if err := decodeBody(resp, &out); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return out.ID, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update makes updates to an existing prepared query.
|
|
||||||
func (c *PreparedQuery) Update(query *PreparedQueryDefinition, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
return c.c.write("/v1/query/"+query.ID, query, nil, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
// List is used to fetch all the prepared queries (always requires a management
|
|
||||||
// token).
|
|
||||||
func (c *PreparedQuery) List(q *QueryOptions) ([]*PreparedQueryDefinition, *QueryMeta, error) {
|
|
||||||
var out []*PreparedQueryDefinition
|
|
||||||
qm, err := c.c.query("/v1/query", &out, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get is used to fetch a specific prepared query.
|
|
||||||
func (c *PreparedQuery) Get(queryID string, q *QueryOptions) ([]*PreparedQueryDefinition, *QueryMeta, error) {
|
|
||||||
var out []*PreparedQueryDefinition
|
|
||||||
qm, err := c.c.query("/v1/query/"+queryID, &out, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete is used to delete a specific prepared query.
|
|
||||||
func (c *PreparedQuery) Delete(queryID string, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
r := c.c.newRequest("DELETE", "/v1/query/"+queryID)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
rtt, resp, err := requireOK(c.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{}
|
|
||||||
wm.RequestTime = rtt
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute is used to execute a specific prepared query. You can execute using
|
|
||||||
// a query ID or name.
|
|
||||||
func (c *PreparedQuery) Execute(queryIDOrName string, q *QueryOptions) (*PreparedQueryExecuteResponse, *QueryMeta, error) {
|
|
||||||
var out *PreparedQueryExecuteResponse
|
|
||||||
qm, err := c.c.query("/v1/query/"+queryIDOrName+"/execute", &out, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return out, qm, nil
|
|
||||||
}
|
|
24
vendor/github.com/hashicorp/consul/api/raw.go
generated
vendored
24
vendor/github.com/hashicorp/consul/api/raw.go
generated
vendored
@ -1,24 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
// Raw can be used to do raw queries against custom endpoints
|
|
||||||
type Raw struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Raw returns a handle to query endpoints
|
|
||||||
func (c *Client) Raw() *Raw {
|
|
||||||
return &Raw{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query is used to do a GET request against an endpoint
|
|
||||||
// and deserialize the response into an interface using
|
|
||||||
// standard Consul conventions.
|
|
||||||
func (raw *Raw) Query(endpoint string, out interface{}, q *QueryOptions) (*QueryMeta, error) {
|
|
||||||
return raw.c.query(endpoint, out, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write is used to do a PUT request against an endpoint
|
|
||||||
// and serialize/deserialized using the standard Consul conventions.
|
|
||||||
func (raw *Raw) Write(endpoint string, in, out interface{}, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
return raw.c.write(endpoint, in, out, q)
|
|
||||||
}
|
|
514
vendor/github.com/hashicorp/consul/api/semaphore.go
generated
vendored
514
vendor/github.com/hashicorp/consul/api/semaphore.go
generated
vendored
@ -1,514 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"path"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultSemaphoreSessionName is the Session Name we assign if none is provided
|
|
||||||
DefaultSemaphoreSessionName = "Consul API Semaphore"
|
|
||||||
|
|
||||||
// DefaultSemaphoreSessionTTL is the default session TTL if no Session is provided
|
|
||||||
// when creating a new Semaphore. This is used because we do not have another
|
|
||||||
// other check to depend upon.
|
|
||||||
DefaultSemaphoreSessionTTL = "15s"
|
|
||||||
|
|
||||||
// DefaultSemaphoreWaitTime is how long we block for at a time to check if semaphore
|
|
||||||
// acquisition is possible. This affects the minimum time it takes to cancel
|
|
||||||
// a Semaphore acquisition.
|
|
||||||
DefaultSemaphoreWaitTime = 15 * time.Second
|
|
||||||
|
|
||||||
// DefaultSemaphoreKey is the key used within the prefix to
|
|
||||||
// use for coordination between all the contenders.
|
|
||||||
DefaultSemaphoreKey = ".lock"
|
|
||||||
|
|
||||||
// SemaphoreFlagValue is a magic flag we set to indicate a key
|
|
||||||
// is being used for a semaphore. It is used to detect a potential
|
|
||||||
// conflict with a lock.
|
|
||||||
SemaphoreFlagValue = 0xe0f69a2baa414de0
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrSemaphoreHeld is returned if we attempt to double lock
|
|
||||||
ErrSemaphoreHeld = fmt.Errorf("Semaphore already held")
|
|
||||||
|
|
||||||
// ErrSemaphoreNotHeld is returned if we attempt to unlock a semaphore
|
|
||||||
// that we do not hold.
|
|
||||||
ErrSemaphoreNotHeld = fmt.Errorf("Semaphore not held")
|
|
||||||
|
|
||||||
// ErrSemaphoreInUse is returned if we attempt to destroy a semaphore
|
|
||||||
// that is in use.
|
|
||||||
ErrSemaphoreInUse = fmt.Errorf("Semaphore in use")
|
|
||||||
|
|
||||||
// ErrSemaphoreConflict is returned if the flags on a key
|
|
||||||
// used for a semaphore do not match expectation
|
|
||||||
ErrSemaphoreConflict = fmt.Errorf("Existing key does not match semaphore use")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Semaphore is used to implement a distributed semaphore
|
|
||||||
// using the Consul KV primitives.
|
|
||||||
type Semaphore struct {
|
|
||||||
c *Client
|
|
||||||
opts *SemaphoreOptions
|
|
||||||
|
|
||||||
isHeld bool
|
|
||||||
sessionRenew chan struct{}
|
|
||||||
lockSession string
|
|
||||||
l sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// SemaphoreOptions is used to parameterize the Semaphore
|
|
||||||
type SemaphoreOptions struct {
|
|
||||||
Prefix string // Must be set and have write permissions
|
|
||||||
Limit int // Must be set, and be positive
|
|
||||||
Value []byte // Optional, value to associate with the contender entry
|
|
||||||
Session string // Optional, created if not specified
|
|
||||||
SessionName string // Optional, defaults to DefaultLockSessionName
|
|
||||||
SessionTTL string // Optional, defaults to DefaultLockSessionTTL
|
|
||||||
MonitorRetries int // Optional, defaults to 0 which means no retries
|
|
||||||
MonitorRetryTime time.Duration // Optional, defaults to DefaultMonitorRetryTime
|
|
||||||
SemaphoreWaitTime time.Duration // Optional, defaults to DefaultSemaphoreWaitTime
|
|
||||||
SemaphoreTryOnce bool // Optional, defaults to false which means try forever
|
|
||||||
}
|
|
||||||
|
|
||||||
// semaphoreLock is written under the DefaultSemaphoreKey and
|
|
||||||
// is used to coordinate between all the contenders.
|
|
||||||
type semaphoreLock struct {
|
|
||||||
// Limit is the integer limit of holders. This is used to
|
|
||||||
// verify that all the holders agree on the value.
|
|
||||||
Limit int
|
|
||||||
|
|
||||||
// Holders is a list of all the semaphore holders.
|
|
||||||
// It maps the session ID to true. It is used as a set effectively.
|
|
||||||
Holders map[string]bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// SemaphorePrefix is used to created a Semaphore which will operate
|
|
||||||
// at the given KV prefix and uses the given limit for the semaphore.
|
|
||||||
// The prefix must have write privileges, and the limit must be agreed
|
|
||||||
// upon by all contenders.
|
|
||||||
func (c *Client) SemaphorePrefix(prefix string, limit int) (*Semaphore, error) {
|
|
||||||
opts := &SemaphoreOptions{
|
|
||||||
Prefix: prefix,
|
|
||||||
Limit: limit,
|
|
||||||
}
|
|
||||||
return c.SemaphoreOpts(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SemaphoreOpts is used to create a Semaphore with the given options.
|
|
||||||
// The prefix must have write privileges, and the limit must be agreed
|
|
||||||
// upon by all contenders. If a Session is not provided, one will be created.
|
|
||||||
func (c *Client) SemaphoreOpts(opts *SemaphoreOptions) (*Semaphore, error) {
|
|
||||||
if opts.Prefix == "" {
|
|
||||||
return nil, fmt.Errorf("missing prefix")
|
|
||||||
}
|
|
||||||
if opts.Limit <= 0 {
|
|
||||||
return nil, fmt.Errorf("semaphore limit must be positive")
|
|
||||||
}
|
|
||||||
if opts.SessionName == "" {
|
|
||||||
opts.SessionName = DefaultSemaphoreSessionName
|
|
||||||
}
|
|
||||||
if opts.SessionTTL == "" {
|
|
||||||
opts.SessionTTL = DefaultSemaphoreSessionTTL
|
|
||||||
} else {
|
|
||||||
if _, err := time.ParseDuration(opts.SessionTTL); err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid SessionTTL: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if opts.MonitorRetryTime == 0 {
|
|
||||||
opts.MonitorRetryTime = DefaultMonitorRetryTime
|
|
||||||
}
|
|
||||||
if opts.SemaphoreWaitTime == 0 {
|
|
||||||
opts.SemaphoreWaitTime = DefaultSemaphoreWaitTime
|
|
||||||
}
|
|
||||||
s := &Semaphore{
|
|
||||||
c: c,
|
|
||||||
opts: opts,
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Acquire attempts to reserve a slot in the semaphore, blocking until
|
|
||||||
// success, interrupted via the stopCh or an error is encountered.
|
|
||||||
// Providing a non-nil stopCh can be used to abort the attempt.
|
|
||||||
// On success, a channel is returned that represents our slot.
|
|
||||||
// This channel could be closed at any time due to session invalidation,
|
|
||||||
// communication errors, operator intervention, etc. It is NOT safe to
|
|
||||||
// assume that the slot is held until Release() unless the Session is specifically
|
|
||||||
// created without any associated health checks. By default Consul sessions
|
|
||||||
// prefer liveness over safety and an application must be able to handle
|
|
||||||
// the session being lost.
|
|
||||||
func (s *Semaphore) Acquire(stopCh <-chan struct{}) (<-chan struct{}, error) {
|
|
||||||
// Hold the lock as we try to acquire
|
|
||||||
s.l.Lock()
|
|
||||||
defer s.l.Unlock()
|
|
||||||
|
|
||||||
// Check if we already hold the semaphore
|
|
||||||
if s.isHeld {
|
|
||||||
return nil, ErrSemaphoreHeld
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we need to create a session first
|
|
||||||
s.lockSession = s.opts.Session
|
|
||||||
if s.lockSession == "" {
|
|
||||||
sess, err := s.createSession()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create session: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.sessionRenew = make(chan struct{})
|
|
||||||
s.lockSession = sess
|
|
||||||
session := s.c.Session()
|
|
||||||
go session.RenewPeriodic(s.opts.SessionTTL, sess, nil, s.sessionRenew)
|
|
||||||
|
|
||||||
// If we fail to acquire the lock, cleanup the session
|
|
||||||
defer func() {
|
|
||||||
if !s.isHeld {
|
|
||||||
close(s.sessionRenew)
|
|
||||||
s.sessionRenew = nil
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the contender entry
|
|
||||||
kv := s.c.KV()
|
|
||||||
made, _, err := kv.Acquire(s.contenderEntry(s.lockSession), nil)
|
|
||||||
if err != nil || !made {
|
|
||||||
return nil, fmt.Errorf("failed to make contender entry: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the query options
|
|
||||||
qOpts := &QueryOptions{
|
|
||||||
WaitTime: s.opts.SemaphoreWaitTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
attempts := 0
|
|
||||||
WAIT:
|
|
||||||
// Check if we should quit
|
|
||||||
select {
|
|
||||||
case <-stopCh:
|
|
||||||
return nil, nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the one-shot mode.
|
|
||||||
if s.opts.SemaphoreTryOnce && attempts > 0 {
|
|
||||||
elapsed := time.Since(start)
|
|
||||||
if elapsed > s.opts.SemaphoreWaitTime {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query wait time should not exceed the semaphore wait time
|
|
||||||
qOpts.WaitTime = s.opts.SemaphoreWaitTime - elapsed
|
|
||||||
}
|
|
||||||
attempts++
|
|
||||||
|
|
||||||
// Read the prefix
|
|
||||||
pairs, meta, err := kv.List(s.opts.Prefix, qOpts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read prefix: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the lock
|
|
||||||
lockPair := s.findLock(pairs)
|
|
||||||
if lockPair.Flags != SemaphoreFlagValue {
|
|
||||||
return nil, ErrSemaphoreConflict
|
|
||||||
}
|
|
||||||
lock, err := s.decodeLock(lockPair)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify we agree with the limit
|
|
||||||
if lock.Limit != s.opts.Limit {
|
|
||||||
return nil, fmt.Errorf("semaphore limit conflict (lock: %d, local: %d)",
|
|
||||||
lock.Limit, s.opts.Limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prune the dead holders
|
|
||||||
s.pruneDeadHolders(lock, pairs)
|
|
||||||
|
|
||||||
// Check if the lock is held
|
|
||||||
if len(lock.Holders) >= lock.Limit {
|
|
||||||
qOpts.WaitIndex = meta.LastIndex
|
|
||||||
goto WAIT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new lock with us as a holder
|
|
||||||
lock.Holders[s.lockSession] = true
|
|
||||||
newLock, err := s.encodeLock(lock, lockPair.ModifyIndex)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt the acquisition
|
|
||||||
didSet, _, err := kv.CAS(newLock, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to update lock: %v", err)
|
|
||||||
}
|
|
||||||
if !didSet {
|
|
||||||
// Update failed, could have been a race with another contender,
|
|
||||||
// retry the operation
|
|
||||||
goto WAIT
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch to ensure we maintain ownership of the slot
|
|
||||||
lockCh := make(chan struct{})
|
|
||||||
go s.monitorLock(s.lockSession, lockCh)
|
|
||||||
|
|
||||||
// Set that we own the lock
|
|
||||||
s.isHeld = true
|
|
||||||
|
|
||||||
// Acquired! All done
|
|
||||||
return lockCh, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release is used to voluntarily give up our semaphore slot. It is
|
|
||||||
// an error to call this if the semaphore has not been acquired.
|
|
||||||
func (s *Semaphore) Release() error {
|
|
||||||
// Hold the lock as we try to release
|
|
||||||
s.l.Lock()
|
|
||||||
defer s.l.Unlock()
|
|
||||||
|
|
||||||
// Ensure the lock is actually held
|
|
||||||
if !s.isHeld {
|
|
||||||
return ErrSemaphoreNotHeld
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set that we no longer own the lock
|
|
||||||
s.isHeld = false
|
|
||||||
|
|
||||||
// Stop the session renew
|
|
||||||
if s.sessionRenew != nil {
|
|
||||||
defer func() {
|
|
||||||
close(s.sessionRenew)
|
|
||||||
s.sessionRenew = nil
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get and clear the lock session
|
|
||||||
lockSession := s.lockSession
|
|
||||||
s.lockSession = ""
|
|
||||||
|
|
||||||
// Remove ourselves as a lock holder
|
|
||||||
kv := s.c.KV()
|
|
||||||
key := path.Join(s.opts.Prefix, DefaultSemaphoreKey)
|
|
||||||
READ:
|
|
||||||
pair, _, err := kv.Get(key, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if pair == nil {
|
|
||||||
pair = &KVPair{}
|
|
||||||
}
|
|
||||||
lock, err := s.decodeLock(pair)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new lock without us as a holder
|
|
||||||
if _, ok := lock.Holders[lockSession]; ok {
|
|
||||||
delete(lock.Holders, lockSession)
|
|
||||||
newLock, err := s.encodeLock(lock, pair.ModifyIndex)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap the locks
|
|
||||||
didSet, _, err := kv.CAS(newLock, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update lock: %v", err)
|
|
||||||
}
|
|
||||||
if !didSet {
|
|
||||||
goto READ
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy the contender entry
|
|
||||||
contenderKey := path.Join(s.opts.Prefix, lockSession)
|
|
||||||
if _, err := kv.Delete(contenderKey, nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy is used to cleanup the semaphore entry. It is not necessary
|
|
||||||
// to invoke. It will fail if the semaphore is in use.
|
|
||||||
func (s *Semaphore) Destroy() error {
|
|
||||||
// Hold the lock as we try to acquire
|
|
||||||
s.l.Lock()
|
|
||||||
defer s.l.Unlock()
|
|
||||||
|
|
||||||
// Check if we already hold the semaphore
|
|
||||||
if s.isHeld {
|
|
||||||
return ErrSemaphoreHeld
|
|
||||||
}
|
|
||||||
|
|
||||||
// List for the semaphore
|
|
||||||
kv := s.c.KV()
|
|
||||||
pairs, _, err := kv.List(s.opts.Prefix, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read prefix: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the lock pair, bail if it doesn't exist
|
|
||||||
lockPair := s.findLock(pairs)
|
|
||||||
if lockPair.ModifyIndex == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if lockPair.Flags != SemaphoreFlagValue {
|
|
||||||
return ErrSemaphoreConflict
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the lock
|
|
||||||
lock, err := s.decodeLock(lockPair)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prune the dead holders
|
|
||||||
s.pruneDeadHolders(lock, pairs)
|
|
||||||
|
|
||||||
// Check if there are any holders
|
|
||||||
if len(lock.Holders) > 0 {
|
|
||||||
return ErrSemaphoreInUse
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt the delete
|
|
||||||
didRemove, _, err := kv.DeleteCAS(lockPair, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to remove semaphore: %v", err)
|
|
||||||
}
|
|
||||||
if !didRemove {
|
|
||||||
return ErrSemaphoreInUse
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createSession is used to create a new managed session
|
|
||||||
func (s *Semaphore) createSession() (string, error) {
|
|
||||||
session := s.c.Session()
|
|
||||||
se := &SessionEntry{
|
|
||||||
Name: s.opts.SessionName,
|
|
||||||
TTL: s.opts.SessionTTL,
|
|
||||||
Behavior: SessionBehaviorDelete,
|
|
||||||
}
|
|
||||||
id, _, err := session.Create(se, nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// contenderEntry returns a formatted KVPair for the contender
|
|
||||||
func (s *Semaphore) contenderEntry(session string) *KVPair {
|
|
||||||
return &KVPair{
|
|
||||||
Key: path.Join(s.opts.Prefix, session),
|
|
||||||
Value: s.opts.Value,
|
|
||||||
Session: session,
|
|
||||||
Flags: SemaphoreFlagValue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// findLock is used to find the KV Pair which is used for coordination
|
|
||||||
func (s *Semaphore) findLock(pairs KVPairs) *KVPair {
|
|
||||||
key := path.Join(s.opts.Prefix, DefaultSemaphoreKey)
|
|
||||||
for _, pair := range pairs {
|
|
||||||
if pair.Key == key {
|
|
||||||
return pair
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &KVPair{Flags: SemaphoreFlagValue}
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeLock is used to decode a semaphoreLock from an
|
|
||||||
// entry in Consul
|
|
||||||
func (s *Semaphore) decodeLock(pair *KVPair) (*semaphoreLock, error) {
|
|
||||||
// Handle if there is no lock
|
|
||||||
if pair == nil || pair.Value == nil {
|
|
||||||
return &semaphoreLock{
|
|
||||||
Limit: s.opts.Limit,
|
|
||||||
Holders: make(map[string]bool),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
l := &semaphoreLock{}
|
|
||||||
if err := json.Unmarshal(pair.Value, l); err != nil {
|
|
||||||
return nil, fmt.Errorf("lock decoding failed: %v", err)
|
|
||||||
}
|
|
||||||
return l, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodeLock is used to encode a semaphoreLock into a KVPair
|
|
||||||
// that can be PUT
|
|
||||||
func (s *Semaphore) encodeLock(l *semaphoreLock, oldIndex uint64) (*KVPair, error) {
|
|
||||||
enc, err := json.Marshal(l)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("lock encoding failed: %v", err)
|
|
||||||
}
|
|
||||||
pair := &KVPair{
|
|
||||||
Key: path.Join(s.opts.Prefix, DefaultSemaphoreKey),
|
|
||||||
Value: enc,
|
|
||||||
Flags: SemaphoreFlagValue,
|
|
||||||
ModifyIndex: oldIndex,
|
|
||||||
}
|
|
||||||
return pair, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// pruneDeadHolders is used to remove all the dead lock holders
|
|
||||||
func (s *Semaphore) pruneDeadHolders(lock *semaphoreLock, pairs KVPairs) {
|
|
||||||
// Gather all the live holders
|
|
||||||
alive := make(map[string]struct{}, len(pairs))
|
|
||||||
for _, pair := range pairs {
|
|
||||||
if pair.Session != "" {
|
|
||||||
alive[pair.Session] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove any holders that are dead
|
|
||||||
for holder := range lock.Holders {
|
|
||||||
if _, ok := alive[holder]; !ok {
|
|
||||||
delete(lock.Holders, holder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// monitorLock is a long running routine to monitor a semaphore ownership
|
|
||||||
// It closes the stopCh if we lose our slot.
|
|
||||||
func (s *Semaphore) monitorLock(session string, stopCh chan struct{}) {
|
|
||||||
defer close(stopCh)
|
|
||||||
kv := s.c.KV()
|
|
||||||
opts := &QueryOptions{RequireConsistent: true}
|
|
||||||
WAIT:
|
|
||||||
retries := s.opts.MonitorRetries
|
|
||||||
RETRY:
|
|
||||||
pairs, meta, err := kv.List(s.opts.Prefix, opts)
|
|
||||||
if err != nil {
|
|
||||||
// If configured we can try to ride out a brief Consul unavailability
|
|
||||||
// by doing retries. Note that we have to attempt the retry in a non-
|
|
||||||
// blocking fashion so that we have a clean place to reset the retry
|
|
||||||
// counter if service is restored.
|
|
||||||
if retries > 0 && IsRetryableError(err) {
|
|
||||||
time.Sleep(s.opts.MonitorRetryTime)
|
|
||||||
retries--
|
|
||||||
opts.WaitIndex = 0
|
|
||||||
goto RETRY
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lockPair := s.findLock(pairs)
|
|
||||||
lock, err := s.decodeLock(lockPair)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.pruneDeadHolders(lock, pairs)
|
|
||||||
if _, ok := lock.Holders[session]; ok {
|
|
||||||
opts.WaitIndex = meta.LastIndex
|
|
||||||
goto WAIT
|
|
||||||
}
|
|
||||||
}
|
|
224
vendor/github.com/hashicorp/consul/api/session.go
generated
vendored
224
vendor/github.com/hashicorp/consul/api/session.go
generated
vendored
@ -1,224 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// SessionBehaviorRelease is the default behavior and causes
|
|
||||||
// all associated locks to be released on session invalidation.
|
|
||||||
SessionBehaviorRelease = "release"
|
|
||||||
|
|
||||||
// SessionBehaviorDelete is new in Consul 0.5 and changes the
|
|
||||||
// behavior to delete all associated locks on session invalidation.
|
|
||||||
// It can be used in a way similar to Ephemeral Nodes in ZooKeeper.
|
|
||||||
SessionBehaviorDelete = "delete"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrSessionExpired = errors.New("session expired")
|
|
||||||
|
|
||||||
// SessionEntry represents a session in consul
|
|
||||||
type SessionEntry struct {
|
|
||||||
CreateIndex uint64
|
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
Node string
|
|
||||||
Checks []string
|
|
||||||
LockDelay time.Duration
|
|
||||||
Behavior string
|
|
||||||
TTL string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session can be used to query the Session endpoints
|
|
||||||
type Session struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session returns a handle to the session endpoints
|
|
||||||
func (c *Client) Session() *Session {
|
|
||||||
return &Session{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateNoChecks is like Create but is used specifically to create
|
|
||||||
// a session with no associated health checks.
|
|
||||||
func (s *Session) CreateNoChecks(se *SessionEntry, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
body := make(map[string]interface{})
|
|
||||||
body["Checks"] = []string{}
|
|
||||||
if se != nil {
|
|
||||||
if se.Name != "" {
|
|
||||||
body["Name"] = se.Name
|
|
||||||
}
|
|
||||||
if se.Node != "" {
|
|
||||||
body["Node"] = se.Node
|
|
||||||
}
|
|
||||||
if se.LockDelay != 0 {
|
|
||||||
body["LockDelay"] = durToMsec(se.LockDelay)
|
|
||||||
}
|
|
||||||
if se.Behavior != "" {
|
|
||||||
body["Behavior"] = se.Behavior
|
|
||||||
}
|
|
||||||
if se.TTL != "" {
|
|
||||||
body["TTL"] = se.TTL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s.create(body, q)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create makes a new session. Providing a session entry can
|
|
||||||
// customize the session. It can also be nil to use defaults.
|
|
||||||
func (s *Session) Create(se *SessionEntry, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
var obj interface{}
|
|
||||||
if se != nil {
|
|
||||||
body := make(map[string]interface{})
|
|
||||||
obj = body
|
|
||||||
if se.Name != "" {
|
|
||||||
body["Name"] = se.Name
|
|
||||||
}
|
|
||||||
if se.Node != "" {
|
|
||||||
body["Node"] = se.Node
|
|
||||||
}
|
|
||||||
if se.LockDelay != 0 {
|
|
||||||
body["LockDelay"] = durToMsec(se.LockDelay)
|
|
||||||
}
|
|
||||||
if len(se.Checks) > 0 {
|
|
||||||
body["Checks"] = se.Checks
|
|
||||||
}
|
|
||||||
if se.Behavior != "" {
|
|
||||||
body["Behavior"] = se.Behavior
|
|
||||||
}
|
|
||||||
if se.TTL != "" {
|
|
||||||
body["TTL"] = se.TTL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s.create(obj, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) create(obj interface{}, q *WriteOptions) (string, *WriteMeta, error) {
|
|
||||||
var out struct{ ID string }
|
|
||||||
wm, err := s.c.write("/v1/session/create", obj, &out, q)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return out.ID, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy invalidates a given session
|
|
||||||
func (s *Session) Destroy(id string, q *WriteOptions) (*WriteMeta, error) {
|
|
||||||
wm, err := s.c.write("/v1/session/destroy/"+id, nil, nil, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renew renews the TTL on a given session
|
|
||||||
func (s *Session) Renew(id string, q *WriteOptions) (*SessionEntry, *WriteMeta, error) {
|
|
||||||
r := s.c.newRequest("PUT", "/v1/session/renew/"+id)
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
rtt, resp, err := s.c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
wm := &WriteMeta{RequestTime: rtt}
|
|
||||||
|
|
||||||
if resp.StatusCode == 404 {
|
|
||||||
return nil, wm, nil
|
|
||||||
} else if resp.StatusCode != 200 {
|
|
||||||
return nil, nil, fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
var entries []*SessionEntry
|
|
||||||
if err := decodeBody(resp, &entries); err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("Failed to read response: %v", err)
|
|
||||||
}
|
|
||||||
if len(entries) > 0 {
|
|
||||||
return entries[0], wm, nil
|
|
||||||
}
|
|
||||||
return nil, wm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenewPeriodic is used to periodically invoke Session.Renew on a
|
|
||||||
// session until a doneCh is closed. This is meant to be used in a long running
|
|
||||||
// goroutine to ensure a session stays valid.
|
|
||||||
func (s *Session) RenewPeriodic(initialTTL string, id string, q *WriteOptions, doneCh <-chan struct{}) error {
|
|
||||||
ctx := q.Context()
|
|
||||||
|
|
||||||
ttl, err := time.ParseDuration(initialTTL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
waitDur := ttl / 2
|
|
||||||
lastRenewTime := time.Now()
|
|
||||||
var lastErr error
|
|
||||||
for {
|
|
||||||
if time.Since(lastRenewTime) > ttl {
|
|
||||||
return lastErr
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-time.After(waitDur):
|
|
||||||
entry, _, err := s.Renew(id, q)
|
|
||||||
if err != nil {
|
|
||||||
waitDur = time.Second
|
|
||||||
lastErr = err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if entry == nil {
|
|
||||||
return ErrSessionExpired
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the server updating the TTL
|
|
||||||
ttl, _ = time.ParseDuration(entry.TTL)
|
|
||||||
waitDur = ttl / 2
|
|
||||||
lastRenewTime = time.Now()
|
|
||||||
|
|
||||||
case <-doneCh:
|
|
||||||
// Attempt a session destroy
|
|
||||||
s.Destroy(id, q)
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case <-ctx.Done():
|
|
||||||
// Bail immediately since attempting the destroy would
|
|
||||||
// use the canceled context in q, which would just bail.
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info looks up a single session
|
|
||||||
func (s *Session) Info(id string, q *QueryOptions) (*SessionEntry, *QueryMeta, error) {
|
|
||||||
var entries []*SessionEntry
|
|
||||||
qm, err := s.c.query("/v1/session/info/"+id, &entries, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if len(entries) > 0 {
|
|
||||||
return entries[0], qm, nil
|
|
||||||
}
|
|
||||||
return nil, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List gets sessions for a node
|
|
||||||
func (s *Session) Node(node string, q *QueryOptions) ([]*SessionEntry, *QueryMeta, error) {
|
|
||||||
var entries []*SessionEntry
|
|
||||||
qm, err := s.c.query("/v1/session/node/"+node, &entries, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return entries, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List gets all active sessions
|
|
||||||
func (s *Session) List(q *QueryOptions) ([]*SessionEntry, *QueryMeta, error) {
|
|
||||||
var entries []*SessionEntry
|
|
||||||
qm, err := s.c.query("/v1/session/list", &entries, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return entries, qm, nil
|
|
||||||
}
|
|
47
vendor/github.com/hashicorp/consul/api/snapshot.go
generated
vendored
47
vendor/github.com/hashicorp/consul/api/snapshot.go
generated
vendored
@ -1,47 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Snapshot can be used to query the /v1/snapshot endpoint to take snapshots of
|
|
||||||
// Consul's internal state and restore snapshots for disaster recovery.
|
|
||||||
type Snapshot struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snapshot returns a handle that exposes the snapshot endpoints.
|
|
||||||
func (c *Client) Snapshot() *Snapshot {
|
|
||||||
return &Snapshot{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save requests a new snapshot and provides an io.ReadCloser with the snapshot
|
|
||||||
// data to save. If this doesn't return an error, then it's the responsibility
|
|
||||||
// of the caller to close it. Only a subset of the QueryOptions are supported:
|
|
||||||
// Datacenter, AllowStale, and Token.
|
|
||||||
func (s *Snapshot) Save(q *QueryOptions) (io.ReadCloser, *QueryMeta, error) {
|
|
||||||
r := s.c.newRequest("GET", "/v1/snapshot")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
|
|
||||||
rtt, resp, err := requireOK(s.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
return resp.Body, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore streams in an existing snapshot and attempts to restore it.
|
|
||||||
func (s *Snapshot) Restore(q *WriteOptions, in io.Reader) error {
|
|
||||||
r := s.c.newRequest("PUT", "/v1/snapshot")
|
|
||||||
r.body = in
|
|
||||||
r.setWriteOptions(q)
|
|
||||||
_, _, err := requireOK(s.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
43
vendor/github.com/hashicorp/consul/api/status.go
generated
vendored
43
vendor/github.com/hashicorp/consul/api/status.go
generated
vendored
@ -1,43 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
// Status can be used to query the Status endpoints
|
|
||||||
type Status struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status returns a handle to the status endpoints
|
|
||||||
func (c *Client) Status() *Status {
|
|
||||||
return &Status{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Leader is used to query for a known leader
|
|
||||||
func (s *Status) Leader() (string, error) {
|
|
||||||
r := s.c.newRequest("GET", "/v1/status/leader")
|
|
||||||
_, resp, err := requireOK(s.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var leader string
|
|
||||||
if err := decodeBody(resp, &leader); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return leader, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Peers is used to query for a known raft peers
|
|
||||||
func (s *Status) Peers() ([]string, error) {
|
|
||||||
r := s.c.newRequest("GET", "/v1/status/peers")
|
|
||||||
_, resp, err := requireOK(s.c.doRequest(r))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var peers []string
|
|
||||||
if err := decodeBody(resp, &peers); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return peers, nil
|
|
||||||
}
|
|
230
vendor/github.com/hashicorp/consul/api/txn.go
generated
vendored
230
vendor/github.com/hashicorp/consul/api/txn.go
generated
vendored
@ -1,230 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Txn is used to manipulate the Txn API
|
|
||||||
type Txn struct {
|
|
||||||
c *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Txn is used to return a handle to the K/V apis
|
|
||||||
func (c *Client) Txn() *Txn {
|
|
||||||
return &Txn{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxnOp is the internal format we send to Consul. Currently only K/V and
|
|
||||||
// check operations are supported.
|
|
||||||
type TxnOp struct {
|
|
||||||
KV *KVTxnOp
|
|
||||||
Node *NodeTxnOp
|
|
||||||
Service *ServiceTxnOp
|
|
||||||
Check *CheckTxnOp
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxnOps is a list of transaction operations.
|
|
||||||
type TxnOps []*TxnOp
|
|
||||||
|
|
||||||
// TxnResult is the internal format we receive from Consul.
|
|
||||||
type TxnResult struct {
|
|
||||||
KV *KVPair
|
|
||||||
Node *Node
|
|
||||||
Service *CatalogService
|
|
||||||
Check *HealthCheck
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxnResults is a list of TxnResult objects.
|
|
||||||
type TxnResults []*TxnResult
|
|
||||||
|
|
||||||
// TxnError is used to return information about an operation in a transaction.
|
|
||||||
type TxnError struct {
|
|
||||||
OpIndex int
|
|
||||||
What string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxnErrors is a list of TxnError objects.
|
|
||||||
type TxnErrors []*TxnError
|
|
||||||
|
|
||||||
// TxnResponse is the internal format we receive from Consul.
|
|
||||||
type TxnResponse struct {
|
|
||||||
Results TxnResults
|
|
||||||
Errors TxnErrors
|
|
||||||
}
|
|
||||||
|
|
||||||
// KVOp constants give possible operations available in a transaction.
|
|
||||||
type KVOp string
|
|
||||||
|
|
||||||
const (
|
|
||||||
KVSet KVOp = "set"
|
|
||||||
KVDelete KVOp = "delete"
|
|
||||||
KVDeleteCAS KVOp = "delete-cas"
|
|
||||||
KVDeleteTree KVOp = "delete-tree"
|
|
||||||
KVCAS KVOp = "cas"
|
|
||||||
KVLock KVOp = "lock"
|
|
||||||
KVUnlock KVOp = "unlock"
|
|
||||||
KVGet KVOp = "get"
|
|
||||||
KVGetTree KVOp = "get-tree"
|
|
||||||
KVCheckSession KVOp = "check-session"
|
|
||||||
KVCheckIndex KVOp = "check-index"
|
|
||||||
KVCheckNotExists KVOp = "check-not-exists"
|
|
||||||
)
|
|
||||||
|
|
||||||
// KVTxnOp defines a single operation inside a transaction.
|
|
||||||
type KVTxnOp struct {
|
|
||||||
Verb KVOp
|
|
||||||
Key string
|
|
||||||
Value []byte
|
|
||||||
Flags uint64
|
|
||||||
Index uint64
|
|
||||||
Session string
|
|
||||||
}
|
|
||||||
|
|
||||||
// KVTxnOps defines a set of operations to be performed inside a single
|
|
||||||
// transaction.
|
|
||||||
type KVTxnOps []*KVTxnOp
|
|
||||||
|
|
||||||
// KVTxnResponse has the outcome of a transaction.
|
|
||||||
type KVTxnResponse struct {
|
|
||||||
Results []*KVPair
|
|
||||||
Errors TxnErrors
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeOp constants give possible operations available in a transaction.
|
|
||||||
type NodeOp string
|
|
||||||
|
|
||||||
const (
|
|
||||||
NodeGet NodeOp = "get"
|
|
||||||
NodeSet NodeOp = "set"
|
|
||||||
NodeCAS NodeOp = "cas"
|
|
||||||
NodeDelete NodeOp = "delete"
|
|
||||||
NodeDeleteCAS NodeOp = "delete-cas"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NodeTxnOp defines a single operation inside a transaction.
|
|
||||||
type NodeTxnOp struct {
|
|
||||||
Verb NodeOp
|
|
||||||
Node Node
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceOp constants give possible operations available in a transaction.
|
|
||||||
type ServiceOp string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ServiceGet ServiceOp = "get"
|
|
||||||
ServiceSet ServiceOp = "set"
|
|
||||||
ServiceCAS ServiceOp = "cas"
|
|
||||||
ServiceDelete ServiceOp = "delete"
|
|
||||||
ServiceDeleteCAS ServiceOp = "delete-cas"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ServiceTxnOp defines a single operation inside a transaction.
|
|
||||||
type ServiceTxnOp struct {
|
|
||||||
Verb ServiceOp
|
|
||||||
Node string
|
|
||||||
Service AgentService
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckOp constants give possible operations available in a transaction.
|
|
||||||
type CheckOp string
|
|
||||||
|
|
||||||
const (
|
|
||||||
CheckGet CheckOp = "get"
|
|
||||||
CheckSet CheckOp = "set"
|
|
||||||
CheckCAS CheckOp = "cas"
|
|
||||||
CheckDelete CheckOp = "delete"
|
|
||||||
CheckDeleteCAS CheckOp = "delete-cas"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CheckTxnOp defines a single operation inside a transaction.
|
|
||||||
type CheckTxnOp struct {
|
|
||||||
Verb CheckOp
|
|
||||||
Check HealthCheck
|
|
||||||
}
|
|
||||||
|
|
||||||
// Txn is used to apply multiple Consul operations in a single, atomic transaction.
|
|
||||||
//
|
|
||||||
// Note that Go will perform the required base64 encoding on the values
|
|
||||||
// automatically because the type is a byte slice. Transactions are defined as a
|
|
||||||
// list of operations to perform, using the different fields in the TxnOp structure
|
|
||||||
// to define operations. If any operation fails, none of the changes are applied
|
|
||||||
// to the state store.
|
|
||||||
//
|
|
||||||
// Even though this is generally a write operation, we take a QueryOptions input
|
|
||||||
// and return a QueryMeta output. If the transaction contains only read ops, then
|
|
||||||
// Consul will fast-path it to a different endpoint internally which supports
|
|
||||||
// consistency controls, but not blocking. If there are write operations then
|
|
||||||
// the request will always be routed through raft and any consistency settings
|
|
||||||
// will be ignored.
|
|
||||||
//
|
|
||||||
// Here's an example:
|
|
||||||
//
|
|
||||||
// ops := KVTxnOps{
|
|
||||||
// &KVTxnOp{
|
|
||||||
// Verb: KVLock,
|
|
||||||
// Key: "test/lock",
|
|
||||||
// Session: "adf4238a-882b-9ddc-4a9d-5b6758e4159e",
|
|
||||||
// Value: []byte("hello"),
|
|
||||||
// },
|
|
||||||
// &KVTxnOp{
|
|
||||||
// Verb: KVGet,
|
|
||||||
// Key: "another/key",
|
|
||||||
// },
|
|
||||||
// &CheckTxnOp{
|
|
||||||
// Verb: CheckSet,
|
|
||||||
// HealthCheck: HealthCheck{
|
|
||||||
// Node: "foo",
|
|
||||||
// CheckID: "redis:a",
|
|
||||||
// Name: "Redis Health Check",
|
|
||||||
// Status: "passing",
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ok, response, _, err := kv.Txn(&ops, nil)
|
|
||||||
//
|
|
||||||
// If there is a problem making the transaction request then an error will be
|
|
||||||
// returned. Otherwise, the ok value will be true if the transaction succeeded
|
|
||||||
// or false if it was rolled back. The response is a structured return value which
|
|
||||||
// will have the outcome of the transaction. Its Results member will have entries
|
|
||||||
// for each operation. For KV operations, Deleted keys will have a nil entry in the
|
|
||||||
// results, and to save space, the Value of each key in the Results will be nil
|
|
||||||
// unless the operation is a KVGet. If the transaction was rolled back, the Errors
|
|
||||||
// member will have entries referencing the index of the operation that failed
|
|
||||||
// along with an error message.
|
|
||||||
func (t *Txn) Txn(txn TxnOps, q *QueryOptions) (bool, *TxnResponse, *QueryMeta, error) {
|
|
||||||
return t.c.txn(txn, q)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) txn(txn TxnOps, q *QueryOptions) (bool, *TxnResponse, *QueryMeta, error) {
|
|
||||||
r := c.newRequest("PUT", "/v1/txn")
|
|
||||||
r.setQueryOptions(q)
|
|
||||||
|
|
||||||
r.obj = txn
|
|
||||||
rtt, resp, err := c.doRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
qm := &QueryMeta{}
|
|
||||||
parseQueryMeta(resp, qm)
|
|
||||||
qm.RequestTime = rtt
|
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusConflict {
|
|
||||||
var txnResp TxnResponse
|
|
||||||
if err := decodeBody(resp, &txnResp); err != nil {
|
|
||||||
return false, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp.StatusCode == http.StatusOK, &txnResp, qm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if _, err := io.Copy(&buf, resp.Body); err != nil {
|
|
||||||
return false, nil, nil, fmt.Errorf("Failed to read response: %v", err)
|
|
||||||
}
|
|
||||||
return false, nil, nil, fmt.Errorf("Failed request: %s", buf.String())
|
|
||||||
}
|
|
349
vendor/github.com/hashicorp/consul/api/watch/funcs.go
generated
vendored
349
vendor/github.com/hashicorp/consul/api/watch/funcs.go
generated
vendored
@ -1,349 +0,0 @@
|
|||||||
package watch
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
consulapi "github.com/hashicorp/consul/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// watchFactory is a function that can create a new WatchFunc
|
|
||||||
// from a parameter configuration
|
|
||||||
type watchFactory func(params map[string]interface{}) (WatcherFunc, error)
|
|
||||||
|
|
||||||
// watchFuncFactory maps each type to a factory function
|
|
||||||
var watchFuncFactory map[string]watchFactory
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
watchFuncFactory = map[string]watchFactory{
|
|
||||||
"key": keyWatch,
|
|
||||||
"keyprefix": keyPrefixWatch,
|
|
||||||
"services": servicesWatch,
|
|
||||||
"nodes": nodesWatch,
|
|
||||||
"service": serviceWatch,
|
|
||||||
"checks": checksWatch,
|
|
||||||
"event": eventWatch,
|
|
||||||
"connect_roots": connectRootsWatch,
|
|
||||||
"connect_leaf": connectLeafWatch,
|
|
||||||
"connect_proxy_config": connectProxyConfigWatch,
|
|
||||||
"agent_service": agentServiceWatch,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// keyWatch is used to return a key watching function
|
|
||||||
func keyWatch(params map[string]interface{}) (WatcherFunc, error) {
|
|
||||||
stale := false
|
|
||||||
if err := assignValueBool(params, "stale", &stale); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var key string
|
|
||||||
if err := assignValue(params, "key", &key); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if key == "" {
|
|
||||||
return nil, fmt.Errorf("Must specify a single key to watch")
|
|
||||||
}
|
|
||||||
fn := func(p *Plan) (BlockingParamVal, interface{}, error) {
|
|
||||||
kv := p.client.KV()
|
|
||||||
opts := makeQueryOptionsWithContext(p, stale)
|
|
||||||
defer p.cancelFunc()
|
|
||||||
pair, meta, err := kv.Get(key, &opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if pair == nil {
|
|
||||||
return WaitIndexVal(meta.LastIndex), nil, err
|
|
||||||
}
|
|
||||||
return WaitIndexVal(meta.LastIndex), pair, err
|
|
||||||
}
|
|
||||||
return fn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// keyPrefixWatch is used to return a key prefix watching function
|
|
||||||
func keyPrefixWatch(params map[string]interface{}) (WatcherFunc, error) {
|
|
||||||
stale := false
|
|
||||||
if err := assignValueBool(params, "stale", &stale); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var prefix string
|
|
||||||
if err := assignValue(params, "prefix", &prefix); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if prefix == "" {
|
|
||||||
return nil, fmt.Errorf("Must specify a single prefix to watch")
|
|
||||||
}
|
|
||||||
fn := func(p *Plan) (BlockingParamVal, interface{}, error) {
|
|
||||||
kv := p.client.KV()
|
|
||||||
opts := makeQueryOptionsWithContext(p, stale)
|
|
||||||
defer p.cancelFunc()
|
|
||||||
pairs, meta, err := kv.List(prefix, &opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return WaitIndexVal(meta.LastIndex), pairs, err
|
|
||||||
}
|
|
||||||
return fn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// servicesWatch is used to watch the list of available services
|
|
||||||
func servicesWatch(params map[string]interface{}) (WatcherFunc, error) {
|
|
||||||
stale := false
|
|
||||||
if err := assignValueBool(params, "stale", &stale); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fn := func(p *Plan) (BlockingParamVal, interface{}, error) {
|
|
||||||
catalog := p.client.Catalog()
|
|
||||||
opts := makeQueryOptionsWithContext(p, stale)
|
|
||||||
defer p.cancelFunc()
|
|
||||||
services, meta, err := catalog.Services(&opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return WaitIndexVal(meta.LastIndex), services, err
|
|
||||||
}
|
|
||||||
return fn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// nodesWatch is used to watch the list of available nodes
|
|
||||||
func nodesWatch(params map[string]interface{}) (WatcherFunc, error) {
|
|
||||||
stale := false
|
|
||||||
if err := assignValueBool(params, "stale", &stale); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fn := func(p *Plan) (BlockingParamVal, interface{}, error) {
|
|
||||||
catalog := p.client.Catalog()
|
|
||||||
opts := makeQueryOptionsWithContext(p, stale)
|
|
||||||
defer p.cancelFunc()
|
|
||||||
nodes, meta, err := catalog.Nodes(&opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return WaitIndexVal(meta.LastIndex), nodes, err
|
|
||||||
}
|
|
||||||
return fn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// serviceWatch is used to watch a specific service for changes
|
|
||||||
func serviceWatch(params map[string]interface{}) (WatcherFunc, error) {
|
|
||||||
stale := false
|
|
||||||
if err := assignValueBool(params, "stale", &stale); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
service string
|
|
||||||
tags []string
|
|
||||||
)
|
|
||||||
if err := assignValue(params, "service", &service); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if service == "" {
|
|
||||||
return nil, fmt.Errorf("Must specify a single service to watch")
|
|
||||||
}
|
|
||||||
if err := assignValueStringSlice(params, "tag", &tags); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
passingOnly := false
|
|
||||||
if err := assignValueBool(params, "passingonly", &passingOnly); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fn := func(p *Plan) (BlockingParamVal, interface{}, error) {
|
|
||||||
health := p.client.Health()
|
|
||||||
opts := makeQueryOptionsWithContext(p, stale)
|
|
||||||
defer p.cancelFunc()
|
|
||||||
nodes, meta, err := health.ServiceMultipleTags(service, tags, passingOnly, &opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return WaitIndexVal(meta.LastIndex), nodes, err
|
|
||||||
}
|
|
||||||
return fn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checksWatch is used to watch a specific checks in a given state
|
|
||||||
func checksWatch(params map[string]interface{}) (WatcherFunc, error) {
|
|
||||||
stale := false
|
|
||||||
if err := assignValueBool(params, "stale", &stale); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var service, state string
|
|
||||||
if err := assignValue(params, "service", &service); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := assignValue(params, "state", &state); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if service != "" && state != "" {
|
|
||||||
return nil, fmt.Errorf("Cannot specify service and state")
|
|
||||||
}
|
|
||||||
if service == "" && state == "" {
|
|
||||||
state = "any"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn := func(p *Plan) (BlockingParamVal, interface{}, error) {
|
|
||||||
health := p.client.Health()
|
|
||||||
opts := makeQueryOptionsWithContext(p, stale)
|
|
||||||
defer p.cancelFunc()
|
|
||||||
var checks []*consulapi.HealthCheck
|
|
||||||
var meta *consulapi.QueryMeta
|
|
||||||
var err error
|
|
||||||
if state != "" {
|
|
||||||
checks, meta, err = health.State(state, &opts)
|
|
||||||
} else {
|
|
||||||
checks, meta, err = health.Checks(service, &opts)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return WaitIndexVal(meta.LastIndex), checks, err
|
|
||||||
}
|
|
||||||
return fn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// eventWatch is used to watch for events, optionally filtering on name
|
|
||||||
func eventWatch(params map[string]interface{}) (WatcherFunc, error) {
|
|
||||||
// The stale setting doesn't apply to events.
|
|
||||||
|
|
||||||
var name string
|
|
||||||
if err := assignValue(params, "name", &name); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fn := func(p *Plan) (BlockingParamVal, interface{}, error) {
|
|
||||||
event := p.client.Event()
|
|
||||||
opts := makeQueryOptionsWithContext(p, false)
|
|
||||||
defer p.cancelFunc()
|
|
||||||
events, meta, err := event.List(name, &opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prune to only the new events
|
|
||||||
for i := 0; i < len(events); i++ {
|
|
||||||
if WaitIndexVal(event.IDToIndex(events[i].ID)).Equal(p.lastParamVal) {
|
|
||||||
events = events[i+1:]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return WaitIndexVal(meta.LastIndex), events, err
|
|
||||||
}
|
|
||||||
return fn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// connectRootsWatch is used to watch for changes to Connect Root certificates.
|
|
||||||
func connectRootsWatch(params map[string]interface{}) (WatcherFunc, error) {
|
|
||||||
// We don't support stale since roots are cached locally in the agent.
|
|
||||||
|
|
||||||
fn := func(p *Plan) (BlockingParamVal, interface{}, error) {
|
|
||||||
agent := p.client.Agent()
|
|
||||||
opts := makeQueryOptionsWithContext(p, false)
|
|
||||||
defer p.cancelFunc()
|
|
||||||
|
|
||||||
roots, meta, err := agent.ConnectCARoots(&opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return WaitIndexVal(meta.LastIndex), roots, err
|
|
||||||
}
|
|
||||||
return fn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// connectLeafWatch is used to watch for changes to Connect Leaf certificates
|
|
||||||
// for given local service id.
|
|
||||||
func connectLeafWatch(params map[string]interface{}) (WatcherFunc, error) {
|
|
||||||
// We don't support stale since certs are cached locally in the agent.
|
|
||||||
|
|
||||||
var serviceName string
|
|
||||||
if err := assignValue(params, "service", &serviceName); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fn := func(p *Plan) (BlockingParamVal, interface{}, error) {
|
|
||||||
agent := p.client.Agent()
|
|
||||||
opts := makeQueryOptionsWithContext(p, false)
|
|
||||||
defer p.cancelFunc()
|
|
||||||
|
|
||||||
leaf, meta, err := agent.ConnectCALeaf(serviceName, &opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return WaitIndexVal(meta.LastIndex), leaf, err
|
|
||||||
}
|
|
||||||
return fn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// connectProxyConfigWatch is used to watch for changes to Connect managed proxy
|
|
||||||
// configuration. Note that this state is agent-local so the watch mechanism
|
|
||||||
// uses `hash` rather than `index` for deciding whether to block.
|
|
||||||
func connectProxyConfigWatch(params map[string]interface{}) (WatcherFunc, error) {
|
|
||||||
// We don't support consistency modes since it's agent local data
|
|
||||||
|
|
||||||
var proxyServiceID string
|
|
||||||
if err := assignValue(params, "proxy_service_id", &proxyServiceID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fn := func(p *Plan) (BlockingParamVal, interface{}, error) {
|
|
||||||
agent := p.client.Agent()
|
|
||||||
opts := makeQueryOptionsWithContext(p, false)
|
|
||||||
defer p.cancelFunc()
|
|
||||||
|
|
||||||
config, _, err := agent.ConnectProxyConfig(proxyServiceID, &opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return string ContentHash since we don't have Raft indexes to block on.
|
|
||||||
return WaitHashVal(config.ContentHash), config, err
|
|
||||||
}
|
|
||||||
return fn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// agentServiceWatch is used to watch for changes to a single service instance
|
|
||||||
// on the local agent. Note that this state is agent-local so the watch
|
|
||||||
// mechanism uses `hash` rather than `index` for deciding whether to block.
|
|
||||||
func agentServiceWatch(params map[string]interface{}) (WatcherFunc, error) {
|
|
||||||
// We don't support consistency modes since it's agent local data
|
|
||||||
|
|
||||||
var serviceID string
|
|
||||||
if err := assignValue(params, "service_id", &serviceID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fn := func(p *Plan) (BlockingParamVal, interface{}, error) {
|
|
||||||
agent := p.client.Agent()
|
|
||||||
opts := makeQueryOptionsWithContext(p, false)
|
|
||||||
defer p.cancelFunc()
|
|
||||||
|
|
||||||
svc, _, err := agent.Service(serviceID, &opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return string ContentHash since we don't have Raft indexes to block on.
|
|
||||||
return WaitHashVal(svc.ContentHash), svc, err
|
|
||||||
}
|
|
||||||
return fn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeQueryOptionsWithContext(p *Plan, stale bool) consulapi.QueryOptions {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
p.setCancelFunc(cancel)
|
|
||||||
opts := consulapi.QueryOptions{AllowStale: stale}
|
|
||||||
switch param := p.lastParamVal.(type) {
|
|
||||||
case WaitIndexVal:
|
|
||||||
opts.WaitIndex = uint64(param)
|
|
||||||
case WaitHashVal:
|
|
||||||
opts.WaitHash = string(param)
|
|
||||||
}
|
|
||||||
return *opts.WithContext(ctx)
|
|
||||||
}
|
|
167
vendor/github.com/hashicorp/consul/api/watch/plan.go
generated
vendored
167
vendor/github.com/hashicorp/consul/api/watch/plan.go
generated
vendored
@ -1,167 +0,0 @@
|
|||||||
package watch
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
consulapi "github.com/hashicorp/consul/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// retryInterval is the base retry value
|
|
||||||
retryInterval = 5 * time.Second
|
|
||||||
|
|
||||||
// maximum back off time, this is to prevent
|
|
||||||
// exponential runaway
|
|
||||||
maxBackoffTime = 180 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
func (p *Plan) Run(address string) error {
|
|
||||||
return p.RunWithConfig(address, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run is used to run a watch plan
|
|
||||||
func (p *Plan) RunWithConfig(address string, conf *consulapi.Config) error {
|
|
||||||
// Setup the client
|
|
||||||
p.address = address
|
|
||||||
if conf == nil {
|
|
||||||
conf = consulapi.DefaultConfig()
|
|
||||||
}
|
|
||||||
conf.Address = address
|
|
||||||
conf.Datacenter = p.Datacenter
|
|
||||||
conf.Token = p.Token
|
|
||||||
client, err := consulapi.NewClient(conf)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to connect to agent: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the logger
|
|
||||||
output := p.LogOutput
|
|
||||||
if output == nil {
|
|
||||||
output = os.Stderr
|
|
||||||
}
|
|
||||||
logger := log.New(output, "", log.LstdFlags)
|
|
||||||
|
|
||||||
return p.RunWithClientAndLogger(client, logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunWithClientAndLogger runs a watch plan using an external client and
|
|
||||||
// log.Logger instance. Using this, the plan's Datacenter, Token and LogOutput
|
|
||||||
// fields are ignored and the passed client is expected to be configured as
|
|
||||||
// needed.
|
|
||||||
func (p *Plan) RunWithClientAndLogger(client *consulapi.Client,
|
|
||||||
logger *log.Logger) error {
|
|
||||||
|
|
||||||
p.client = client
|
|
||||||
|
|
||||||
// Loop until we are canceled
|
|
||||||
failures := 0
|
|
||||||
OUTER:
|
|
||||||
for !p.shouldStop() {
|
|
||||||
// Invoke the handler
|
|
||||||
blockParamVal, result, err := p.Watcher(p)
|
|
||||||
|
|
||||||
// Check if we should terminate since the function
|
|
||||||
// could have blocked for a while
|
|
||||||
if p.shouldStop() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle an error in the watch function
|
|
||||||
if err != nil {
|
|
||||||
// Perform an exponential backoff
|
|
||||||
failures++
|
|
||||||
if blockParamVal == nil {
|
|
||||||
p.lastParamVal = nil
|
|
||||||
} else {
|
|
||||||
p.lastParamVal = blockParamVal.Next(p.lastParamVal)
|
|
||||||
}
|
|
||||||
retry := retryInterval * time.Duration(failures*failures)
|
|
||||||
if retry > maxBackoffTime {
|
|
||||||
retry = maxBackoffTime
|
|
||||||
}
|
|
||||||
logger.Printf("[ERR] consul.watch: Watch (type: %s) errored: %v, retry in %v",
|
|
||||||
p.Type, err, retry)
|
|
||||||
select {
|
|
||||||
case <-time.After(retry):
|
|
||||||
continue OUTER
|
|
||||||
case <-p.stopCh:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear the failures
|
|
||||||
failures = 0
|
|
||||||
|
|
||||||
// If the index is unchanged do nothing
|
|
||||||
if p.lastParamVal != nil && p.lastParamVal.Equal(blockParamVal) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the index, look for change
|
|
||||||
oldParamVal := p.lastParamVal
|
|
||||||
p.lastParamVal = blockParamVal.Next(oldParamVal)
|
|
||||||
if oldParamVal != nil && reflect.DeepEqual(p.lastResult, result) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the updated result
|
|
||||||
p.lastResult = result
|
|
||||||
// If a hybrid handler exists use that
|
|
||||||
if p.HybridHandler != nil {
|
|
||||||
p.HybridHandler(blockParamVal, result)
|
|
||||||
} else if p.Handler != nil {
|
|
||||||
idx, ok := blockParamVal.(WaitIndexVal)
|
|
||||||
if !ok {
|
|
||||||
logger.Printf("[ERR] consul.watch: Handler only supports index-based " +
|
|
||||||
" watches but non index-based watch run. Skipping Handler.")
|
|
||||||
}
|
|
||||||
p.Handler(uint64(idx), result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop is used to stop running the watch plan
|
|
||||||
func (p *Plan) Stop() {
|
|
||||||
p.stopLock.Lock()
|
|
||||||
defer p.stopLock.Unlock()
|
|
||||||
if p.stop {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.stop = true
|
|
||||||
if p.cancelFunc != nil {
|
|
||||||
p.cancelFunc()
|
|
||||||
}
|
|
||||||
close(p.stopCh)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Plan) shouldStop() bool {
|
|
||||||
select {
|
|
||||||
case <-p.stopCh:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Plan) setCancelFunc(cancel context.CancelFunc) {
|
|
||||||
p.stopLock.Lock()
|
|
||||||
defer p.stopLock.Unlock()
|
|
||||||
if p.shouldStop() {
|
|
||||||
// The watch is stopped and execute the new cancel func to stop watchFactory
|
|
||||||
cancel()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.cancelFunc = cancel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Plan) IsStopped() bool {
|
|
||||||
p.stopLock.Lock()
|
|
||||||
defer p.stopLock.Unlock()
|
|
||||||
return p.stop
|
|
||||||
}
|
|
289
vendor/github.com/hashicorp/consul/api/watch/watch.go
generated
vendored
289
vendor/github.com/hashicorp/consul/api/watch/watch.go
generated
vendored
@ -1,289 +0,0 @@
|
|||||||
package watch
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
consulapi "github.com/hashicorp/consul/api"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
)
|
|
||||||
|
|
||||||
const DefaultTimeout = 10 * time.Second
|
|
||||||
|
|
||||||
// Plan is the parsed version of a watch specification. A watch provides
|
|
||||||
// the details of a query, which generates a view into the Consul data store.
|
|
||||||
// This view is watched for changes and a handler is invoked to take any
|
|
||||||
// appropriate actions.
|
|
||||||
type Plan struct {
|
|
||||||
Datacenter string
|
|
||||||
Token string
|
|
||||||
Type string
|
|
||||||
HandlerType string
|
|
||||||
Exempt map[string]interface{}
|
|
||||||
|
|
||||||
Watcher WatcherFunc
|
|
||||||
// Handler is kept for backward compatibility but only supports watches based
|
|
||||||
// on index param. To support hash based watches, set HybridHandler instead.
|
|
||||||
Handler HandlerFunc
|
|
||||||
HybridHandler HybridHandlerFunc
|
|
||||||
LogOutput io.Writer
|
|
||||||
|
|
||||||
address string
|
|
||||||
client *consulapi.Client
|
|
||||||
lastParamVal BlockingParamVal
|
|
||||||
lastResult interface{}
|
|
||||||
|
|
||||||
stop bool
|
|
||||||
stopCh chan struct{}
|
|
||||||
stopLock sync.Mutex
|
|
||||||
cancelFunc context.CancelFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
type HttpHandlerConfig struct {
|
|
||||||
Path string `mapstructure:"path"`
|
|
||||||
Method string `mapstructure:"method"`
|
|
||||||
Timeout time.Duration `mapstructure:"-"`
|
|
||||||
TimeoutRaw string `mapstructure:"timeout"`
|
|
||||||
Header map[string][]string `mapstructure:"header"`
|
|
||||||
TLSSkipVerify bool `mapstructure:"tls_skip_verify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockingParamVal is an interface representing the common operations needed for
|
|
||||||
// different styles of blocking. It's used to abstract the core watch plan from
|
|
||||||
// whether we are performing index-based or hash-based blocking.
|
|
||||||
type BlockingParamVal interface {
|
|
||||||
// Equal returns whether the other param value should be considered equal
|
|
||||||
// (i.e. representing no change in the watched resource). Equal must not panic
|
|
||||||
// if other is nil.
|
|
||||||
Equal(other BlockingParamVal) bool
|
|
||||||
|
|
||||||
// Next is called when deciding which value to use on the next blocking call.
|
|
||||||
// It assumes the BlockingParamVal value it is called on is the most recent one
|
|
||||||
// returned and passes the previous one which may be nil as context. This
|
|
||||||
// allows types to customize logic around ordering without assuming there is
|
|
||||||
// an order. For example WaitIndexVal can check that the index didn't go
|
|
||||||
// backwards and if it did then reset to 0. Most other cases should just
|
|
||||||
// return themselves (the most recent value) to be used in the next request.
|
|
||||||
Next(previous BlockingParamVal) BlockingParamVal
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitIndexVal is a type representing a Consul index that implements
|
|
||||||
// BlockingParamVal.
|
|
||||||
type WaitIndexVal uint64
|
|
||||||
|
|
||||||
// Equal implements BlockingParamVal
|
|
||||||
func (idx WaitIndexVal) Equal(other BlockingParamVal) bool {
|
|
||||||
if otherIdx, ok := other.(WaitIndexVal); ok {
|
|
||||||
return idx == otherIdx
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next implements BlockingParamVal
|
|
||||||
func (idx WaitIndexVal) Next(previous BlockingParamVal) BlockingParamVal {
|
|
||||||
if previous == nil {
|
|
||||||
return idx
|
|
||||||
}
|
|
||||||
prevIdx, ok := previous.(WaitIndexVal)
|
|
||||||
if ok && prevIdx > idx {
|
|
||||||
// This value is smaller than the previous index, reset.
|
|
||||||
return WaitIndexVal(0)
|
|
||||||
}
|
|
||||||
return idx
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitHashVal is a type representing a Consul content hash that implements
|
|
||||||
// BlockingParamVal.
|
|
||||||
type WaitHashVal string
|
|
||||||
|
|
||||||
// Equal implements BlockingParamVal
|
|
||||||
func (h WaitHashVal) Equal(other BlockingParamVal) bool {
|
|
||||||
if otherHash, ok := other.(WaitHashVal); ok {
|
|
||||||
return h == otherHash
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next implements BlockingParamVal
|
|
||||||
func (h WaitHashVal) Next(previous BlockingParamVal) BlockingParamVal {
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatcherFunc is used to watch for a diff.
|
|
||||||
type WatcherFunc func(*Plan) (BlockingParamVal, interface{}, error)
|
|
||||||
|
|
||||||
// HandlerFunc is used to handle new data. It only works for index-based watches
|
|
||||||
// (which is almost all end points currently) and is kept for backwards
|
|
||||||
// compatibility until more places can make use of hash-based watches too.
|
|
||||||
type HandlerFunc func(uint64, interface{})
|
|
||||||
|
|
||||||
// HybridHandlerFunc is used to handle new data. It can support either
|
|
||||||
// index-based or hash-based watches via the BlockingParamVal.
|
|
||||||
type HybridHandlerFunc func(BlockingParamVal, interface{})
|
|
||||||
|
|
||||||
// Parse takes a watch query and compiles it into a WatchPlan or an error
|
|
||||||
func Parse(params map[string]interface{}) (*Plan, error) {
|
|
||||||
return ParseExempt(params, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseExempt takes a watch query and compiles it into a WatchPlan or an error
|
|
||||||
// Any exempt parameters are stored in the Exempt map
|
|
||||||
func ParseExempt(params map[string]interface{}, exempt []string) (*Plan, error) {
|
|
||||||
plan := &Plan{
|
|
||||||
stopCh: make(chan struct{}),
|
|
||||||
Exempt: make(map[string]interface{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the generic parameters
|
|
||||||
if err := assignValue(params, "datacenter", &plan.Datacenter); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := assignValue(params, "token", &plan.Token); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := assignValue(params, "type", &plan.Type); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Ensure there is a watch type
|
|
||||||
if plan.Type == "" {
|
|
||||||
return nil, fmt.Errorf("Watch type must be specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the specific handler
|
|
||||||
if err := assignValue(params, "handler_type", &plan.HandlerType); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch plan.HandlerType {
|
|
||||||
case "http":
|
|
||||||
if _, ok := params["http_handler_config"]; !ok {
|
|
||||||
return nil, fmt.Errorf("Handler type 'http' requires 'http_handler_config' to be set")
|
|
||||||
}
|
|
||||||
config, err := parseHttpHandlerConfig(params["http_handler_config"])
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(fmt.Sprintf("Failed to parse 'http_handler_config': %v", err))
|
|
||||||
}
|
|
||||||
plan.Exempt["http_handler_config"] = config
|
|
||||||
delete(params, "http_handler_config")
|
|
||||||
|
|
||||||
case "script":
|
|
||||||
// Let the caller check for configuration in exempt parameters
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for a factory function
|
|
||||||
factory := watchFuncFactory[plan.Type]
|
|
||||||
if factory == nil {
|
|
||||||
return nil, fmt.Errorf("Unsupported watch type: %s", plan.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the watch func
|
|
||||||
fn, err := factory(params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
plan.Watcher = fn
|
|
||||||
|
|
||||||
// Remove the exempt parameters
|
|
||||||
if len(exempt) > 0 {
|
|
||||||
for _, ex := range exempt {
|
|
||||||
val, ok := params[ex]
|
|
||||||
if ok {
|
|
||||||
plan.Exempt[ex] = val
|
|
||||||
delete(params, ex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure all parameters are consumed
|
|
||||||
if len(params) != 0 {
|
|
||||||
var bad []string
|
|
||||||
for key := range params {
|
|
||||||
bad = append(bad, key)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("Invalid parameters: %v", bad)
|
|
||||||
}
|
|
||||||
return plan, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// assignValue is used to extract a value ensuring it is a string
|
|
||||||
func assignValue(params map[string]interface{}, name string, out *string) error {
|
|
||||||
if raw, ok := params[name]; ok {
|
|
||||||
val, ok := raw.(string)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("Expecting %s to be a string", name)
|
|
||||||
}
|
|
||||||
*out = val
|
|
||||||
delete(params, name)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// assignValueBool is used to extract a value ensuring it is a bool
|
|
||||||
func assignValueBool(params map[string]interface{}, name string, out *bool) error {
|
|
||||||
if raw, ok := params[name]; ok {
|
|
||||||
val, ok := raw.(bool)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("Expecting %s to be a boolean", name)
|
|
||||||
}
|
|
||||||
*out = val
|
|
||||||
delete(params, name)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// assignValueStringSlice is used to extract a value ensuring it is either a string or a slice of strings
|
|
||||||
func assignValueStringSlice(params map[string]interface{}, name string, out *[]string) error {
|
|
||||||
if raw, ok := params[name]; ok {
|
|
||||||
var tmp []string
|
|
||||||
switch raw.(type) {
|
|
||||||
case string:
|
|
||||||
tmp = make([]string, 1, 1)
|
|
||||||
tmp[0] = raw.(string)
|
|
||||||
case []string:
|
|
||||||
l := len(raw.([]string))
|
|
||||||
tmp = make([]string, l, l)
|
|
||||||
copy(tmp, raw.([]string))
|
|
||||||
case []interface{}:
|
|
||||||
l := len(raw.([]interface{}))
|
|
||||||
tmp = make([]string, l, l)
|
|
||||||
for i, v := range raw.([]interface{}) {
|
|
||||||
if s, ok := v.(string); ok {
|
|
||||||
tmp[i] = s
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("Index %d of %s expected to be string", i, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Expecting %s to be a string or []string", name)
|
|
||||||
}
|
|
||||||
*out = tmp
|
|
||||||
delete(params, name)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the 'http_handler_config' parameters
|
|
||||||
func parseHttpHandlerConfig(configParams interface{}) (*HttpHandlerConfig, error) {
|
|
||||||
var config HttpHandlerConfig
|
|
||||||
if err := mapstructure.Decode(configParams, &config); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Path == "" {
|
|
||||||
return nil, fmt.Errorf("Requires 'path' to be set")
|
|
||||||
}
|
|
||||||
if config.Method == "" {
|
|
||||||
config.Method = "POST"
|
|
||||||
}
|
|
||||||
if config.TimeoutRaw == "" {
|
|
||||||
config.Timeout = DefaultTimeout
|
|
||||||
} else if timeout, err := time.ParseDuration(config.TimeoutRaw); err != nil {
|
|
||||||
return nil, fmt.Errorf(fmt.Sprintf("Failed to parse timeout: %v", err))
|
|
||||||
} else {
|
|
||||||
config.Timeout = timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
return &config, nil
|
|
||||||
}
|
|
139
vendor/github.com/hashicorp/consul/sdk/freeport/freeport.go
generated
vendored
139
vendor/github.com/hashicorp/consul/sdk/freeport/freeport.go
generated
vendored
@ -1,139 +0,0 @@
|
|||||||
// Package freeport provides a helper for allocating free ports across multiple
|
|
||||||
// processes on the same machine.
|
|
||||||
package freeport
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mitchellh/go-testing-interface"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// blockSize is the size of the allocated port block. ports are given out
|
|
||||||
// consecutively from that block with roll-over for the lifetime of the
|
|
||||||
// application/test run.
|
|
||||||
blockSize = 1500
|
|
||||||
|
|
||||||
// maxBlocks is the number of available port blocks.
|
|
||||||
// lowPort + maxBlocks * blockSize must be less than 65535.
|
|
||||||
maxBlocks = 30
|
|
||||||
|
|
||||||
// lowPort is the lowest port number that should be used.
|
|
||||||
lowPort = 10000
|
|
||||||
|
|
||||||
// attempts is how often we try to allocate a port block
|
|
||||||
// before giving up.
|
|
||||||
attempts = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// firstPort is the first port of the allocated block.
|
|
||||||
firstPort int
|
|
||||||
|
|
||||||
// lockLn is the system-wide mutex for the port block.
|
|
||||||
lockLn net.Listener
|
|
||||||
|
|
||||||
// mu guards nextPort
|
|
||||||
mu sync.Mutex
|
|
||||||
|
|
||||||
// once is used to do the initialization on the first call to retrieve free
|
|
||||||
// ports
|
|
||||||
once sync.Once
|
|
||||||
|
|
||||||
// port is the last allocated port.
|
|
||||||
port int
|
|
||||||
)
|
|
||||||
|
|
||||||
// initialize is used to initialize freeport.
|
|
||||||
func initialize() {
|
|
||||||
if lowPort+maxBlocks*blockSize > 65535 {
|
|
||||||
panic("freeport: block size too big or too many blocks requested")
|
|
||||||
}
|
|
||||||
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
firstPort, lockLn = alloc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// alloc reserves a port block for exclusive use for the lifetime of the
|
|
||||||
// application. lockLn serves as a system-wide mutex for the port block and is
|
|
||||||
// implemented as a TCP listener which is bound to the firstPort and which will
|
|
||||||
// be automatically released when the application terminates.
|
|
||||||
func alloc() (int, net.Listener) {
|
|
||||||
for i := 0; i < attempts; i++ {
|
|
||||||
block := int(rand.Int31n(int32(maxBlocks)))
|
|
||||||
firstPort := lowPort + block*blockSize
|
|
||||||
ln, err := net.ListenTCP("tcp", tcpAddr("127.0.0.1", firstPort))
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// log.Printf("[DEBUG] freeport: allocated port block %d (%d-%d)", block, firstPort, firstPort+blockSize-1)
|
|
||||||
return firstPort, ln
|
|
||||||
}
|
|
||||||
panic("freeport: cannot allocate port block")
|
|
||||||
}
|
|
||||||
|
|
||||||
func tcpAddr(ip string, port int) *net.TCPAddr {
|
|
||||||
return &net.TCPAddr{IP: net.ParseIP(ip), Port: port}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get wraps the Free function and panics on any failure retrieving ports.
|
|
||||||
func Get(n int) (ports []int) {
|
|
||||||
ports, err := Free(n)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ports
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetT is suitable for use when retrieving unused ports in tests. If there is
|
|
||||||
// an error retrieving free ports, the test will be failed.
|
|
||||||
func GetT(t testing.T, n int) (ports []int) {
|
|
||||||
ports, err := Free(n)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed retrieving free port: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ports
|
|
||||||
}
|
|
||||||
|
|
||||||
// Free returns a list of free ports from the allocated port block. It is safe
|
|
||||||
// to call this method concurrently. Ports have been tested to be available on
|
|
||||||
// 127.0.0.1 TCP but there is no guarantee that they will remain free in the
|
|
||||||
// future.
|
|
||||||
func Free(n int) (ports []int, err error) {
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
|
|
||||||
if n > blockSize-1 {
|
|
||||||
return nil, fmt.Errorf("freeport: block size too small")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reserve a port block
|
|
||||||
once.Do(initialize)
|
|
||||||
|
|
||||||
for len(ports) < n {
|
|
||||||
port++
|
|
||||||
|
|
||||||
// roll-over the port
|
|
||||||
if port < firstPort+1 || port >= firstPort+blockSize {
|
|
||||||
port = firstPort + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the port is in use then skip it
|
|
||||||
ln, err := net.ListenTCP("tcp", tcpAddr("127.0.0.1", port))
|
|
||||||
if err != nil {
|
|
||||||
// log.Println("[DEBUG] freeport: port already in use: ", port)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ln.Close()
|
|
||||||
|
|
||||||
ports = append(ports, port)
|
|
||||||
}
|
|
||||||
// log.Println("[DEBUG] freeport: free ports:", ports)
|
|
||||||
return ports, nil
|
|
||||||
}
|
|
78
vendor/github.com/hashicorp/consul/sdk/testutil/README.md
generated
vendored
78
vendor/github.com/hashicorp/consul/sdk/testutil/README.md
generated
vendored
@ -1,78 +0,0 @@
|
|||||||
Consul Testing Utilities
|
|
||||||
========================
|
|
||||||
|
|
||||||
This package provides some generic helpers to facilitate testing in Consul.
|
|
||||||
|
|
||||||
TestServer
|
|
||||||
==========
|
|
||||||
|
|
||||||
TestServer is a harness for managing Consul agents and initializing them with
|
|
||||||
test data. Using it, you can form test clusters, create services, add health
|
|
||||||
checks, manipulate the K/V store, etc. This test harness is completely decoupled
|
|
||||||
from Consul's core and API client, meaning it can be easily imported and used in
|
|
||||||
external unit tests for various applications. It works by invoking the Consul
|
|
||||||
CLI, which means it is a requirement to have Consul installed in the `$PATH`.
|
|
||||||
|
|
||||||
Following is an example usage:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package my_program
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hashicorp/consul/consul/structs"
|
|
||||||
"github.com/hashicorp/consul/sdk/testutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFoo_bar(t *testing.T) {
|
|
||||||
// Create a test Consul server
|
|
||||||
srv1, err := testutil.NewTestServer()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer srv1.Stop()
|
|
||||||
|
|
||||||
// Create a secondary server, passing in configuration
|
|
||||||
// to avoid bootstrapping as we are forming a cluster.
|
|
||||||
srv2, err := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
|
|
||||||
c.Bootstrap = false
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer srv2.Stop()
|
|
||||||
|
|
||||||
// Join the servers together
|
|
||||||
srv1.JoinLAN(t, srv2.LANAddr)
|
|
||||||
|
|
||||||
// Create a test key/value pair
|
|
||||||
srv1.SetKV(t, "foo", []byte("bar"))
|
|
||||||
|
|
||||||
// Create lots of test key/value pairs
|
|
||||||
srv1.PopulateKV(t, map[string][]byte{
|
|
||||||
"bar": []byte("123"),
|
|
||||||
"baz": []byte("456"),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create a service
|
|
||||||
srv1.AddService(t, "redis", structs.HealthPassing, []string{"master"})
|
|
||||||
|
|
||||||
// Create a service that will be accessed in target source code
|
|
||||||
srv1.AddAccessibleService("redis", structs.HealthPassing, "127.0.0.1", 6379, []string{"master"})
|
|
||||||
|
|
||||||
// Create a service check
|
|
||||||
srv1.AddCheck(t, "service:redis", "redis", structs.HealthPassing)
|
|
||||||
|
|
||||||
// Create a node check
|
|
||||||
srv1.AddCheck(t, "mem", "", structs.HealthCritical)
|
|
||||||
|
|
||||||
// The HTTPAddr field contains the address of the Consul
|
|
||||||
// API on the new test server instance.
|
|
||||||
println(srv1.HTTPAddr)
|
|
||||||
|
|
||||||
// All functions also have a wrapper method to limit the passing of "t"
|
|
||||||
wrap := srv1.Wrap(t)
|
|
||||||
wrap.SetKV("foo", []byte("bar"))
|
|
||||||
}
|
|
||||||
```
|
|
68
vendor/github.com/hashicorp/consul/sdk/testutil/io.go
generated
vendored
68
vendor/github.com/hashicorp/consul/sdk/testutil/io.go
generated
vendored
@ -1,68 +0,0 @@
|
|||||||
package testutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// tmpdir is the base directory for all temporary directories
|
|
||||||
// and files created with TempDir and TempFile. This could be
|
|
||||||
// achieved by setting a system environment variable but then
|
|
||||||
// the test execution would depend on whether or not the
|
|
||||||
// environment variable is set.
|
|
||||||
//
|
|
||||||
// On macOS the temp base directory is quite long and that
|
|
||||||
// triggers a problem with some tests that bind to UNIX sockets
|
|
||||||
// where the filename seems to be too long. Using a shorter name
|
|
||||||
// fixes this and makes the paths more readable.
|
|
||||||
//
|
|
||||||
// It also provides a single base directory for cleanup.
|
|
||||||
var tmpdir = "/tmp/consul-test"
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if err := os.MkdirAll(tmpdir, 0755); err != nil {
|
|
||||||
fmt.Printf("Cannot create %s. Reverting to /tmp\n", tmpdir)
|
|
||||||
tmpdir = "/tmp"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TempDir creates a temporary directory within tmpdir
|
|
||||||
// with the name 'testname-name'. If the directory cannot
|
|
||||||
// be created t.Fatal is called.
|
|
||||||
func TempDir(t *testing.T, name string) string {
|
|
||||||
if t != nil && t.Name() != "" {
|
|
||||||
name = t.Name() + "-" + name
|
|
||||||
}
|
|
||||||
name = strings.Replace(name, "/", "_", -1)
|
|
||||||
d, err := ioutil.TempDir(tmpdir, name)
|
|
||||||
if err != nil {
|
|
||||||
if t == nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// TempFile creates a temporary file within tmpdir
|
|
||||||
// with the name 'testname-name'. If the file cannot
|
|
||||||
// be created t.Fatal is called. If a temporary directory
|
|
||||||
// has been created before consider storing the file
|
|
||||||
// inside this directory to avoid double cleanup.
|
|
||||||
func TempFile(t *testing.T, name string) *os.File {
|
|
||||||
if t != nil && t.Name() != "" {
|
|
||||||
name = t.Name() + "-" + name
|
|
||||||
}
|
|
||||||
name = strings.Replace(name, "/", "_", -1)
|
|
||||||
f, err := ioutil.TempFile(tmpdir, name)
|
|
||||||
if err != nil {
|
|
||||||
if t == nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
207
vendor/github.com/hashicorp/consul/sdk/testutil/retry/retry.go
generated
vendored
207
vendor/github.com/hashicorp/consul/sdk/testutil/retry/retry.go
generated
vendored
@ -1,207 +0,0 @@
|
|||||||
// Package retry provides support for repeating operations in tests.
|
|
||||||
//
|
|
||||||
// A sample retry operation looks like this:
|
|
||||||
//
|
|
||||||
// func TestX(t *testing.T) {
|
|
||||||
// retry.Run(t, func(r *retry.R) {
|
|
||||||
// if err := foo(); err != nil {
|
|
||||||
// r.Fatal("f: ", err)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
package retry
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Failer is an interface compatible with testing.T.
|
|
||||||
type Failer interface {
|
|
||||||
// Log is called for the final test output
|
|
||||||
Log(args ...interface{})
|
|
||||||
|
|
||||||
// FailNow is called when the retrying is abandoned.
|
|
||||||
FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
// R provides context for the retryer.
|
|
||||||
type R struct {
|
|
||||||
fail bool
|
|
||||||
output []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *R) FailNow() {
|
|
||||||
r.fail = true
|
|
||||||
runtime.Goexit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *R) Fatal(args ...interface{}) {
|
|
||||||
r.log(fmt.Sprint(args...))
|
|
||||||
r.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *R) Fatalf(format string, args ...interface{}) {
|
|
||||||
r.log(fmt.Sprintf(format, args...))
|
|
||||||
r.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *R) Error(args ...interface{}) {
|
|
||||||
r.log(fmt.Sprint(args...))
|
|
||||||
r.fail = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *R) Errorf(format string, args ...interface{}) {
|
|
||||||
r.log(fmt.Sprintf(format, args...))
|
|
||||||
r.fail = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *R) Check(err error) {
|
|
||||||
if err != nil {
|
|
||||||
r.log(err.Error())
|
|
||||||
r.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *R) log(s string) {
|
|
||||||
r.output = append(r.output, decorate(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func decorate(s string) string {
|
|
||||||
_, file, line, ok := runtime.Caller(3)
|
|
||||||
if ok {
|
|
||||||
n := strings.LastIndex(file, "/")
|
|
||||||
if n >= 0 {
|
|
||||||
file = file[n+1:]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
file = "???"
|
|
||||||
line = 1
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s:%d: %s", file, line, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Run(t Failer, f func(r *R)) {
|
|
||||||
run(DefaultFailer(), t, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunWith(r Retryer, t Failer, f func(r *R)) {
|
|
||||||
run(r, t, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dedup(a []string) string {
|
|
||||||
if len(a) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
m := map[string]int{}
|
|
||||||
for _, s := range a {
|
|
||||||
m[s] = m[s] + 1
|
|
||||||
}
|
|
||||||
var b bytes.Buffer
|
|
||||||
for _, s := range a {
|
|
||||||
if _, ok := m[s]; ok {
|
|
||||||
b.WriteString(s)
|
|
||||||
b.WriteRune('\n')
|
|
||||||
delete(m, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func run(r Retryer, t Failer, f func(r *R)) {
|
|
||||||
rr := &R{}
|
|
||||||
fail := func() {
|
|
||||||
out := dedup(rr.output)
|
|
||||||
if out != "" {
|
|
||||||
t.Log(out)
|
|
||||||
}
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
for r.NextOr(fail) {
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
f(rr)
|
|
||||||
}()
|
|
||||||
wg.Wait()
|
|
||||||
if rr.fail {
|
|
||||||
rr.fail = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultFailer provides default retry.Run() behavior for unit tests.
|
|
||||||
func DefaultFailer() *Timer {
|
|
||||||
return &Timer{Timeout: 7 * time.Second, Wait: 25 * time.Millisecond}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TwoSeconds repeats an operation for two seconds and waits 25ms in between.
|
|
||||||
func TwoSeconds() *Timer {
|
|
||||||
return &Timer{Timeout: 2 * time.Second, Wait: 25 * time.Millisecond}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ThreeTimes repeats an operation three times and waits 25ms in between.
|
|
||||||
func ThreeTimes() *Counter {
|
|
||||||
return &Counter{Count: 3, Wait: 25 * time.Millisecond}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retryer provides an interface for repeating operations
|
|
||||||
// until they succeed or an exit condition is met.
|
|
||||||
type Retryer interface {
|
|
||||||
// NextOr returns true if the operation should be repeated.
|
|
||||||
// Otherwise, it calls fail and returns false.
|
|
||||||
NextOr(fail func()) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Counter repeats an operation a given number of
|
|
||||||
// times and waits between subsequent operations.
|
|
||||||
type Counter struct {
|
|
||||||
Count int
|
|
||||||
Wait time.Duration
|
|
||||||
|
|
||||||
count int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Counter) NextOr(fail func()) bool {
|
|
||||||
if r.count == r.Count {
|
|
||||||
fail()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if r.count > 0 {
|
|
||||||
time.Sleep(r.Wait)
|
|
||||||
}
|
|
||||||
r.count++
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timer repeats an operation for a given amount
|
|
||||||
// of time and waits between subsequent operations.
|
|
||||||
type Timer struct {
|
|
||||||
Timeout time.Duration
|
|
||||||
Wait time.Duration
|
|
||||||
|
|
||||||
// stop is the timeout deadline.
|
|
||||||
// Set on the first invocation of Next().
|
|
||||||
stop time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Timer) NextOr(fail func()) bool {
|
|
||||||
if r.stop.IsZero() {
|
|
||||||
r.stop = time.Now().Add(r.Timeout)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if time.Now().After(r.stop) {
|
|
||||||
fail()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
time.Sleep(r.Wait)
|
|
||||||
return true
|
|
||||||
}
|
|
457
vendor/github.com/hashicorp/consul/sdk/testutil/server.go
generated
vendored
457
vendor/github.com/hashicorp/consul/sdk/testutil/server.go
generated
vendored
@ -1,457 +0,0 @@
|
|||||||
package testutil
|
|
||||||
|
|
||||||
// TestServer is a test helper. It uses a fork/exec model to create
|
|
||||||
// a test Consul server instance in the background and initialize it
|
|
||||||
// with some data and/or services. The test server can then be used
|
|
||||||
// to run a unit test, and offers an easy API to tear itself down
|
|
||||||
// when the test has completed. The only prerequisite is to have a consul
|
|
||||||
// binary available on the $PATH.
|
|
||||||
//
|
|
||||||
// This package does not use Consul's official API client. This is
|
|
||||||
// because we use TestServer to test the API client, which would
|
|
||||||
// otherwise cause an import cycle.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/consul/sdk/freeport"
|
|
||||||
"github.com/hashicorp/consul/sdk/testutil/retry"
|
|
||||||
"github.com/hashicorp/go-cleanhttp"
|
|
||||||
"github.com/hashicorp/go-uuid"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestPerformanceConfig configures the performance parameters.
|
|
||||||
type TestPerformanceConfig struct {
|
|
||||||
RaftMultiplier uint `json:"raft_multiplier,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestPortConfig configures the various ports used for services
|
|
||||||
// provided by the Consul server.
|
|
||||||
type TestPortConfig struct {
|
|
||||||
DNS int `json:"dns,omitempty"`
|
|
||||||
HTTP int `json:"http,omitempty"`
|
|
||||||
HTTPS int `json:"https,omitempty"`
|
|
||||||
SerfLan int `json:"serf_lan,omitempty"`
|
|
||||||
SerfWan int `json:"serf_wan,omitempty"`
|
|
||||||
Server int `json:"server,omitempty"`
|
|
||||||
ProxyMinPort int `json:"proxy_min_port,omitempty"`
|
|
||||||
ProxyMaxPort int `json:"proxy_max_port,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestAddressConfig contains the bind addresses for various
|
|
||||||
// components of the Consul server.
|
|
||||||
type TestAddressConfig struct {
|
|
||||||
HTTP string `json:"http,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestNetworkSegment contains the configuration for a network segment.
|
|
||||||
type TestNetworkSegment struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Bind string `json:"bind"`
|
|
||||||
Port int `json:"port"`
|
|
||||||
Advertise string `json:"advertise"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestServerConfig is the main server configuration struct.
|
|
||||||
type TestServerConfig struct {
|
|
||||||
NodeName string `json:"node_name"`
|
|
||||||
NodeID string `json:"node_id"`
|
|
||||||
NodeMeta map[string]string `json:"node_meta,omitempty"`
|
|
||||||
Performance *TestPerformanceConfig `json:"performance,omitempty"`
|
|
||||||
Bootstrap bool `json:"bootstrap,omitempty"`
|
|
||||||
Server bool `json:"server,omitempty"`
|
|
||||||
DataDir string `json:"data_dir,omitempty"`
|
|
||||||
Datacenter string `json:"datacenter,omitempty"`
|
|
||||||
Segments []TestNetworkSegment `json:"segments"`
|
|
||||||
DisableCheckpoint bool `json:"disable_update_check"`
|
|
||||||
LogLevel string `json:"log_level,omitempty"`
|
|
||||||
Bind string `json:"bind_addr,omitempty"`
|
|
||||||
Addresses *TestAddressConfig `json:"addresses,omitempty"`
|
|
||||||
Ports *TestPortConfig `json:"ports,omitempty"`
|
|
||||||
RaftProtocol int `json:"raft_protocol,omitempty"`
|
|
||||||
ACLMasterToken string `json:"acl_master_token,omitempty"`
|
|
||||||
ACLDatacenter string `json:"acl_datacenter,omitempty"`
|
|
||||||
PrimaryDatacenter string `json:"primary_datacenter,omitempty"`
|
|
||||||
ACLDefaultPolicy string `json:"acl_default_policy,omitempty"`
|
|
||||||
ACLEnforceVersion8 bool `json:"acl_enforce_version_8"`
|
|
||||||
ACL TestACLs `json:"acl,omitempty"`
|
|
||||||
Encrypt string `json:"encrypt,omitempty"`
|
|
||||||
CAFile string `json:"ca_file,omitempty"`
|
|
||||||
CertFile string `json:"cert_file,omitempty"`
|
|
||||||
KeyFile string `json:"key_file,omitempty"`
|
|
||||||
VerifyIncoming bool `json:"verify_incoming,omitempty"`
|
|
||||||
VerifyIncomingRPC bool `json:"verify_incoming_rpc,omitempty"`
|
|
||||||
VerifyIncomingHTTPS bool `json:"verify_incoming_https,omitempty"`
|
|
||||||
VerifyOutgoing bool `json:"verify_outgoing,omitempty"`
|
|
||||||
EnableScriptChecks bool `json:"enable_script_checks,omitempty"`
|
|
||||||
Connect map[string]interface{} `json:"connect,omitempty"`
|
|
||||||
EnableDebug bool `json:"enable_debug,omitempty"`
|
|
||||||
ReadyTimeout time.Duration `json:"-"`
|
|
||||||
Stdout, Stderr io.Writer `json:"-"`
|
|
||||||
Args []string `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestACLs struct {
|
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
|
||||||
TokenReplication bool `json:"enable_token_replication,omitempty"`
|
|
||||||
PolicyTTL string `json:"policy_ttl,omitempty"`
|
|
||||||
TokenTTL string `json:"token_ttl,omitempty"`
|
|
||||||
DownPolicy string `json:"down_policy,omitempty"`
|
|
||||||
DefaultPolicy string `json:"default_policy,omitempty"`
|
|
||||||
EnableKeyListPolicy bool `json:"enable_key_list_policy,omitempty"`
|
|
||||||
Tokens TestTokens `json:"tokens,omitempty"`
|
|
||||||
DisabledTTL string `json:"disabled_ttl,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestTokens struct {
|
|
||||||
Master string `json:"master,omitempty"`
|
|
||||||
Replication string `json:"replication,omitempty"`
|
|
||||||
AgentMaster string `json:"agent_master,omitempty"`
|
|
||||||
Default string `json:"default,omitempty"`
|
|
||||||
Agent string `json:"agent,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServerConfigCallback is a function interface which can be
|
|
||||||
// passed to NewTestServerConfig to modify the server config.
|
|
||||||
type ServerConfigCallback func(c *TestServerConfig)
|
|
||||||
|
|
||||||
// defaultServerConfig returns a new TestServerConfig struct
|
|
||||||
// with all of the listen ports incremented by one.
|
|
||||||
func defaultServerConfig() *TestServerConfig {
|
|
||||||
nodeID, err := uuid.GenerateUUID()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ports := freeport.Get(6)
|
|
||||||
return &TestServerConfig{
|
|
||||||
NodeName: "node-" + nodeID,
|
|
||||||
NodeID: nodeID,
|
|
||||||
DisableCheckpoint: true,
|
|
||||||
Performance: &TestPerformanceConfig{
|
|
||||||
RaftMultiplier: 1,
|
|
||||||
},
|
|
||||||
Bootstrap: true,
|
|
||||||
Server: true,
|
|
||||||
LogLevel: "debug",
|
|
||||||
Bind: "127.0.0.1",
|
|
||||||
Addresses: &TestAddressConfig{},
|
|
||||||
Ports: &TestPortConfig{
|
|
||||||
DNS: ports[0],
|
|
||||||
HTTP: ports[1],
|
|
||||||
HTTPS: ports[2],
|
|
||||||
SerfLan: ports[3],
|
|
||||||
SerfWan: ports[4],
|
|
||||||
Server: ports[5],
|
|
||||||
},
|
|
||||||
ReadyTimeout: 10 * time.Second,
|
|
||||||
Connect: map[string]interface{}{
|
|
||||||
"enabled": true,
|
|
||||||
"ca_config": map[string]interface{}{
|
|
||||||
// const TestClusterID causes import cycle so hard code it here.
|
|
||||||
"cluster_id": "11111111-2222-3333-4444-555555555555",
|
|
||||||
},
|
|
||||||
"proxy": map[string]interface{}{
|
|
||||||
"allow_managed_api_registration": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestService is used to serialize a service definition.
|
|
||||||
type TestService struct {
|
|
||||||
ID string `json:",omitempty"`
|
|
||||||
Name string `json:",omitempty"`
|
|
||||||
Tags []string `json:",omitempty"`
|
|
||||||
Address string `json:",omitempty"`
|
|
||||||
Port int `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestCheck is used to serialize a check definition.
|
|
||||||
type TestCheck struct {
|
|
||||||
ID string `json:",omitempty"`
|
|
||||||
Name string `json:",omitempty"`
|
|
||||||
ServiceID string `json:",omitempty"`
|
|
||||||
TTL string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestKVResponse is what we use to decode KV data.
|
|
||||||
type TestKVResponse struct {
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestServer is the main server wrapper struct.
|
|
||||||
type TestServer struct {
|
|
||||||
cmd *exec.Cmd
|
|
||||||
Config *TestServerConfig
|
|
||||||
|
|
||||||
HTTPAddr string
|
|
||||||
HTTPSAddr string
|
|
||||||
LANAddr string
|
|
||||||
WANAddr string
|
|
||||||
|
|
||||||
HTTPClient *http.Client
|
|
||||||
|
|
||||||
tmpdir string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTestServer is an easy helper method to create a new Consul
|
|
||||||
// test server with the most basic configuration.
|
|
||||||
func NewTestServer() (*TestServer, error) {
|
|
||||||
return NewTestServerConfigT(nil, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTestServerConfig(cb ServerConfigCallback) (*TestServer, error) {
|
|
||||||
return NewTestServerConfigT(nil, cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTestServerConfig creates a new TestServer, and makes a call to an optional
|
|
||||||
// callback function to modify the configuration. If there is an error
|
|
||||||
// configuring or starting the server, the server will NOT be running when the
|
|
||||||
// function returns (thus you do not need to stop it).
|
|
||||||
func NewTestServerConfigT(t *testing.T, cb ServerConfigCallback) (*TestServer, error) {
|
|
||||||
return newTestServerConfigT(t, cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newTestServerConfigT is the internal helper for NewTestServerConfigT.
|
|
||||||
func newTestServerConfigT(t *testing.T, cb ServerConfigCallback) (*TestServer, error) {
|
|
||||||
path, err := exec.LookPath("consul")
|
|
||||||
if err != nil || path == "" {
|
|
||||||
return nil, fmt.Errorf("consul not found on $PATH - download and install " +
|
|
||||||
"consul or skip this test")
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix := "consul"
|
|
||||||
if t != nil {
|
|
||||||
// Use test name for tmpdir if available
|
|
||||||
prefix = strings.Replace(t.Name(), "/", "_", -1)
|
|
||||||
}
|
|
||||||
tmpdir, err := ioutil.TempDir("", prefix)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to create tempdir")
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := defaultServerConfig()
|
|
||||||
cfg.DataDir = filepath.Join(tmpdir, "data")
|
|
||||||
if cb != nil {
|
|
||||||
cb(cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := json.Marshal(cfg)
|
|
||||||
if err != nil {
|
|
||||||
os.RemoveAll(tmpdir)
|
|
||||||
return nil, errors.Wrap(err, "failed marshaling json")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("CONFIG JSON: %s", string(b))
|
|
||||||
configFile := filepath.Join(tmpdir, "config.json")
|
|
||||||
if err := ioutil.WriteFile(configFile, b, 0644); err != nil {
|
|
||||||
os.RemoveAll(tmpdir)
|
|
||||||
return nil, errors.Wrap(err, "failed writing config content")
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout := io.Writer(os.Stdout)
|
|
||||||
if cfg.Stdout != nil {
|
|
||||||
stdout = cfg.Stdout
|
|
||||||
}
|
|
||||||
stderr := io.Writer(os.Stderr)
|
|
||||||
if cfg.Stderr != nil {
|
|
||||||
stderr = cfg.Stderr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the server
|
|
||||||
args := []string{"agent", "-config-file", configFile}
|
|
||||||
args = append(args, cfg.Args...)
|
|
||||||
cmd := exec.Command("consul", args...)
|
|
||||||
cmd.Stdout = stdout
|
|
||||||
cmd.Stderr = stderr
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
os.RemoveAll(tmpdir)
|
|
||||||
return nil, errors.Wrap(err, "failed starting command")
|
|
||||||
}
|
|
||||||
|
|
||||||
httpAddr := fmt.Sprintf("127.0.0.1:%d", cfg.Ports.HTTP)
|
|
||||||
client := cleanhttp.DefaultClient()
|
|
||||||
if strings.HasPrefix(cfg.Addresses.HTTP, "unix://") {
|
|
||||||
httpAddr = cfg.Addresses.HTTP
|
|
||||||
tr := cleanhttp.DefaultTransport()
|
|
||||||
tr.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
|
|
||||||
return net.Dial("unix", httpAddr[len("unix://"):])
|
|
||||||
}
|
|
||||||
client = &http.Client{Transport: tr}
|
|
||||||
}
|
|
||||||
|
|
||||||
server := &TestServer{
|
|
||||||
Config: cfg,
|
|
||||||
cmd: cmd,
|
|
||||||
|
|
||||||
HTTPAddr: httpAddr,
|
|
||||||
HTTPSAddr: fmt.Sprintf("127.0.0.1:%d", cfg.Ports.HTTPS),
|
|
||||||
LANAddr: fmt.Sprintf("127.0.0.1:%d", cfg.Ports.SerfLan),
|
|
||||||
WANAddr: fmt.Sprintf("127.0.0.1:%d", cfg.Ports.SerfWan),
|
|
||||||
|
|
||||||
HTTPClient: client,
|
|
||||||
|
|
||||||
tmpdir: tmpdir,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the server to be ready
|
|
||||||
if err := server.waitForAPI(); err != nil {
|
|
||||||
server.Stop()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return server, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the test Consul server, and removes the Consul data
|
|
||||||
// directory once we are done.
|
|
||||||
func (s *TestServer) Stop() error {
|
|
||||||
defer os.RemoveAll(s.tmpdir)
|
|
||||||
|
|
||||||
// There was no process
|
|
||||||
if s.cmd == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.cmd.Process != nil {
|
|
||||||
if err := s.cmd.Process.Signal(os.Interrupt); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to kill consul server")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for the process to exit to be sure that the data dir can be
|
|
||||||
// deleted on all platforms.
|
|
||||||
return s.cmd.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitForAPI waits for only the agent HTTP endpoint to start
|
|
||||||
// responding. This is an indication that the agent has started,
|
|
||||||
// but will likely return before a leader is elected.
|
|
||||||
func (s *TestServer) waitForAPI() error {
|
|
||||||
var failed bool
|
|
||||||
|
|
||||||
// This retry replicates the logic of retry.Run to allow for nested retries.
|
|
||||||
// By returning an error we can wrap TestServer creation with retry.Run
|
|
||||||
// in makeClientWithConfig.
|
|
||||||
timer := retry.TwoSeconds()
|
|
||||||
deadline := time.Now().Add(timer.Timeout)
|
|
||||||
for !time.Now().After(deadline) {
|
|
||||||
time.Sleep(timer.Wait)
|
|
||||||
|
|
||||||
resp, err := s.HTTPClient.Get(s.url("/v1/agent/self"))
|
|
||||||
if err != nil {
|
|
||||||
failed = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
if err = s.requireOK(resp); err != nil {
|
|
||||||
failed = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
failed = false
|
|
||||||
}
|
|
||||||
if failed {
|
|
||||||
return fmt.Errorf("api unavailable")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitForLeader waits for the Consul server's HTTP API to become
|
|
||||||
// available, and then waits for a known leader and an index of
|
|
||||||
// 2 or more to be observed to confirm leader election is done.
|
|
||||||
func (s *TestServer) WaitForLeader(t *testing.T) {
|
|
||||||
retry.Run(t, func(r *retry.R) {
|
|
||||||
// Query the API and check the status code.
|
|
||||||
url := s.url("/v1/catalog/nodes")
|
|
||||||
resp, err := s.HTTPClient.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
r.Fatalf("failed http get '%s': %v", url, err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if err := s.requireOK(resp); err != nil {
|
|
||||||
r.Fatal("failed OK response", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we have a leader and a node registration.
|
|
||||||
if leader := resp.Header.Get("X-Consul-KnownLeader"); leader != "true" {
|
|
||||||
r.Fatalf("Consul leader status: %#v", leader)
|
|
||||||
}
|
|
||||||
index, err := strconv.ParseInt(resp.Header.Get("X-Consul-Index"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
r.Fatal("bad consul index", err)
|
|
||||||
}
|
|
||||||
if index < 2 {
|
|
||||||
r.Fatal("consul index should be at least 2")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitForSerfCheck ensures we have a node with serfHealth check registered
|
|
||||||
// Behavior mirrors testrpc.WaitForTestAgent but avoids the dependency cycle in api pkg
|
|
||||||
func (s *TestServer) WaitForSerfCheck(t *testing.T) {
|
|
||||||
retry.Run(t, func(r *retry.R) {
|
|
||||||
// Query the API and check the status code.
|
|
||||||
url := s.url("/v1/catalog/nodes?index=0")
|
|
||||||
resp, err := s.HTTPClient.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
r.Fatal("failed http get", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if err := s.requireOK(resp); err != nil {
|
|
||||||
r.Fatal("failed OK response", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch for the anti-entropy sync to finish.
|
|
||||||
var payload []map[string]interface{}
|
|
||||||
dec := json.NewDecoder(resp.Body)
|
|
||||||
if err := dec.Decode(&payload); err != nil {
|
|
||||||
r.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(payload) < 1 {
|
|
||||||
r.Fatal("No nodes")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the serfHealth check is registered
|
|
||||||
url = s.url(fmt.Sprintf("/v1/health/node/%s", payload[0]["Node"]))
|
|
||||||
resp, err = s.HTTPClient.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
r.Fatal("failed http get", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if err := s.requireOK(resp); err != nil {
|
|
||||||
r.Fatal("failed OK response", err)
|
|
||||||
}
|
|
||||||
dec = json.NewDecoder(resp.Body)
|
|
||||||
if err = dec.Decode(&payload); err != nil {
|
|
||||||
r.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var found bool
|
|
||||||
for _, check := range payload {
|
|
||||||
if check["CheckID"].(string) == "serfHealth" {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
r.Fatal("missing serfHealth registration")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
256
vendor/github.com/hashicorp/consul/sdk/testutil/server_methods.go
generated
vendored
256
vendor/github.com/hashicorp/consul/sdk/testutil/server_methods.go
generated
vendored
@ -1,256 +0,0 @@
|
|||||||
package testutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// copied from testutil to break circular dependency
|
|
||||||
const (
|
|
||||||
HealthAny = "any"
|
|
||||||
HealthPassing = "passing"
|
|
||||||
HealthWarning = "warning"
|
|
||||||
HealthCritical = "critical"
|
|
||||||
HealthMaint = "maintenance"
|
|
||||||
)
|
|
||||||
|
|
||||||
// JoinLAN is used to join local datacenters together.
|
|
||||||
func (s *TestServer) JoinLAN(t *testing.T, addr string) {
|
|
||||||
resp := s.put(t, "/v1/agent/join/"+addr, nil)
|
|
||||||
defer resp.Body.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// JoinWAN is used to join remote datacenters together.
|
|
||||||
func (s *TestServer) JoinWAN(t *testing.T, addr string) {
|
|
||||||
resp := s.put(t, "/v1/agent/join/"+addr+"?wan=1", nil)
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetKV sets an individual key in the K/V store.
|
|
||||||
func (s *TestServer) SetKV(t *testing.T, key string, val []byte) {
|
|
||||||
resp := s.put(t, "/v1/kv/"+key, bytes.NewBuffer(val))
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetKVString sets an individual key in the K/V store, but accepts a string
|
|
||||||
// instead of []byte.
|
|
||||||
func (s *TestServer) SetKVString(t *testing.T, key string, val string) {
|
|
||||||
resp := s.put(t, "/v1/kv/"+key, bytes.NewBufferString(val))
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKV retrieves a single key and returns its value
|
|
||||||
func (s *TestServer) GetKV(t *testing.T, key string) []byte {
|
|
||||||
resp := s.get(t, "/v1/kv/"+key)
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
raw, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to read body: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var result []*TestKVResponse
|
|
||||||
if err := json.Unmarshal(raw, &result); err != nil {
|
|
||||||
t.Fatalf("failed to unmarshal: %s", err)
|
|
||||||
}
|
|
||||||
if len(result) < 1 {
|
|
||||||
t.Fatalf("key does not exist: %s", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := base64.StdEncoding.DecodeString(result[0].Value)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to base64 decode: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKVString retrieves a value from the store, but returns as a string instead
|
|
||||||
// of []byte.
|
|
||||||
func (s *TestServer) GetKVString(t *testing.T, key string) string {
|
|
||||||
return string(s.GetKV(t, key))
|
|
||||||
}
|
|
||||||
|
|
||||||
// PopulateKV fills the Consul KV with data from a generic map.
|
|
||||||
func (s *TestServer) PopulateKV(t *testing.T, data map[string][]byte) {
|
|
||||||
for k, v := range data {
|
|
||||||
s.SetKV(t, k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListKV returns a list of keys present in the KV store. This will list all
|
|
||||||
// keys under the given prefix recursively and return them as a slice.
|
|
||||||
func (s *TestServer) ListKV(t *testing.T, prefix string) []string {
|
|
||||||
resp := s.get(t, "/v1/kv/"+prefix+"?keys")
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
raw, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to read body: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var result []string
|
|
||||||
if err := json.Unmarshal(raw, &result); err != nil {
|
|
||||||
t.Fatalf("failed to unmarshal: %s", err)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddService adds a new service to the Consul instance. It also
|
|
||||||
// automatically adds a health check with the given status, which
|
|
||||||
// can be one of "passing", "warning", or "critical".
|
|
||||||
func (s *TestServer) AddService(t *testing.T, name, status string, tags []string) {
|
|
||||||
s.AddAddressableService(t, name, status, "", 0, tags) // set empty address and 0 as port for non-accessible service
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddAddressableService adds a new service to the Consul instance by
|
|
||||||
// passing "address" and "port". It is helpful when you need to prepare a fakeService
|
|
||||||
// that maybe accessed with in target source code.
|
|
||||||
// It also automatically adds a health check with the given status, which
|
|
||||||
// can be one of "passing", "warning", or "critical", just like `AddService` does.
|
|
||||||
func (s *TestServer) AddAddressableService(t *testing.T, name, status, address string, port int, tags []string) {
|
|
||||||
svc := &TestService{
|
|
||||||
Name: name,
|
|
||||||
Tags: tags,
|
|
||||||
Address: address,
|
|
||||||
Port: port,
|
|
||||||
}
|
|
||||||
payload, err := s.encodePayload(svc)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
s.put(t, "/v1/agent/service/register", payload)
|
|
||||||
|
|
||||||
chkName := "service:" + name
|
|
||||||
chk := &TestCheck{
|
|
||||||
Name: chkName,
|
|
||||||
ServiceID: name,
|
|
||||||
TTL: "10m",
|
|
||||||
}
|
|
||||||
payload, err = s.encodePayload(chk)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
s.put(t, "/v1/agent/check/register", payload)
|
|
||||||
|
|
||||||
switch status {
|
|
||||||
case HealthPassing:
|
|
||||||
s.put(t, "/v1/agent/check/pass/"+chkName, nil)
|
|
||||||
case HealthWarning:
|
|
||||||
s.put(t, "/v1/agent/check/warn/"+chkName, nil)
|
|
||||||
case HealthCritical:
|
|
||||||
s.put(t, "/v1/agent/check/fail/"+chkName, nil)
|
|
||||||
default:
|
|
||||||
t.Fatalf("Unrecognized status: %s", status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCheck adds a check to the Consul instance. If the serviceID is
|
|
||||||
// left empty (""), then the check will be associated with the node.
|
|
||||||
// The check status may be "passing", "warning", or "critical".
|
|
||||||
func (s *TestServer) AddCheck(t *testing.T, name, serviceID, status string) {
|
|
||||||
chk := &TestCheck{
|
|
||||||
ID: name,
|
|
||||||
Name: name,
|
|
||||||
TTL: "10m",
|
|
||||||
}
|
|
||||||
if serviceID != "" {
|
|
||||||
chk.ServiceID = serviceID
|
|
||||||
}
|
|
||||||
|
|
||||||
payload, err := s.encodePayload(chk)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
s.put(t, "/v1/agent/check/register", payload)
|
|
||||||
|
|
||||||
switch status {
|
|
||||||
case HealthPassing:
|
|
||||||
s.put(t, "/v1/agent/check/pass/"+name, nil)
|
|
||||||
case HealthWarning:
|
|
||||||
s.put(t, "/v1/agent/check/warn/"+name, nil)
|
|
||||||
case HealthCritical:
|
|
||||||
s.put(t, "/v1/agent/check/fail/"+name, nil)
|
|
||||||
default:
|
|
||||||
t.Fatalf("Unrecognized status: %s", status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// put performs a new HTTP PUT request.
|
|
||||||
func (s *TestServer) put(t *testing.T, path string, body io.Reader) *http.Response {
|
|
||||||
req, err := http.NewRequest("PUT", s.url(path), body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create PUT request: %s", err)
|
|
||||||
}
|
|
||||||
resp, err := s.HTTPClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to make PUT request: %s", err)
|
|
||||||
}
|
|
||||||
if err := s.requireOK(resp); err != nil {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
t.Fatalf("not OK PUT: %s", err)
|
|
||||||
}
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
// get performs a new HTTP GET request.
|
|
||||||
func (s *TestServer) get(t *testing.T, path string) *http.Response {
|
|
||||||
resp, err := s.HTTPClient.Get(s.url(path))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create GET request: %s", err)
|
|
||||||
}
|
|
||||||
if err := s.requireOK(resp); err != nil {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
t.Fatalf("not OK GET: %s", err)
|
|
||||||
}
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodePayload returns a new io.Reader wrapping the encoded contents
|
|
||||||
// of the payload, suitable for passing directly to a new request.
|
|
||||||
func (s *TestServer) encodePayload(payload interface{}) (io.Reader, error) {
|
|
||||||
var encoded bytes.Buffer
|
|
||||||
enc := json.NewEncoder(&encoded)
|
|
||||||
if err := enc.Encode(payload); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to encode payload")
|
|
||||||
}
|
|
||||||
return &encoded, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// url is a helper function which takes a relative URL and
|
|
||||||
// makes it into a proper URL against the local Consul server.
|
|
||||||
func (s *TestServer) url(path string) string {
|
|
||||||
if s == nil {
|
|
||||||
log.Fatal("s is nil")
|
|
||||||
}
|
|
||||||
if s.Config == nil {
|
|
||||||
log.Fatal("s.Config is nil")
|
|
||||||
}
|
|
||||||
if s.Config.Ports == nil {
|
|
||||||
log.Fatal("s.Config.Ports is nil")
|
|
||||||
}
|
|
||||||
if s.Config.Ports.HTTP == 0 {
|
|
||||||
log.Fatal("s.Config.Ports.HTTP is 0")
|
|
||||||
}
|
|
||||||
if path == "" {
|
|
||||||
log.Fatal("path is empty")
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("http://127.0.0.1:%d%s", s.Config.Ports.HTTP, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// requireOK checks the HTTP response code and ensures it is acceptable.
|
|
||||||
func (s *TestServer) requireOK(resp *http.Response) error {
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return fmt.Errorf("Bad status code: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
65
vendor/github.com/hashicorp/consul/sdk/testutil/server_wrapper.go
generated
vendored
65
vendor/github.com/hashicorp/consul/sdk/testutil/server_wrapper.go
generated
vendored
@ -1,65 +0,0 @@
|
|||||||
package testutil
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
type WrappedServer struct {
|
|
||||||
s *TestServer
|
|
||||||
t *testing.T
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap wraps the test server in a `testing.t` for convenience.
|
|
||||||
//
|
|
||||||
// For example, the following code snippets are equivalent.
|
|
||||||
//
|
|
||||||
// server.JoinLAN(t, "1.2.3.4")
|
|
||||||
// server.Wrap(t).JoinLAN("1.2.3.4")
|
|
||||||
//
|
|
||||||
// This is useful when you are calling multiple functions and save the wrapped
|
|
||||||
// value as another variable to reduce the inclusion of "t".
|
|
||||||
func (s *TestServer) Wrap(t *testing.T) *WrappedServer {
|
|
||||||
return &WrappedServer{s, t}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WrappedServer) JoinLAN(addr string) {
|
|
||||||
w.s.JoinLAN(w.t, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WrappedServer) JoinWAN(addr string) {
|
|
||||||
w.s.JoinWAN(w.t, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WrappedServer) SetKV(key string, val []byte) {
|
|
||||||
w.s.SetKV(w.t, key, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WrappedServer) SetKVString(key string, val string) {
|
|
||||||
w.s.SetKVString(w.t, key, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WrappedServer) GetKV(key string) []byte {
|
|
||||||
return w.s.GetKV(w.t, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WrappedServer) GetKVString(key string) string {
|
|
||||||
return w.s.GetKVString(w.t, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WrappedServer) PopulateKV(data map[string][]byte) {
|
|
||||||
w.s.PopulateKV(w.t, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WrappedServer) ListKV(prefix string) []string {
|
|
||||||
return w.s.ListKV(w.t, prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WrappedServer) AddService(name, status string, tags []string) {
|
|
||||||
w.s.AddService(w.t, name, status, tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WrappedServer) AddAddressableService(name, status, address string, port int, tags []string) {
|
|
||||||
w.s.AddAddressableService(w.t, name, status, address, port, tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WrappedServer) AddCheck(name, serviceID, status string) {
|
|
||||||
w.s.AddCheck(w.t, name, serviceID, status)
|
|
||||||
}
|
|
42
vendor/github.com/hashicorp/consul/sdk/testutil/testlog.go
generated
vendored
42
vendor/github.com/hashicorp/consul/sdk/testutil/testlog.go
generated
vendored
@ -1,42 +0,0 @@
|
|||||||
package testutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var sendTestLogsToStdout bool
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
sendTestLogsToStdout = os.Getenv("NOLOGBUFFER") == "1"
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogger(t testing.TB) *log.Logger {
|
|
||||||
return log.New(&testWriter{t}, "test: ", log.LstdFlags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoggerWithName(t testing.TB, name string) *log.Logger {
|
|
||||||
return log.New(&testWriter{t}, "test["+name+"]: ", log.LstdFlags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriter(t testing.TB) io.Writer {
|
|
||||||
return &testWriter{t}
|
|
||||||
}
|
|
||||||
|
|
||||||
type testWriter struct {
|
|
||||||
t testing.TB
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tw *testWriter) Write(p []byte) (n int, err error) {
|
|
||||||
tw.t.Helper()
|
|
||||||
if sendTestLogsToStdout {
|
|
||||||
fmt.Fprint(os.Stdout, strings.TrimSpace(string(p))+"\n")
|
|
||||||
} else {
|
|
||||||
tw.t.Log(strings.TrimSpace(string(p)))
|
|
||||||
}
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
7
vendor/modules.txt
vendored
7
vendor/modules.txt
vendored
@ -192,13 +192,6 @@ github.com/gregjones/httpcache
|
|||||||
github.com/gregjones/httpcache/diskcache
|
github.com/gregjones/httpcache/diskcache
|
||||||
# github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed
|
# github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed
|
||||||
github.com/hailocab/go-hostpool
|
github.com/hailocab/go-hostpool
|
||||||
# github.com/hashicorp/consul/api v1.1.0 => ./api
|
|
||||||
github.com/hashicorp/consul/api
|
|
||||||
github.com/hashicorp/consul/api/watch
|
|
||||||
# github.com/hashicorp/consul/sdk v0.1.1 => ./sdk
|
|
||||||
github.com/hashicorp/consul/sdk/freeport
|
|
||||||
github.com/hashicorp/consul/sdk/testutil/retry
|
|
||||||
github.com/hashicorp/consul/sdk/testutil
|
|
||||||
# github.com/hashicorp/errwrap v1.0.0
|
# github.com/hashicorp/errwrap v1.0.0
|
||||||
github.com/hashicorp/errwrap
|
github.com/hashicorp/errwrap
|
||||||
# github.com/hashicorp/go-bexpr v0.1.0
|
# github.com/hashicorp/go-bexpr v0.1.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user