Add Consul TLS options to access API endpoint (#8253)

This commit is contained in:
Michel Vocks 2020-01-29 09:44:35 +01:00 committed by GitHub
parent 96a6857f0c
commit f695eb737b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1261 additions and 184 deletions

View File

@ -72,6 +72,7 @@ github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrj
github.com/hashicorp/go-hclog v0.10.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.10.1 h1:uyt/l0dWjJ879yiAu+T7FG3/6QX+zwm4bQ8P7XsYt3o=
github.com/hashicorp/go-hclog v0.10.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-kms-wrapping v0.0.0-20191129225826-634facde9f88/go.mod h1:Pm+Umb/6Gij6ZG534L7QDyvkauaOQWGb+arj9aFjCE0=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=

View File

@ -24,6 +24,9 @@ func (b *backend) client(ctx context.Context, s logical.Storage) (*api.Client, e
consulConf.Address = conf.Address
consulConf.Scheme = conf.Scheme
consulConf.Token = conf.Token
consulConf.TLSConfig.CAPem = []byte(conf.CACert)
consulConf.TLSConfig.CertPEM = []byte(conf.ClientCert)
consulConf.TLSConfig.KeyPEM = []byte(conf.ClientKey)
client, err := api.NewClient(consulConf)
return client, nil, err

View File

@ -32,6 +32,24 @@ func pathConfigAccess(b *backend) *framework.Path {
Type: framework.TypeString,
Description: "Token for API calls",
},
"ca_cert": &framework.FieldSchema{
Type: framework.TypeString,
Description: `CA certificate to use when verifying Consul server certificate,
must be x509 PEM encoded.`,
},
"client_cert": &framework.FieldSchema{
Type: framework.TypeString,
Description: `Client certificate used for Consul's TLS communication,
must be x509 PEM encoded and if this is set you need to also set client_key.`,
},
"client_key": &framework.FieldSchema{
Type: framework.TypeString,
Description: `Client key used for Consul's TLS communication,
must be x509 PEM encoded and if this is set you need to also set client_cert.`,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
@ -80,9 +98,12 @@ func (b *backend) pathConfigAccessRead(ctx context.Context, req *logical.Request
func (b *backend) pathConfigAccessWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
entry, err := logical.StorageEntryJSON("config/access", accessConfig{
Address: data.Get("address").(string),
Scheme: data.Get("scheme").(string),
Token: data.Get("token").(string),
Address: data.Get("address").(string),
Scheme: data.Get("scheme").(string),
Token: data.Get("token").(string),
CACert: data.Get("ca_cert").(string),
ClientCert: data.Get("client_cert").(string),
ClientKey: data.Get("client_key").(string),
})
if err != nil {
return nil, err
@ -96,7 +117,10 @@ func (b *backend) pathConfigAccessWrite(ctx context.Context, req *logical.Reques
}
type accessConfig struct {
Address string `json:"address"`
Scheme string `json:"scheme"`
Token string `json:"token"`
Address string `json:"address"`
Scheme string `json:"scheme"`
Token string `json:"token"`
CACert string `json:"ca_cert"`
ClientCert string `json:"client_cert"`
ClientKey string `json:"client_key"`
}

2
go.mod
View File

@ -50,7 +50,7 @@ require (
github.com/google/go-metrics-stackdriver v0.0.0-20190816035513-b52628e82e2a
github.com/grpc-ecosystem/grpc-gateway v1.8.5 // indirect
github.com/hashicorp/consul-template v0.22.0
github.com/hashicorp/consul/api v1.1.0
github.com/hashicorp/consul/api v1.2.1-0.20200128105449-6681be918a6e
github.com/hashicorp/errwrap v1.0.0
github.com/hashicorp/go-cleanhttp v0.5.1
github.com/hashicorp/go-gcp-common v0.5.0

3
go.sum
View File

@ -288,8 +288,11 @@ github.com/hashicorp/consul-template v0.22.0 h1:ti5cqAekOeMfFYLJCjlPtKGwBcqwVxoZ
github.com/hashicorp/consul-template v0.22.0/go.mod h1:lHrykBIcPobCuEcIMLJryKxDyk2lUMnQWmffOEONH0k=
github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.2.1-0.20200128105449-6681be918a6e h1:vOqdnsq53winzJDN6RTQe9n9g87S595PNsdwKyBWXRM=
github.com/hashicorp/consul/api v1.2.1-0.20200128105449-6681be918a6e/go.mod h1:ztzLK20HA5O27oTf2j/wbNgq8qj/crN8xsSx7pzX0sc=
github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.2.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
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.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=

View File

@ -18,15 +18,14 @@ const (
ACLManagementType = "management"
)
type ACLTokenPolicyLink struct {
ID string
Name string
}
type ACLTokenRoleLink struct {
type ACLLink struct {
ID string
Name string
}
type ACLTokenPolicyLink = ACLLink
type ACLTokenRoleLink = ACLLink
// ACLToken represents an ACL Token
type ACLToken struct {
CreateIndex uint64
@ -46,6 +45,10 @@ type ACLToken struct {
// DEPRECATED (ACL-Legacy-Compat)
// Rules will only be present for legacy tokens returned via the new APIs
Rules string `json:",omitempty"`
// Namespace is the namespace the ACLToken is associated with.
// Namespaces is a Consul Enterprise feature.
Namespace string `json:",omitempty"`
}
type ACLTokenListEntry struct {
@ -61,6 +64,10 @@ type ACLTokenListEntry struct {
CreateTime time.Time
Hash []byte
Legacy bool
// Namespace is the namespace the ACLTokenListEntry is associated with.
// Namespacing is a Consul Enterprise feature.
Namespace string `json:",omitempty"`
}
// ACLEntry is used to represent a legacy ACL token
@ -105,6 +112,10 @@ type ACLPolicy struct {
Hash []byte
CreateIndex uint64
ModifyIndex uint64
// Namespace is the namespace the ACLPolicy is associated with.
// Namespacing is a Consul Enterprise feature.
Namespace string `json:",omitempty"`
}
type ACLPolicyListEntry struct {
@ -115,12 +126,13 @@ type ACLPolicyListEntry struct {
Hash []byte
CreateIndex uint64
ModifyIndex uint64
// Namespace is the namespace the ACLPolicyListEntry is associated with.
// Namespacing is a Consul Enterprise feature.
Namespace string `json:",omitempty"`
}
type ACLRolePolicyLink struct {
ID string
Name string
}
type ACLRolePolicyLink = ACLLink
// ACLRole represents an ACL Role.
type ACLRole struct {
@ -132,6 +144,10 @@ type ACLRole struct {
Hash []byte
CreateIndex uint64
ModifyIndex uint64
// Namespace is the namespace the ACLRole is associated with.
// Namespacing is a Consul Enterprise feature.
Namespace string `json:",omitempty"`
}
// BindingRuleBindType is the type of binding rule mechanism used.
@ -155,6 +171,10 @@ type ACLBindingRule struct {
CreateIndex uint64
ModifyIndex uint64
// Namespace is the namespace the ACLBindingRule is associated with.
// Namespacing is a Consul Enterprise feature.
Namespace string `json:",omitempty"`
}
type ACLAuthMethod struct {
@ -169,6 +189,10 @@ type ACLAuthMethod struct {
CreateIndex uint64
ModifyIndex uint64
// Namespace is the namespace the ACLAuthMethod is associated with.
// Namespacing is a Consul Enterprise feature.
Namespace string `json:",omitempty"`
}
type ACLAuthMethodListEntry struct {
@ -177,6 +201,10 @@ type ACLAuthMethodListEntry struct {
Description string
CreateIndex uint64
ModifyIndex uint64
// Namespace is the namespace the ACLAuthMethodListEntry is associated with.
// Namespacing is a Consul Enterprise feature.
Namespace string `json:",omitempty"`
}
// ParseKubernetesAuthMethodConfig takes a raw config map and returns a parsed

View File

@ -23,23 +23,11 @@ const (
// service proxies another service within Consul and speaks the connect
// protocol.
ServiceKindConnectProxy ServiceKind = "connect-proxy"
)
// ProxyExecMode is the execution mode for a managed Connect proxy.
type ProxyExecMode string
const (
// ProxyExecModeDaemon indicates that the proxy command should be long-running
// and should be started and supervised by the agent until it's target service
// is deregistered.
ProxyExecModeDaemon ProxyExecMode = "daemon"
// ProxyExecModeScript indicates that the proxy command should be invoke to
// completion on each change to the configuration of lifecycle event. The
// script typically fetches the config and certificates from the agent API and
// then configures an externally managed daemon, perhaps starting and stopping
// it if necessary.
ProxyExecModeScript ProxyExecMode = "script"
// ServiceKindMeshGateway is a Mesh Gateway for the Connect feature. This
// service will proxy connections based off the SNI header set by other
// connect proxies
ServiceKindMeshGateway ServiceKind = "mesh-gateway"
)
// UpstreamDestType is the type of upstream discovery mechanism.
@ -64,7 +52,9 @@ type AgentCheck struct {
Output string
ServiceID string
ServiceName string
Type string
Definition HealthCheckDefinition
Namespace string `json:",omitempty"`
}
// AgentWeights represent optional weights for a service
@ -82,15 +72,18 @@ type AgentService struct {
Meta map[string]string
Port int
Address string
TaggedAddresses map[string]ServiceAddress `json:",omitempty"`
Weights AgentWeights
EnableTagOverride bool
CreateIndex uint64 `json:",omitempty" bexpr:"-"`
ModifyIndex uint64 `json:",omitempty" bexpr:"-"`
ContentHash string `json:",omitempty" bexpr:"-"`
// DEPRECATED (ProxyDestination) - remove this field
ProxyDestination string `json:",omitempty" bexpr:"-"`
Proxy *AgentServiceConnectProxyConfig `json:",omitempty"`
Connect *AgentServiceConnect `json:",omitempty"`
CreateIndex uint64 `json:",omitempty" bexpr:"-"`
ModifyIndex uint64 `json:",omitempty" bexpr:"-"`
ContentHash string `json:",omitempty" bexpr:"-"`
Proxy *AgentServiceConnectProxyConfig `json:",omitempty"`
Connect *AgentServiceConnect `json:",omitempty"`
// NOTE: If we ever set the ContentHash outside of singular service lookup then we may need
// to include the Namespace in the hash. When we do, then we are in for lots of fun with tests.
// For now though, ignoring it works well enough.
Namespace string `json:",omitempty" bexpr:"-" hash:"ignore"`
}
// AgentServiceChecksInfo returns information about a Service and its checks
@ -103,28 +96,20 @@ type AgentServiceChecksInfo struct {
// AgentServiceConnect represents the Connect configuration of a service.
type AgentServiceConnect struct {
Native bool `json:",omitempty"`
Proxy *AgentServiceConnectProxy `json:",omitempty" bexpr:"-"`
SidecarService *AgentServiceRegistration `json:",omitempty" bexpr:"-"`
}
// AgentServiceConnectProxy represents the Connect Proxy configuration of a
// service.
type AgentServiceConnectProxy struct {
ExecMode ProxyExecMode `json:",omitempty"`
Command []string `json:",omitempty"`
Config map[string]interface{} `json:",omitempty" bexpr:"-"`
Upstreams []Upstream `json:",omitempty"`
}
// AgentServiceConnectProxyConfig is the proxy configuration in a connect-proxy
// ServiceDefinition or response.
type AgentServiceConnectProxyConfig struct {
DestinationServiceName string
DestinationServiceName string `json:",omitempty"`
DestinationServiceID string `json:",omitempty"`
LocalServiceAddress string `json:",omitempty"`
LocalServicePort int `json:",omitempty"`
Config map[string]interface{} `json:",omitempty" bexpr:"-"`
Upstreams []Upstream
Upstreams []Upstream `json:",omitempty"`
MeshGateway MeshGatewayConfig `json:",omitempty"`
Expose ExposeConfig `json:",omitempty"`
}
// AgentMember represents a cluster member known to the agent
@ -157,21 +142,21 @@ type MembersOpts struct {
// AgentServiceRegistration is used to register a new service
type AgentServiceRegistration struct {
Kind ServiceKind `json:",omitempty"`
ID string `json:",omitempty"`
Name string `json:",omitempty"`
Tags []string `json:",omitempty"`
Port int `json:",omitempty"`
Address string `json:",omitempty"`
EnableTagOverride bool `json:",omitempty"`
Meta map[string]string `json:",omitempty"`
Weights *AgentWeights `json:",omitempty"`
Kind ServiceKind `json:",omitempty"`
ID string `json:",omitempty"`
Name string `json:",omitempty"`
Tags []string `json:",omitempty"`
Port int `json:",omitempty"`
Address string `json:",omitempty"`
TaggedAddresses map[string]ServiceAddress `json:",omitempty"`
EnableTagOverride bool `json:",omitempty"`
Meta map[string]string `json:",omitempty"`
Weights *AgentWeights `json:",omitempty"`
Check *AgentServiceCheck
Checks AgentServiceChecks
// DEPRECATED (ProxyDestination) - remove this field
ProxyDestination string `json:",omitempty"`
Proxy *AgentServiceConnectProxyConfig `json:",omitempty"`
Connect *AgentServiceConnect `json:",omitempty"`
Proxy *AgentServiceConnectProxyConfig `json:",omitempty"`
Connect *AgentServiceConnect `json:",omitempty"`
Namespace string `json:",omitempty" bexpr:"-" hash:"ignore"`
}
// AgentCheckRegistration is used to register a new check
@ -181,6 +166,7 @@ type AgentCheckRegistration struct {
Notes string `json:",omitempty"`
ServiceID string `json:",omitempty"`
AgentServiceCheck
Namespace string `json:",omitempty"`
}
// AgentServiceCheck is used to define a node or service level check
@ -276,12 +262,8 @@ type ConnectProxyConfig struct {
TargetServiceID string
TargetServiceName string
ContentHash string
// DEPRECATED(managed-proxies) - this struct is re-used for sidecar configs
// but they don't need ExecMode or Command
ExecMode ProxyExecMode `json:",omitempty"`
Command []string `json:",omitempty"`
Config map[string]interface{} `bexpr:"-"`
Upstreams []Upstream
Config map[string]interface{} `bexpr:"-"`
Upstreams []Upstream
}
// Upstream is the response structure for a proxy upstream configuration.
@ -293,6 +275,7 @@ type Upstream struct {
LocalBindAddress string `json:",omitempty"`
LocalBindPort int `json:",omitempty"`
Config map[string]interface{} `json:",omitempty" bexpr:"-"`
MeshGateway MeshGatewayConfig `json:",omitempty"`
}
// Agent can be used to query the Agent endpoints
@ -755,6 +738,19 @@ func (a *Agent) ForceLeave(node string) error {
return nil
}
//ForceLeavePrune is used to have an a failed agent removed
//from the list of members
func (a *Agent) ForceLeavePrune(node string) error {
r := a.c.newRequest("PUT", "/v1/agent/force-leave/"+node)
r.params.Set("prune", "1")
_, resp, err := requireOK(a.c.doRequest(r))
if err != nil {
return err
}
resp.Body.Close()
return nil
}
// ConnectAuthorize is used to authorize an incoming connection
// to a natively integrated Connect service.
func (a *Agent) ConnectAuthorize(auth *AgentAuthorizeParams) (*AgentAuthorize, error) {
@ -815,31 +811,6 @@ func (a *Agent) ConnectCALeaf(serviceID string, q *QueryOptions) (*LeafCert, *Qu
return &out, qm, nil
}
// ConnectProxyConfig gets the configuration for a local managed proxy instance.
//
// Note that this uses an unconventional blocking mechanism since it's
// agent-local state. That means there is no persistent raft index so we block
// based on object hash instead.
func (a *Agent) ConnectProxyConfig(proxyServiceID string, q *QueryOptions) (*ConnectProxyConfig, *QueryMeta, error) {
r := a.c.newRequest("GET", "/v1/agent/connect/proxy/"+proxyServiceID)
r.setQueryOptions(q)
rtt, resp, err := requireOK(a.c.doRequest(r))
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
qm := &QueryMeta{}
parseQueryMeta(resp, qm)
qm.RequestTime = rtt
var out ConnectProxyConfig
if err := decodeBody(resp, &out); err != nil {
return nil, nil, err
}
return &out, qm, nil
}
// EnableServiceMaintenance toggles service maintenance mode on
// for the given service ID.
func (a *Agent) EnableServiceMaintenance(serviceID, reason string) error {

View File

@ -71,10 +71,18 @@ const (
// client in this package but is defined here for consistency with all the
// other ENV names we use.
GRPCAddrEnvName = "CONSUL_GRPC_ADDR"
// HTTPNamespaceEnvVar defines an environment variable name which sets
// the HTTP Namespace to be used by default. This can still be overridden.
HTTPNamespaceEnvName = "CONSUL_NAMESPACE"
)
// QueryOptions are used to parameterize a query
type QueryOptions struct {
// Namespace overrides the `default` namespace
// Note: Namespaces are available only in Consul Enterprise
Namespace string
// Providing a datacenter overwrites the DC provided
// by the Config
Datacenter string
@ -89,7 +97,7 @@ type QueryOptions struct {
RequireConsistent bool
// UseCache requests that the agent cache results locally. See
// https://www.consul.io/api/index.html#agent-caching for more details on the
// https://www.consul.io/api/features/caching.html for more details on the
// semantics.
UseCache bool
@ -99,14 +107,14 @@ type QueryOptions struct {
// 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/index.html#agent-caching for more details.
// 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/index.html#agent-caching for more details.
// https://www.consul.io/api/features/caching.html for more details.
StaleIfError time.Duration
// WaitIndex is used to enable a blocking query. Waits
@ -143,6 +151,10 @@ type QueryOptions struct {
// a value from 0 to 5 (inclusive).
RelayFactor uint8
// LocalOnly is used in keyring list operation to force the keyring
// query to only hit local servers (no WAN traffic).
LocalOnly bool
// Connect filters prepared query execution to only include Connect-capable
// services. This currently affects prepared query execution.
Connect bool
@ -174,6 +186,10 @@ func (o *QueryOptions) WithContext(ctx context.Context) *QueryOptions {
// WriteOptions are used to parameterize a write
type WriteOptions struct {
// Namespace overrides the `default` namespace
// Note: Namespaces are available only in Consul Enterprise
Namespace string
// Providing a datacenter overwrites the DC provided
// by the Config
Datacenter string
@ -288,6 +304,10 @@ type Config struct {
// If provided it is read once at startup and never again.
TokenFile string
// Namespace is the name of the namespace to send along for the request
// when no other Namespace ispresent in the QueryOptions
Namespace string
TLSConfig TLSConfig
}
@ -307,14 +327,26 @@ type TLSConfig struct {
// Consul communication, defaults to the system bundle if not specified.
CAPath string
// CAPem is the optional PEM-encoded CA certificate used for Consul
// communication, defaults to the system bundle if not specified.
CAPem []byte
// CertFile is the optional path to the certificate for Consul
// communication. If this is set then you need to also set KeyFile.
CertFile string
// CertPEM is the optional PEM-encoded certificate for Consul
// communication. If this is set then you need to also set KeyPEM.
CertPEM []byte
// 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
// KeyPEM is the optional PEM-encoded private key for Consul communication.
// If this is set then you need to also set CertPEM.
KeyPEM []byte
// InsecureSkipVerify if set to true will disable TLS host verification.
InsecureSkipVerify bool
}
@ -411,6 +443,10 @@ func defaultConfig(transportFn func() *http.Transport) *Config {
}
}
if v := os.Getenv(HTTPNamespaceEnvName); v != "" {
config.Namespace = v
}
return config
}
@ -434,18 +470,31 @@ func SetupTLSConfig(tlsConfig *TLSConfig) (*tls.Config, error) {
tlsClientConfig.ServerName = server
}
if len(tlsConfig.CertPEM) != 0 && len(tlsConfig.KeyPEM) != 0 {
tlsCert, err := tls.X509KeyPair(tlsConfig.CertPEM, tlsConfig.KeyPEM)
if err != nil {
return nil, err
}
tlsClientConfig.Certificates = []tls.Certificate{tlsCert}
} else if len(tlsConfig.CertPEM) != 0 || len(tlsConfig.KeyPEM) != 0 {
return nil, fmt.Errorf("both client cert and client key must be provided")
}
if tlsConfig.CertFile != "" && tlsConfig.KeyFile != "" {
tlsCert, err := tls.LoadX509KeyPair(tlsConfig.CertFile, tlsConfig.KeyFile)
if err != nil {
return nil, err
}
tlsClientConfig.Certificates = []tls.Certificate{tlsCert}
} else if tlsConfig.CertFile != "" || tlsConfig.KeyFile != "" {
return nil, fmt.Errorf("both client cert and client key must be provided")
}
if tlsConfig.CAFile != "" || tlsConfig.CAPath != "" {
if tlsConfig.CAFile != "" || tlsConfig.CAPath != "" || len(tlsConfig.CAPem) != 0 {
rootConfig := &rootcerts.Config{
CAFile: tlsConfig.CAFile,
CAPath: tlsConfig.CAPath,
CAFile: tlsConfig.CAFile,
CAPath: tlsConfig.CAPath,
CACertificate: tlsConfig.CAPem,
}
if err := rootcerts.ConfigureTLS(tlsClientConfig, rootConfig); err != nil {
return nil, err
@ -620,6 +669,9 @@ func (r *request) setQueryOptions(q *QueryOptions) {
if q == nil {
return
}
if q.Namespace != "" {
r.params.Set("ns", q.Namespace)
}
if q.Datacenter != "" {
r.params.Set("dc", q.Datacenter)
}
@ -655,6 +707,9 @@ func (r *request) setQueryOptions(q *QueryOptions) {
if q.RelayFactor != 0 {
r.params.Set("relay-factor", strconv.Itoa(int(q.RelayFactor)))
}
if q.LocalOnly {
r.params.Set("local-only", fmt.Sprintf("%t", q.LocalOnly))
}
if q.Connect {
r.params.Set("connect", "true")
}
@ -672,6 +727,7 @@ func (r *request) setQueryOptions(q *QueryOptions) {
r.header.Set("Cache-Control", strings.Join(cc, ", "))
}
}
r.ctx = q.ctx
}
@ -715,6 +771,9 @@ func (r *request) setWriteOptions(q *WriteOptions) {
if q == nil {
return
}
if q.Namespace != "" {
r.params.Set("ns", q.Namespace)
}
if q.Datacenter != "" {
r.params.Set("dc", q.Datacenter)
}
@ -779,6 +838,9 @@ func (c *Client) newRequest(method, path string) *request {
if c.config.Datacenter != "" {
r.params.Set("dc", c.config.Datacenter)
}
if c.config.Namespace != "" {
r.params.Set("ns", c.config.Namespace)
}
if c.config.WaitTime != 0 {
r.params.Set("wait", durToMsec(r.config.WaitTime))
}

View File

@ -1,5 +1,10 @@
package api
import (
"net"
"strconv"
)
type Weights struct {
Passing int
Warning int
@ -16,6 +21,11 @@ type Node struct {
ModifyIndex uint64
}
type ServiceAddress struct {
Address string
Port int
}
type CatalogService struct {
ID string
Node string
@ -26,17 +36,17 @@ type CatalogService struct {
ServiceID string
ServiceName string
ServiceAddress string
ServiceTaggedAddresses map[string]ServiceAddress
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
ServiceProxy *AgentServiceConnectProxyConfig
CreateIndex uint64
Checks HealthChecks
ModifyIndex uint64
Namespace string `json:",omitempty"`
}
type CatalogNode struct {
@ -44,6 +54,11 @@ type CatalogNode struct {
Services map[string]*AgentService
}
type CatalogNodeServiceList struct {
Node *Node
Services []*AgentService
}
type CatalogRegistration struct {
ID string
Node string
@ -59,10 +74,11 @@ type CatalogRegistration struct {
type CatalogDeregistration struct {
Node string
Address string // Obsolete.
Address string `json:",omitempty"` // Obsolete.
Datacenter string
ServiceID string
CheckID string
Namespace string `json:",omitempty"`
}
// Catalog can be used to query the Catalog endpoints
@ -242,3 +258,36 @@ func (c *Catalog) Node(node string, q *QueryOptions) (*CatalogNode, *QueryMeta,
}
return out, qm, nil
}
// NodeServiceList is used to query for service information about a single node. It differs from
// the Node function only in its return type which will contain a list of services as opposed to
// a map of service ids to services. This different structure allows for using the wildcard specifier
// '*' for the Namespace in the QueryOptions.
func (c *Catalog) NodeServiceList(node string, q *QueryOptions) (*CatalogNodeServiceList, *QueryMeta, error) {
r := c.c.newRequest("GET", "/v1/catalog/node-services/"+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 *CatalogNodeServiceList
if err := decodeBody(resp, &out); err != nil {
return nil, nil, err
}
return out, qm, nil
}
func ParseServiceAddr(addrPort string) (ServiceAddress, error) {
port := 0
host, portStr, err := net.SplitHostPort(addrPort)
if err == nil {
port, err = strconv.Atoi(portStr)
}
return ServiceAddress{Address: host, Port: port}, err
}

View File

@ -12,8 +12,12 @@ import (
)
const (
ServiceDefaults string = "service-defaults"
ProxyDefaults string = "proxy-defaults"
ServiceDefaults string = "service-defaults"
ProxyDefaults string = "proxy-defaults"
ServiceRouter string = "service-router"
ServiceSplitter string = "service-splitter"
ServiceResolver string = "service-resolver"
ProxyConfigGlobal string = "global"
)
@ -24,10 +28,71 @@ type ConfigEntry interface {
GetModifyIndex() uint64
}
type MeshGatewayMode string
const (
// MeshGatewayModeDefault represents no specific mode and should
// be used to indicate that a different layer of the configuration
// chain should take precedence
MeshGatewayModeDefault MeshGatewayMode = ""
// MeshGatewayModeNone represents that the Upstream Connect connections
// should be direct and not flow through a mesh gateway.
MeshGatewayModeNone MeshGatewayMode = "none"
// MeshGatewayModeLocal represents that the Upstrea Connect connections
// should be made to a mesh gateway in the local datacenter. This is
MeshGatewayModeLocal MeshGatewayMode = "local"
// MeshGatewayModeRemote represents that the Upstream Connect connections
// should be made to a mesh gateway in a remote datacenter.
MeshGatewayModeRemote MeshGatewayMode = "remote"
)
// MeshGatewayConfig controls how Mesh Gateways are used for upstream Connect
// services
type MeshGatewayConfig struct {
// Mode is the mode that should be used for the upstream connection.
Mode MeshGatewayMode `json:",omitempty"`
}
// ExposeConfig describes HTTP paths to expose through Envoy outside of Connect.
// Users can expose individual paths and/or all HTTP/GRPC paths for checks.
type ExposeConfig struct {
// Checks defines whether paths associated with Consul checks will be exposed.
// This flag triggers exposing all HTTP and GRPC check paths registered for the service.
Checks bool `json:",omitempty"`
// Paths is the list of paths exposed through the proxy.
Paths []ExposePath `json:",omitempty"`
}
type ExposePath struct {
// ListenerPort defines the port of the proxy's listener for exposed paths.
ListenerPort int `json:",omitempty"`
// Path is the path to expose through the proxy, ie. "/metrics."
Path string `json:",omitempty"`
// LocalPathPort is the port that the service is listening on for the given path.
LocalPathPort int `json:",omitempty"`
// Protocol describes the upstream's service protocol.
// Valid values are "http" and "http2", defaults to "http"
Protocol string `json:",omitempty"`
// ParsedFromCheck is set if this path was parsed from a registered check
ParsedFromCheck bool
}
type ServiceConfigEntry struct {
Kind string
Name string
Protocol string
Namespace string `json:",omitempty"`
Protocol string `json:",omitempty"`
MeshGateway MeshGatewayConfig `json:",omitempty"`
Expose ExposeConfig `json:",omitempty"`
ExternalSNI string `json:",omitempty"`
CreateIndex uint64
ModifyIndex uint64
}
@ -51,7 +116,10 @@ func (s *ServiceConfigEntry) GetModifyIndex() uint64 {
type ProxyConfigEntry struct {
Kind string
Name string
Config map[string]interface{}
Namespace string `json:",omitempty"`
Config map[string]interface{} `json:",omitempty"`
MeshGateway MeshGatewayConfig `json:",omitempty"`
Expose ExposeConfig `json:",omitempty"`
CreateIndex uint64
ModifyIndex uint64
}
@ -80,14 +148,35 @@ type rawEntryListResponse struct {
func makeConfigEntry(kind, name string) (ConfigEntry, error) {
switch kind {
case ServiceDefaults:
return &ServiceConfigEntry{Name: name}, nil
return &ServiceConfigEntry{Kind: kind, Name: name}, nil
case ProxyDefaults:
return &ProxyConfigEntry{Name: name}, nil
return &ProxyConfigEntry{Kind: kind, Name: name}, nil
case ServiceRouter:
return &ServiceRouterConfigEntry{Kind: kind, Name: name}, nil
case ServiceSplitter:
return &ServiceSplitterConfigEntry{Kind: kind, Name: name}, nil
case ServiceResolver:
return &ServiceResolverConfigEntry{Kind: kind, Name: name}, nil
default:
return nil, fmt.Errorf("invalid config entry kind: %s", kind)
}
}
func MakeConfigEntry(kind, name string) (ConfigEntry, error) {
return makeConfigEntry(kind, name)
}
// DecodeConfigEntry will decode the result of using json.Unmarshal of a config
// entry into a map[string]interface{}.
//
// Important caveats:
//
// - This will NOT work if the map[string]interface{} was produced using HCL
// decoding as that requires more extensive parsing to work around the issues
// with map[string][]interface{} that arise.
//
// - This will only decode fields using their camel case json field
// representations.
func DecodeConfigEntry(raw map[string]interface{}) (ConfigEntry, error) {
var entry ConfigEntry
@ -132,7 +221,19 @@ func DecodeConfigEntryFromJSON(data []byte) (ConfigEntry, error) {
return DecodeConfigEntry(raw)
}
// Config can be used to query the Config endpoints
func decodeConfigEntrySlice(raw []map[string]interface{}) ([]ConfigEntry, error) {
var entries []ConfigEntry
for _, rawEntry := range raw {
entry, err := DecodeConfigEntry(rawEntry)
if err != nil {
return nil, err
}
entries = append(entries, entry)
}
return entries, nil
}
// ConfigEntries can be used to query the Config endpoints
type ConfigEntries struct {
c *Client
}
@ -195,13 +296,9 @@ func (conf *ConfigEntries) List(kind string, q *QueryOptions) ([]ConfigEntry, *Q
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)
entries, err := decodeConfigEntrySlice(raw)
if err != nil {
return nil, nil, err
}
return entries, qm, nil

View File

@ -0,0 +1,203 @@
package api
import (
"encoding/json"
"time"
)
type ServiceRouterConfigEntry struct {
Kind string
Name string
Namespace string `json:",omitempty"`
Routes []ServiceRoute `json:",omitempty"`
CreateIndex uint64
ModifyIndex uint64
}
func (e *ServiceRouterConfigEntry) GetKind() string { return e.Kind }
func (e *ServiceRouterConfigEntry) GetName() string { return e.Name }
func (e *ServiceRouterConfigEntry) GetCreateIndex() uint64 { return e.CreateIndex }
func (e *ServiceRouterConfigEntry) GetModifyIndex() uint64 { return e.ModifyIndex }
type ServiceRoute struct {
Match *ServiceRouteMatch `json:",omitempty"`
Destination *ServiceRouteDestination `json:",omitempty"`
}
type ServiceRouteMatch struct {
HTTP *ServiceRouteHTTPMatch `json:",omitempty"`
}
type ServiceRouteHTTPMatch struct {
PathExact string `json:",omitempty"`
PathPrefix string `json:",omitempty"`
PathRegex string `json:",omitempty"`
Header []ServiceRouteHTTPMatchHeader `json:",omitempty"`
QueryParam []ServiceRouteHTTPMatchQueryParam `json:",omitempty"`
Methods []string `json:",omitempty"`
}
type ServiceRouteHTTPMatchHeader struct {
Name string
Present bool `json:",omitempty"`
Exact string `json:",omitempty"`
Prefix string `json:",omitempty"`
Suffix string `json:",omitempty"`
Regex string `json:",omitempty"`
Invert bool `json:",omitempty"`
}
type ServiceRouteHTTPMatchQueryParam struct {
Name string
Present bool `json:",omitempty"`
Exact string `json:",omitempty"`
Regex string `json:",omitempty"`
}
type ServiceRouteDestination struct {
Service string `json:",omitempty"`
ServiceSubset string `json:",omitempty"`
Namespace string `json:",omitempty"`
PrefixRewrite string `json:",omitempty"`
RequestTimeout time.Duration `json:",omitempty"`
NumRetries uint32 `json:",omitempty"`
RetryOnConnectFailure bool `json:",omitempty"`
RetryOnStatusCodes []uint32 `json:",omitempty"`
}
func (e *ServiceRouteDestination) MarshalJSON() ([]byte, error) {
type Alias ServiceRouteDestination
exported := &struct {
RequestTimeout string `json:",omitempty"`
*Alias
}{
RequestTimeout: e.RequestTimeout.String(),
Alias: (*Alias)(e),
}
if e.RequestTimeout == 0 {
exported.RequestTimeout = ""
}
return json.Marshal(exported)
}
func (e *ServiceRouteDestination) UnmarshalJSON(data []byte) error {
type Alias ServiceRouteDestination
aux := &struct {
RequestTimeout string
*Alias
}{
Alias: (*Alias)(e),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
var err error
if aux.RequestTimeout != "" {
if e.RequestTimeout, err = time.ParseDuration(aux.RequestTimeout); err != nil {
return err
}
}
return nil
}
type ServiceSplitterConfigEntry struct {
Kind string
Name string
Namespace string `json:",omitempty"`
Splits []ServiceSplit `json:",omitempty"`
CreateIndex uint64
ModifyIndex uint64
}
func (e *ServiceSplitterConfigEntry) GetKind() string { return e.Kind }
func (e *ServiceSplitterConfigEntry) GetName() string { return e.Name }
func (e *ServiceSplitterConfigEntry) GetCreateIndex() uint64 { return e.CreateIndex }
func (e *ServiceSplitterConfigEntry) GetModifyIndex() uint64 { return e.ModifyIndex }
type ServiceSplit struct {
Weight float32
Service string `json:",omitempty"`
ServiceSubset string `json:",omitempty"`
Namespace string `json:",omitempty"`
}
type ServiceResolverConfigEntry struct {
Kind string
Name string
Namespace string `json:",omitempty"`
DefaultSubset string `json:",omitempty"`
Subsets map[string]ServiceResolverSubset `json:",omitempty"`
Redirect *ServiceResolverRedirect `json:",omitempty"`
Failover map[string]ServiceResolverFailover `json:",omitempty"`
ConnectTimeout time.Duration `json:",omitempty"`
CreateIndex uint64
ModifyIndex uint64
}
func (e *ServiceResolverConfigEntry) MarshalJSON() ([]byte, error) {
type Alias ServiceResolverConfigEntry
exported := &struct {
ConnectTimeout string `json:",omitempty"`
*Alias
}{
ConnectTimeout: e.ConnectTimeout.String(),
Alias: (*Alias)(e),
}
if e.ConnectTimeout == 0 {
exported.ConnectTimeout = ""
}
return json.Marshal(exported)
}
func (e *ServiceResolverConfigEntry) UnmarshalJSON(data []byte) error {
type Alias ServiceResolverConfigEntry
aux := &struct {
ConnectTimeout string
*Alias
}{
Alias: (*Alias)(e),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
var err error
if aux.ConnectTimeout != "" {
if e.ConnectTimeout, err = time.ParseDuration(aux.ConnectTimeout); err != nil {
return err
}
}
return nil
}
func (e *ServiceResolverConfigEntry) GetKind() string { return e.Kind }
func (e *ServiceResolverConfigEntry) GetName() string { return e.Name }
func (e *ServiceResolverConfigEntry) GetCreateIndex() uint64 { return e.CreateIndex }
func (e *ServiceResolverConfigEntry) GetModifyIndex() uint64 { return e.ModifyIndex }
type ServiceResolverSubset struct {
Filter string `json:",omitempty"`
OnlyPassing bool `json:",omitempty"`
}
type ServiceResolverRedirect struct {
Service string `json:",omitempty"`
ServiceSubset string `json:",omitempty"`
Namespace string `json:",omitempty"`
Datacenter string `json:",omitempty"`
}
type ServiceResolverFailover struct {
Service string `json:",omitempty"`
ServiceSubset string `json:",omitempty"`
Namespace string `json:",omitempty"`
Datacenters []string `json:",omitempty"`
}

View File

@ -17,6 +17,12 @@ type CAConfig struct {
// and maps).
Config map[string]interface{}
// State is read-only data that the provider might have persisted for use
// after restart or leadership transition. For example this might include
// UUIDs of resources it has created. Setting this when writing a
// configuration is an error.
State map[string]string
CreateIndex uint64
ModifyIndex uint64
}
@ -33,9 +39,10 @@ type CommonCAProviderConfig struct {
type ConsulCAProviderConfig struct {
CommonCAProviderConfig `mapstructure:",squash"`
PrivateKey string
RootCert string
RotationPeriod time.Duration
PrivateKey string
RootCert string
RotationPeriod time.Duration
IntermediateCertTTL time.Duration
}
// ParseConsulCAConfig takes a raw config map and returns a parsed

View File

@ -54,6 +54,13 @@ type Intention struct {
// or modified.
CreatedAt, UpdatedAt time.Time
// Hash of the contents of the intention
//
// This is needed mainly for replication purposes. When replicating from
// one DC to another keeping the content Hash will allow us to detect
// content changes more efficiently than checking every single field
Hash []byte
CreateIndex uint64
ModifyIndex uint64
}

View File

@ -84,7 +84,7 @@ func (c *Coordinate) Update(coord *CoordinateEntry, q *WriteOptions) (*WriteMeta
return wm, nil
}
// Node is used to return the coordinates of a single in the LAN pool.
// Node is used to return the coordinates of a single node 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)

View File

@ -0,0 +1,230 @@
package api
import (
"encoding/json"
"fmt"
"time"
)
// DiscoveryChain can be used to query the discovery-chain endpoints
type DiscoveryChain struct {
c *Client
}
// DiscoveryChain returns a handle to the discovery-chain endpoints
func (c *Client) DiscoveryChain() *DiscoveryChain {
return &DiscoveryChain{c}
}
func (d *DiscoveryChain) Get(name string, opts *DiscoveryChainOptions, q *QueryOptions) (*DiscoveryChainResponse, *QueryMeta, error) {
if name == "" {
return nil, nil, fmt.Errorf("Name parameter must not be empty")
}
method := "GET"
if opts != nil && opts.requiresPOST() {
method = "POST"
}
r := d.c.newRequest(method, fmt.Sprintf("/v1/discovery-chain/%s", name))
r.setQueryOptions(q)
if opts != nil {
if opts.EvaluateInDatacenter != "" {
r.params.Set("compile-dc", opts.EvaluateInDatacenter)
}
// TODO(namespaces): handle possible EvaluateInNamespace here
}
if method == "POST" {
r.obj = opts
}
rtt, resp, err := requireOK(d.c.doRequest(r))
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
qm := &QueryMeta{}
parseQueryMeta(resp, qm)
qm.RequestTime = rtt
var out DiscoveryChainResponse
if err := decodeBody(resp, &out); err != nil {
return nil, nil, err
}
return &out, qm, nil
}
type DiscoveryChainOptions struct {
EvaluateInDatacenter string `json:"-"`
// OverrideMeshGateway allows for the mesh gateway setting to be overridden
// for any resolver in the compiled chain.
OverrideMeshGateway MeshGatewayConfig `json:",omitempty"`
// OverrideProtocol allows for the final protocol for the chain to be
// altered.
//
// - If the chain ordinarily would be TCP and an L7 protocol is passed here
// the chain will not include Routers or Splitters.
//
// - If the chain ordinarily would be L7 and TCP is passed here the chain
// will not include Routers or Splitters.
OverrideProtocol string `json:",omitempty"`
// OverrideConnectTimeout allows for the ConnectTimeout setting to be
// overridden for any resolver in the compiled chain.
OverrideConnectTimeout time.Duration `json:",omitempty"`
}
func (o *DiscoveryChainOptions) requiresPOST() bool {
if o == nil {
return false
}
return o.OverrideMeshGateway.Mode != "" ||
o.OverrideProtocol != "" ||
o.OverrideConnectTimeout != 0
}
type DiscoveryChainResponse struct {
Chain *CompiledDiscoveryChain
}
type CompiledDiscoveryChain struct {
ServiceName string
Namespace string
Datacenter string
// CustomizationHash is a unique hash of any data that affects the
// compilation of the discovery chain other than config entries or the
// name/namespace/datacenter evaluation criteria.
//
// If set, this value should be used to prefix/suffix any generated load
// balancer data plane objects to avoid sharing customized and
// non-customized versions.
CustomizationHash string
// Protocol is the overall protocol shared by everything in the chain.
Protocol string
// StartNode is the first key into the Nodes map that should be followed
// when walking the discovery chain.
StartNode string
// Nodes contains all nodes available for traversal in the chain keyed by a
// unique name. You can walk this by starting with StartNode.
//
// NOTE: The names should be treated as opaque values and are only
// guaranteed to be consistent within a single compilation.
Nodes map[string]*DiscoveryGraphNode
// Targets is a list of all targets used in this chain.
//
// NOTE: The names should be treated as opaque values and are only
// guaranteed to be consistent within a single compilation.
Targets map[string]*DiscoveryTarget
}
const (
DiscoveryGraphNodeTypeRouter = "router"
DiscoveryGraphNodeTypeSplitter = "splitter"
DiscoveryGraphNodeTypeResolver = "resolver"
)
// DiscoveryGraphNode is a single node in the compiled discovery chain.
type DiscoveryGraphNode struct {
Type string
Name string // this is NOT necessarily a service
// fields for Type==router
Routes []*DiscoveryRoute
// fields for Type==splitter
Splits []*DiscoverySplit
// fields for Type==resolver
Resolver *DiscoveryResolver
}
// compiled form of ServiceRoute
type DiscoveryRoute struct {
Definition *ServiceRoute
NextNode string
}
// compiled form of ServiceSplit
type DiscoverySplit struct {
Weight float32
NextNode string
}
// compiled form of ServiceResolverConfigEntry
type DiscoveryResolver struct {
Default bool
ConnectTimeout time.Duration
Target string
Failover *DiscoveryFailover
}
func (r *DiscoveryResolver) MarshalJSON() ([]byte, error) {
type Alias DiscoveryResolver
exported := &struct {
ConnectTimeout string `json:",omitempty"`
*Alias
}{
ConnectTimeout: r.ConnectTimeout.String(),
Alias: (*Alias)(r),
}
if r.ConnectTimeout == 0 {
exported.ConnectTimeout = ""
}
return json.Marshal(exported)
}
func (r *DiscoveryResolver) UnmarshalJSON(data []byte) error {
type Alias DiscoveryResolver
aux := &struct {
ConnectTimeout string
*Alias
}{
Alias: (*Alias)(r),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
var err error
if aux.ConnectTimeout != "" {
if r.ConnectTimeout, err = time.ParseDuration(aux.ConnectTimeout); err != nil {
return err
}
}
return nil
}
// compiled form of ServiceResolverFailover
type DiscoveryFailover struct {
Targets []string
}
// DiscoveryTarget represents all of the inputs necessary to use a resolver
// config entry to execute a catalog query to generate a list of service
// instances during discovery.
type DiscoveryTarget struct {
ID string
Service string
ServiceSubset string
Namespace string
Datacenter string
MeshGateway MeshGatewayConfig
Subset ServiceResolverSubset
External bool
SNI string
Name string
}

View File

@ -5,12 +5,11 @@ go 1.12
replace github.com/hashicorp/consul/sdk => ../sdk
require (
github.com/hashicorp/consul/sdk v0.1.1
github.com/hashicorp/consul/sdk v0.2.0
github.com/hashicorp/go-cleanhttp v0.5.1
github.com/hashicorp/go-rootcerts v1.0.0
github.com/hashicorp/go-rootcerts v1.0.2
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
)

View File

@ -21,6 +21,8 @@ github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uP
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-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
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=
@ -43,6 +45,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N
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-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.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=
@ -74,3 +78,5 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
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=
golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd h1:3x5uuvBgE6oaXJjCOvpCC1IpgJogqQ+PqGGU3ZxAgII=
golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -36,6 +36,8 @@ type HealthCheck struct {
ServiceID string
ServiceName string
ServiceTags []string
Type string
Namespace string `json:",omitempty"`
Definition HealthCheckDefinition
@ -94,40 +96,63 @@ func (d *HealthCheckDefinition) MarshalJSON() ([]byte, error) {
return json.Marshal(out)
}
func (d *HealthCheckDefinition) UnmarshalJSON(data []byte) error {
func (t *HealthCheckDefinition) UnmarshalJSON(data []byte) (err error) {
type Alias HealthCheckDefinition
aux := &struct {
Interval string
Timeout string
DeregisterCriticalServiceAfter string
IntervalDuration interface{}
TimeoutDuration interface{}
DeregisterCriticalServiceAfterDuration interface{}
*Alias
}{
Alias: (*Alias)(d),
Alias: (*Alias)(t),
}
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
if aux.IntervalDuration == nil {
t.IntervalDuration = time.Duration(t.Interval)
} else {
switch v := aux.IntervalDuration.(type) {
case string:
if t.IntervalDuration, err = time.ParseDuration(v); err != nil {
return err
}
case float64:
t.IntervalDuration = time.Duration(v)
}
d.Interval = ReadableDuration(d.IntervalDuration)
t.Interval = ReadableDuration(t.IntervalDuration)
}
if aux.Timeout != "" {
if d.TimeoutDuration, err = time.ParseDuration(aux.Timeout); err != nil {
return err
if aux.TimeoutDuration == nil {
t.TimeoutDuration = time.Duration(t.Timeout)
} else {
switch v := aux.TimeoutDuration.(type) {
case string:
if t.TimeoutDuration, err = time.ParseDuration(v); err != nil {
return err
}
case float64:
t.TimeoutDuration = time.Duration(v)
}
d.Timeout = ReadableDuration(d.TimeoutDuration)
t.Timeout = ReadableDuration(t.TimeoutDuration)
}
if aux.DeregisterCriticalServiceAfter != "" {
if d.DeregisterCriticalServiceAfterDuration, err = time.ParseDuration(aux.DeregisterCriticalServiceAfter); err != nil {
return err
if aux.DeregisterCriticalServiceAfterDuration == nil {
t.DeregisterCriticalServiceAfterDuration = time.Duration(t.DeregisterCriticalServiceAfter)
} else {
switch v := aux.DeregisterCriticalServiceAfterDuration.(type) {
case string:
if t.DeregisterCriticalServiceAfterDuration, err = time.ParseDuration(v); err != nil {
return err
}
case float64:
t.DeregisterCriticalServiceAfterDuration = time.Duration(v)
}
d.DeregisterCriticalServiceAfter = ReadableDuration(d.DeregisterCriticalServiceAfterDuration)
t.DeregisterCriticalServiceAfter = ReadableDuration(t.DeregisterCriticalServiceAfterDuration)
}
return nil
}

View File

@ -40,6 +40,10 @@ type KVPair struct {
// interactions with this key over the same session must specify the same
// session ID.
Session string
// Namespace is the namespace the KVPair is associated with
// Namespacing is a Consul Enterprise feature.
Namespace string `json:",omitempty"`
}
// KVPairs is a list of KVPair objects

View File

@ -79,6 +79,7 @@ type LockOptions struct {
MonitorRetryTime time.Duration // Optional, defaults to DefaultMonitorRetryTime
LockWaitTime time.Duration // Optional, defaults to DefaultLockWaitTime
LockTryOnce bool // Optional, defaults to false which means try forever
Namespace string `json:",omitempty"` // Optional, defaults to API client config, namespace of ACL token, or "default" namespace
}
// LockKey returns a handle to a lock struct which can be used
@ -140,6 +141,10 @@ func (l *Lock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
return nil, ErrLockHeld
}
wOpts := WriteOptions{
Namespace: l.opts.Namespace,
}
// Check if we need to create a session first
l.lockSession = l.opts.Session
if l.lockSession == "" {
@ -150,8 +155,9 @@ func (l *Lock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
l.sessionRenew = make(chan struct{})
l.lockSession = s
session := l.c.Session()
go session.RenewPeriodic(l.opts.SessionTTL, s, nil, l.sessionRenew)
go session.RenewPeriodic(l.opts.SessionTTL, s, &wOpts, l.sessionRenew)
// If we fail to acquire the lock, cleanup the session
defer func() {
@ -164,8 +170,9 @@ func (l *Lock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
// Setup the query options
kv := l.c.KV()
qOpts := &QueryOptions{
WaitTime: l.opts.LockWaitTime,
qOpts := QueryOptions{
WaitTime: l.opts.LockWaitTime,
Namespace: l.opts.Namespace,
}
start := time.Now()
@ -191,7 +198,7 @@ WAIT:
attempts++
// Look for an existing lock, blocking until not taken
pair, meta, err := kv.Get(l.opts.Key, qOpts)
pair, meta, err := kv.Get(l.opts.Key, &qOpts)
if err != nil {
return nil, fmt.Errorf("failed to read lock: %v", err)
}
@ -209,7 +216,8 @@ WAIT:
// Try to acquire the lock
pair = l.lockEntry(l.lockSession)
locked, _, err = kv.Acquire(pair, nil)
locked, _, err = kv.Acquire(pair, &wOpts)
if err != nil {
return nil, fmt.Errorf("failed to acquire lock: %v", err)
}
@ -218,7 +226,7 @@ WAIT:
if !locked {
// Determine why the lock failed
qOpts.WaitIndex = 0
pair, meta, err = kv.Get(l.opts.Key, qOpts)
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
@ -277,7 +285,9 @@ func (l *Lock) Unlock() error {
// Release the lock explicitly
kv := l.c.KV()
_, _, err := kv.Release(lockEnt, nil)
w := WriteOptions{Namespace: l.opts.Namespace}
_, _, err := kv.Release(lockEnt, &w)
if err != nil {
return fmt.Errorf("failed to release lock: %v", err)
}
@ -298,7 +308,9 @@ func (l *Lock) Destroy() error {
// Look for an existing lock
kv := l.c.KV()
pair, _, err := kv.Get(l.opts.Key, nil)
q := QueryOptions{Namespace: l.opts.Namespace}
pair, _, err := kv.Get(l.opts.Key, &q)
if err != nil {
return fmt.Errorf("failed to read lock: %v", err)
}
@ -319,7 +331,8 @@ func (l *Lock) Destroy() error {
}
// Attempt the delete
didRemove, _, err := kv.DeleteCAS(pair, nil)
w := WriteOptions{Namespace: l.opts.Namespace}
didRemove, _, err := kv.DeleteCAS(pair, &w)
if err != nil {
return fmt.Errorf("failed to remove lock: %v", err)
}
@ -339,7 +352,8 @@ func (l *Lock) createSession() (string, error) {
TTL: l.opts.SessionTTL,
}
}
id, _, err := session.Create(se, nil)
w := WriteOptions{Namespace: l.opts.Namespace}
id, _, err := session.Create(se, &w)
if err != nil {
return "", err
}
@ -361,11 +375,14 @@ func (l *Lock) lockEntry(session string) *KVPair {
func (l *Lock) monitorLock(session string, stopCh chan struct{}) {
defer close(stopCh)
kv := l.c.KV()
opts := &QueryOptions{RequireConsistent: true}
opts := QueryOptions{
RequireConsistent: true,
Namespace: l.opts.Namespace,
}
WAIT:
retries := l.opts.MonitorRetries
RETRY:
pair, meta, err := kv.Get(l.opts.Key, opts)
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-

159
vendor/github.com/hashicorp/consul/api/namespace.go generated vendored Normal file
View File

@ -0,0 +1,159 @@
package api
import (
"fmt"
"time"
)
// Namespace is the configuration of a single namespace. Namespacing is a Consul Enterprise feature.
type Namespace struct {
// Name is the name of the Namespace. It must be unique and
// must be a DNS hostname. There are also other reserved names
// that may not be used.
Name string `json:"Name"`
// Description is where the user puts any information they want
// about the namespace. It is not used internally.
Description string `json:"Description,omitempty"`
// ACLs is the configuration of ACLs for this namespace. It has its
// own struct so that we can add more to it in the future.
// This is nullable so that we can omit if empty when encoding in JSON
ACLs *NamespaceACLConfig `json:"ACLs,omitempty"`
// Meta is a map that can be used to add kv metadata to the namespace definition
Meta map[string]string `json:"Meta,omitempty"`
// DeletedAt is the time when the Namespace was marked for deletion
// This is nullable so that we can omit if empty when encoding in JSON
DeletedAt *time.Time `json:"DeletedAt,omitempty"`
// CreateIndex is the Raft index at which the Namespace was created
CreateIndex uint64 `json:"CreateIndex,omitempty"`
// ModifyIndex is the latest Raft index at which the Namespace was modified.
ModifyIndex uint64 `json:"ModifyIndex,omitempty"`
}
// NamespaceACLConfig is the Namespace specific ACL configuration container
type NamespaceACLConfig struct {
// PolicyDefaults is the list of policies that should be used for the parent authorizer
// of all tokens in the associated namespace.
PolicyDefaults []ACLLink `json:"PolicyDefaults"`
// RoleDefaults is the list of roles that should be used for the parent authorizer
// of all tokens in the associated namespace.
RoleDefaults []ACLLink `json:"RoleDefaults"`
}
// Namespaces can be used to manage Namespaces in Consul Enterprise..
type Namespaces struct {
c *Client
}
// Operator returns a handle to the operator endpoints.
func (c *Client) Namespaces() *Namespaces {
return &Namespaces{c}
}
func (n *Namespaces) Create(ns *Namespace, q *WriteOptions) (*Namespace, *WriteMeta, error) {
if ns.Name == "" {
return nil, nil, fmt.Errorf("Must specify a Name for Namespace creation")
}
r := n.c.newRequest("PUT", "/v1/namespace")
r.setWriteOptions(q)
r.obj = ns
rtt, resp, err := requireOK(n.c.doRequest(r))
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
wm := &WriteMeta{RequestTime: rtt}
var out Namespace
if err := decodeBody(resp, &out); err != nil {
return nil, nil, err
}
return &out, wm, nil
}
func (n *Namespaces) Update(ns *Namespace, q *WriteOptions) (*Namespace, *WriteMeta, error) {
if ns.Name == "" {
return nil, nil, fmt.Errorf("Must specify a Name for Namespace updating")
}
r := n.c.newRequest("PUT", "/v1/namespace/"+ns.Name)
r.setWriteOptions(q)
r.obj = ns
rtt, resp, err := requireOK(n.c.doRequest(r))
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
wm := &WriteMeta{RequestTime: rtt}
var out Namespace
if err := decodeBody(resp, &out); err != nil {
return nil, nil, err
}
return &out, wm, nil
}
func (n *Namespaces) Read(name string, q *QueryOptions) (*Namespace, *QueryMeta, error) {
var out Namespace
r := n.c.newRequest("GET", "/v1/namespace/"+name)
r.setQueryOptions(q)
found, rtt, resp, err := requireNotFoundOrOK(n.c.doRequest(r))
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
qm := &QueryMeta{}
parseQueryMeta(resp, qm)
qm.RequestTime = rtt
if !found {
return nil, qm, nil
}
if err := decodeBody(resp, &out); err != nil {
return nil, nil, err
}
return &out, qm, nil
}
func (n *Namespaces) Delete(name string, q *WriteOptions) (*WriteMeta, error) {
r := n.c.newRequest("DELETE", "/v1/namespace/"+name)
r.setWriteOptions(q)
rtt, resp, err := requireOK(n.c.doRequest(r))
if err != nil {
return nil, err
}
resp.Body.Close()
wm := &WriteMeta{RequestTime: rtt}
return wm, nil
}
func (n *Namespaces) List(q *QueryOptions) ([]*Namespace, *QueryMeta, error) {
var out []*Namespace
r := n.c.newRequest("GET", "/v1/namespaces")
r.setQueryOptions(q)
rtt, resp, err := requireOK(n.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, &out); err != nil {
return nil, nil, err
}
return out, qm, nil
}

View File

@ -25,6 +25,10 @@ type AutopilotConfiguration struct {
// be behind before being considered unhealthy.
MaxTrailingLogs uint64
// MinQuorum sets the minimum number of servers allowed in a cluster before
// autopilot can prune dead servers.
MinQuorum uint
// 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.
@ -130,19 +134,28 @@ func (d *ReadableDuration) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%s"`, d.Duration().String())), nil
}
func (d *ReadableDuration) UnmarshalJSON(raw []byte) error {
func (d *ReadableDuration) UnmarshalJSON(raw []byte) (err error) {
if d == nil {
return fmt.Errorf("cannot unmarshal to nil pointer")
}
var dur time.Duration
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
if len(str) >= 2 && str[0] == '"' && str[len(str)-1] == '"' {
// quoted string
dur, err = time.ParseDuration(str[1 : len(str)-1])
if err != nil {
return err
}
} else {
// no quotes, not a string
v, err := strconv.ParseFloat(str, 64)
if err != nil {
return err
}
dur = time.Duration(v)
}
*d = ReadableDuration(dur)
return nil
}

View File

@ -0,0 +1,111 @@
package api
import (
"io/ioutil"
"strings"
"time"
)
type License struct {
// The unique identifier of the license
LicenseID string `json:"license_id"`
// The customer ID associated with the license
CustomerID string `json:"customer_id"`
// If set, an identifier that should be used to lock the license to a
// particular site, cluster, etc.
InstallationID string `json:"installation_id"`
// The time at which the license was issued
IssueTime time.Time `json:"issue_time"`
// The time at which the license starts being valid
StartTime time.Time `json:"start_time"`
// The time after which the license expires
ExpirationTime time.Time `json:"expiration_time"`
// The time at which the license ceases to function and can
// no longer be used in any capacity
TerminationTime time.Time `json:"termination_time"`
// The product the license is valid for
Product string `json:"product"`
// License Specific Flags
Flags map[string]interface{} `json:"flags"`
// List of features enabled by the license
Features []string `json:"features"`
}
type LicenseReply struct {
Valid bool
License *License
Warnings []string
}
func (op *Operator) LicenseGet(q *QueryOptions) (*LicenseReply, error) {
var reply LicenseReply
if _, err := op.c.query("/v1/operator/license", &reply, q); err != nil {
return nil, err
} else {
return &reply, nil
}
}
func (op *Operator) LicenseGetSigned(q *QueryOptions) (string, error) {
r := op.c.newRequest("GET", "/v1/operator/license")
r.params.Set("signed", "1")
r.setQueryOptions(q)
_, resp, err := requireOK(op.c.doRequest(r))
if err != nil {
return "", err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(data), nil
}
// LicenseReset will reset the license to the builtin one if it is still valid.
// If the builtin license is invalid, the current license stays active.
func (op *Operator) LicenseReset(opts *WriteOptions) (*LicenseReply, error) {
var reply LicenseReply
r := op.c.newRequest("DELETE", "/v1/operator/license")
r.setWriteOptions(opts)
_, resp, err := requireOK(op.c.doRequest(r))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if err := decodeBody(resp, &reply); err != nil {
return nil, err
}
return &reply, nil
}
func (op *Operator) LicensePut(license string, opts *WriteOptions) (*LicenseReply, error) {
var reply LicenseReply
r := op.c.newRequest("PUT", "/v1/operator/license")
r.setWriteOptions(opts)
r.body = strings.NewReader(license)
_, resp, err := requireOK(op.c.doRequest(r))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if err := decodeBody(resp, &reply); err != nil {
return nil, err
}
return &reply, nil
}

View File

@ -73,6 +73,7 @@ type SemaphoreOptions struct {
MonitorRetryTime time.Duration // Optional, defaults to DefaultMonitorRetryTime
SemaphoreWaitTime time.Duration // Optional, defaults to DefaultSemaphoreWaitTime
SemaphoreTryOnce bool // Optional, defaults to false which means try forever
Namespace string `json:",omitempty"` // Optional, defaults to API client config, namespace of ACL token, or "default" namespace
}
// semaphoreLock is written under the DefaultSemaphoreKey and
@ -176,14 +177,17 @@ func (s *Semaphore) Acquire(stopCh <-chan struct{}) (<-chan struct{}, error) {
// Create the contender entry
kv := s.c.KV()
made, _, err := kv.Acquire(s.contenderEntry(s.lockSession), nil)
wOpts := WriteOptions{Namespace: s.opts.Namespace}
made, _, err := kv.Acquire(s.contenderEntry(s.lockSession), &wOpts)
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,
qOpts := QueryOptions{
WaitTime: s.opts.SemaphoreWaitTime,
Namespace: s.opts.Namespace,
}
start := time.Now()
@ -209,7 +213,7 @@ WAIT:
attempts++
// Read the prefix
pairs, meta, err := kv.List(s.opts.Prefix, qOpts)
pairs, meta, err := kv.List(s.opts.Prefix, &qOpts)
if err != nil {
return nil, fmt.Errorf("failed to read prefix: %v", err)
}
@ -247,7 +251,7 @@ WAIT:
}
// Attempt the acquisition
didSet, _, err := kv.CAS(newLock, nil)
didSet, _, err := kv.CAS(newLock, &wOpts)
if err != nil {
return nil, fmt.Errorf("failed to update lock: %v", err)
}
@ -298,8 +302,12 @@ func (s *Semaphore) Release() error {
// Remove ourselves as a lock holder
kv := s.c.KV()
key := path.Join(s.opts.Prefix, DefaultSemaphoreKey)
wOpts := WriteOptions{Namespace: s.opts.Namespace}
qOpts := QueryOptions{Namespace: s.opts.Namespace}
READ:
pair, _, err := kv.Get(key, nil)
pair, _, err := kv.Get(key, &qOpts)
if err != nil {
return err
}
@ -320,7 +328,7 @@ READ:
}
// Swap the locks
didSet, _, err := kv.CAS(newLock, nil)
didSet, _, err := kv.CAS(newLock, &wOpts)
if err != nil {
return fmt.Errorf("failed to update lock: %v", err)
}
@ -331,7 +339,7 @@ READ:
// Destroy the contender entry
contenderKey := path.Join(s.opts.Prefix, lockSession)
if _, err := kv.Delete(contenderKey, nil); err != nil {
if _, err := kv.Delete(contenderKey, &wOpts); err != nil {
return err
}
return nil
@ -351,7 +359,9 @@ func (s *Semaphore) Destroy() error {
// List for the semaphore
kv := s.c.KV()
pairs, _, err := kv.List(s.opts.Prefix, nil)
q := QueryOptions{Namespace: s.opts.Namespace}
pairs, _, err := kv.List(s.opts.Prefix, &q)
if err != nil {
return fmt.Errorf("failed to read prefix: %v", err)
}
@ -380,7 +390,8 @@ func (s *Semaphore) Destroy() error {
}
// Attempt the delete
didRemove, _, err := kv.DeleteCAS(lockPair, nil)
w := WriteOptions{Namespace: s.opts.Namespace}
didRemove, _, err := kv.DeleteCAS(lockPair, &w)
if err != nil {
return fmt.Errorf("failed to remove semaphore: %v", err)
}
@ -398,7 +409,9 @@ func (s *Semaphore) createSession() (string, error) {
TTL: s.opts.SessionTTL,
Behavior: SessionBehaviorDelete,
}
id, _, err := session.Create(se, nil)
w := WriteOptions{Namespace: s.opts.Namespace}
id, _, err := session.Create(se, &w)
if err != nil {
return "", err
}
@ -483,11 +496,14 @@ func (s *Semaphore) pruneDeadHolders(lock *semaphoreLock, pairs KVPairs) {
func (s *Semaphore) monitorLock(session string, stopCh chan struct{}) {
defer close(stopCh)
kv := s.c.KV()
opts := &QueryOptions{RequireConsistent: true}
opts := QueryOptions{
RequireConsistent: true,
Namespace: s.opts.Namespace,
}
WAIT:
retries := s.opts.MonitorRetries
RETRY:
pairs, meta, err := kv.List(s.opts.Prefix, opts)
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-

View File

@ -25,10 +25,23 @@ type SessionEntry struct {
ID string
Name string
Node string
Checks []string
LockDelay time.Duration
Behavior string
TTL string
Namespace string `json:",omitempty"`
// Deprecated for Consul Enterprise in v1.7.0.
Checks []string
// NodeChecks and ServiceChecks are new in Consul 1.7.0.
// When associating checks with sessions, namespaces can be specified for service checks.
NodeChecks []string
ServiceChecks []ServiceCheck
}
type ServiceCheck struct {
ID string
Namespace string
}
// Session can be used to query the Session endpoints
@ -45,7 +58,7 @@ func (c *Client) Session() *Session {
// 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{}
body["NodeChecks"] = []string{}
if se != nil {
if se.Name != "" {
body["Name"] = se.Name
@ -86,6 +99,12 @@ func (s *Session) Create(se *SessionEntry, q *WriteOptions) (string, *WriteMeta,
if len(se.Checks) > 0 {
body["Checks"] = se.Checks
}
if len(se.NodeChecks) > 0 {
body["NodeChecks"] = se.NodeChecks
}
if len(se.ServiceChecks) > 0 {
body["ServiceChecks"] = se.ServiceChecks
}
if se.Behavior != "" {
body["Behavior"] = se.Behavior
}

View File

@ -93,6 +93,19 @@ type KVTxnResponse struct {
Errors TxnErrors
}
// SessionOp constants give possible operations available in a transaction.
type SessionOp string
const (
SessionDelete SessionOp = "delete"
)
// SessionTxnOp defines a single operation inside a transaction.
type SessionTxnOp struct {
Verb SessionOp
Session Session
}
// NodeOp constants give possible operations available in a transaction.
type NodeOp string

View File

@ -72,6 +72,7 @@ github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrj
github.com/hashicorp/go-hclog v0.10.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.10.1 h1:uyt/l0dWjJ879yiAu+T7FG3/6QX+zwm4bQ8P7XsYt3o=
github.com/hashicorp/go-hclog v0.10.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-kms-wrapping v0.0.0-20191129225826-634facde9f88/go.mod h1:Pm+Umb/6Gij6ZG534L7QDyvkauaOQWGb+arj9aFjCE0=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=

View File

@ -127,9 +127,9 @@ func SetupTLSConfig(conf map[string]string, address string) (*tls.Config, error)
}
insecureSkipVerify := false
tlsSkipVerify, ok := conf["tls_skip_verify"]
tlsSkipVerify := conf["tls_skip_verify"]
if ok && tlsSkipVerify != "" {
if tlsSkipVerify != "" {
b, err := parseutil.ParseBool(tlsSkipVerify)
if err != nil {
return nil, errwrap.Wrapf("failed parsing tls_skip_verify parameter: {{err}}", err)

2
vendor/modules.txt vendored
View File

@ -297,7 +297,7 @@ github.com/hashicorp/consul-template/signals
github.com/hashicorp/consul-template/template
github.com/hashicorp/consul-template/version
github.com/hashicorp/consul-template/watch
# github.com/hashicorp/consul/api v1.1.0
# github.com/hashicorp/consul/api v1.2.1-0.20200128105449-6681be918a6e
github.com/hashicorp/consul/api
# github.com/hashicorp/errwrap v1.0.0
github.com/hashicorp/errwrap

View File

@ -35,6 +35,15 @@ Consul tokens.
- `token` `(string: <required>)` Specifies the Consul ACL token to use. This
must be a management type token.
- `ca_cert` `(string: "")` - CA certificate to use when verifying Consul server certificate,
must be x509 PEM encoded.
- `client_cert` `(string: "")` - Client certificate used for Consul's TLS communication,
must be x509 PEM encoded and if this is set you need to also set client_key.
- `client_key` `(string: "")` - Client key used for Consul's TLS communication,
must be x509 PEM encoded and if this is set you need to also set client_cert.
### Sample Payload
```json