mirror of https://github.com/hashicorp/consul
692 lines
26 KiB
Go
692 lines
26 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/mitchellh/mapstructure"
|
|
)
|
|
|
|
const (
|
|
ServiceDefaults string = "service-defaults"
|
|
ProxyDefaults string = "proxy-defaults"
|
|
ServiceRouter string = "service-router"
|
|
ServiceSplitter string = "service-splitter"
|
|
ServiceResolver string = "service-resolver"
|
|
IngressGateway string = "ingress-gateway"
|
|
TerminatingGateway string = "terminating-gateway"
|
|
ServiceIntentions string = "service-intentions"
|
|
MeshConfig string = "mesh"
|
|
ExportedServices string = "exported-services"
|
|
SamenessGroup string = "sameness-group"
|
|
RateLimitIPConfig string = "control-plane-request-limit"
|
|
|
|
ProxyConfigGlobal string = "global"
|
|
MeshConfigMesh string = "mesh"
|
|
APIGateway string = "api-gateway"
|
|
TCPRoute string = "tcp-route"
|
|
FileSystemCertificate string = "file-system-certificate"
|
|
InlineCertificate string = "inline-certificate"
|
|
HTTPRoute string = "http-route"
|
|
JWTProvider string = "jwt-provider"
|
|
)
|
|
|
|
const (
|
|
BuiltinAWSLambdaExtension string = "builtin/aws/lambda"
|
|
BuiltinExtAuthzExtension string = "builtin/ext-authz"
|
|
BuiltinLuaExtension string = "builtin/lua"
|
|
BuiltinOTELAccessLoggingExtension string = "builtin/otel-access-logging"
|
|
BuiltinPropertyOverrideExtension string = "builtin/property-override"
|
|
BuiltinWasmExtension string = "builtin/wasm"
|
|
// BuiltinValidateExtension should not be exposed directly or accepted as a valid configured
|
|
// extension type, as it is only used indirectly via troubleshooting tools. It is included here
|
|
// for common reference alongside other builtin extensions.
|
|
BuiltinValidateExtension string = "builtin/proxy/validate"
|
|
)
|
|
|
|
type ConfigEntry interface {
|
|
GetKind() string
|
|
GetName() string
|
|
GetPartition() string
|
|
GetNamespace() string
|
|
GetMeta() map[string]string
|
|
GetCreateIndex() uint64
|
|
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 Upstream Connect connections
|
|
// should be made to a mesh gateway in the local datacenter.
|
|
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"`
|
|
}
|
|
|
|
type ProxyMode string
|
|
|
|
const (
|
|
// ProxyModeDefault represents no specific mode and should
|
|
// be used to indicate that a different layer of the configuration
|
|
// chain should take precedence
|
|
ProxyModeDefault ProxyMode = ""
|
|
|
|
// ProxyModeTransparent represents that inbound and outbound application
|
|
// traffic is being captured and redirected through the proxy.
|
|
ProxyModeTransparent ProxyMode = "transparent"
|
|
|
|
// ProxyModeDirect represents that the proxy's listeners must be dialed directly
|
|
// by the local application and other proxies.
|
|
ProxyModeDirect ProxyMode = "direct"
|
|
)
|
|
|
|
type TransparentProxyConfig struct {
|
|
// The port of the listener where outbound application traffic is being redirected to.
|
|
OutboundListenerPort int `json:",omitempty" alias:"outbound_listener_port"`
|
|
|
|
// DialedDirectly indicates whether transparent proxies can dial this proxy instance directly.
|
|
// The discovery chain is not considered when dialing a service instance directly.
|
|
// This setting is useful when addressing stateful services, such as a database cluster with a leader node.
|
|
DialedDirectly bool `json:",omitempty" alias:"dialed_directly"`
|
|
}
|
|
|
|
type MutualTLSMode string
|
|
|
|
const (
|
|
// MutualTLSModeDefault represents no specific mode and should
|
|
// be used to indicate that a different layer of the configuration
|
|
// chain should take precedence.
|
|
MutualTLSModeDefault MutualTLSMode = ""
|
|
|
|
// MutualTLSModeStrict requires mTLS for incoming traffic.
|
|
MutualTLSModeStrict MutualTLSMode = "strict"
|
|
|
|
// MutualTLSModePermissive allows incoming non-mTLS traffic.
|
|
MutualTLSModePermissive MutualTLSMode = "permissive"
|
|
)
|
|
|
|
// 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"`
|
|
}
|
|
|
|
// EnvoyExtension has configuration for an extension that patches Envoy resources.
|
|
type EnvoyExtension struct {
|
|
Name string
|
|
Required bool
|
|
Arguments map[string]interface{} `bexpr:"-"`
|
|
ConsulVersion string
|
|
EnvoyVersion string
|
|
}
|
|
|
|
type ExposePath struct {
|
|
// ListenerPort defines the port of the proxy's listener for exposed paths.
|
|
ListenerPort int `json:",omitempty" alias:"listener_port"`
|
|
|
|
// 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" alias:"local_path_port"`
|
|
|
|
// 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 LogSinkType string
|
|
|
|
const (
|
|
DefaultLogSinkType LogSinkType = ""
|
|
FileLogSinkType LogSinkType = "file"
|
|
StdErrLogSinkType LogSinkType = "stderr"
|
|
StdOutLogSinkType LogSinkType = "stdout"
|
|
)
|
|
|
|
// AccessLogsConfig contains the associated default settings for all Envoy instances within the datacenter or partition
|
|
type AccessLogsConfig struct {
|
|
// Enabled turns off all access logging
|
|
Enabled bool `json:",omitempty" alias:"enabled"`
|
|
|
|
// DisableListenerLogs turns off just listener logs for connections rejected by Envoy because they don't
|
|
// have a matching listener filter.
|
|
DisableListenerLogs bool `json:",omitempty" alias:"disable_listener_logs"`
|
|
|
|
// Type selects the output for logs: "file", "stderr". "stdout"
|
|
Type LogSinkType `json:",omitempty" alias:"type"`
|
|
|
|
// Path is the output file to write logs
|
|
Path string `json:",omitempty" alias:"path"`
|
|
|
|
// The presence of one format string or the other implies the access log string encoding.
|
|
// Defining Both is invalid.
|
|
JSONFormat string `json:",omitempty" alias:"json_format"`
|
|
TextFormat string `json:",omitempty" alias:"text_format"`
|
|
}
|
|
|
|
type UpstreamConfiguration struct {
|
|
// Overrides is a slice of per-service configuration. The name field is
|
|
// required.
|
|
Overrides []*UpstreamConfig `json:",omitempty"`
|
|
|
|
// Defaults contains default configuration for all upstreams of a given
|
|
// service. The name field must be empty.
|
|
Defaults *UpstreamConfig `json:",omitempty"`
|
|
}
|
|
|
|
type UpstreamConfig struct {
|
|
// Name is only accepted within service-defaults.upstreamConfig.overrides .
|
|
Name string `json:",omitempty"`
|
|
|
|
// Partition is only accepted within service-defaults.upstreamConfig.overrides .
|
|
Partition string `json:",omitempty"`
|
|
|
|
// Namespace is only accepted within service-defaults.upstreamConfig.overrides .
|
|
Namespace string `json:",omitempty"`
|
|
|
|
// Peer is only accepted within service-defaults.upstreamConfig.overrides .
|
|
Peer string `json:",omitempty"`
|
|
|
|
// EnvoyListenerJSON is a complete override ("escape hatch") for the upstream's
|
|
// listener.
|
|
//
|
|
// Note: This escape hatch is NOT compatible with the discovery chain and
|
|
// will be ignored if a discovery chain is active.
|
|
EnvoyListenerJSON string `json:",omitempty" alias:"envoy_listener_json"`
|
|
|
|
// EnvoyClusterJSON is a complete override ("escape hatch") for the upstream's
|
|
// cluster. The Connect client TLS certificate and context will be injected
|
|
// overriding any TLS settings present.
|
|
//
|
|
// Note: This escape hatch is NOT compatible with the discovery chain and
|
|
// will be ignored if a discovery chain is active.
|
|
EnvoyClusterJSON string `json:",omitempty" alias:"envoy_cluster_json"`
|
|
|
|
// Protocol describes the upstream's service protocol. Valid values are "tcp",
|
|
// "http" and "grpc". Anything else is treated as tcp. The enables protocol
|
|
// aware features like per-request metrics and connection pooling, tracing,
|
|
// routing etc.
|
|
Protocol string `json:",omitempty"`
|
|
|
|
// ConnectTimeoutMs is the number of milliseconds to timeout making a new
|
|
// connection to this upstream. Defaults to 5000 (5 seconds) if not set.
|
|
ConnectTimeoutMs int `json:",omitempty" alias:"connect_timeout_ms"`
|
|
|
|
// Limits are the set of limits that are applied to the proxy for a specific upstream of a
|
|
// service instance.
|
|
Limits *UpstreamLimits `json:",omitempty"`
|
|
|
|
// PassiveHealthCheck configuration determines how upstream proxy instances will
|
|
// be monitored for removal from the load balancing pool.
|
|
PassiveHealthCheck *PassiveHealthCheck `json:",omitempty" alias:"passive_health_check"`
|
|
|
|
// MeshGatewayConfig controls how Mesh Gateways are configured and used
|
|
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway" `
|
|
|
|
// BalanceOutboundConnections indicates that the proxy should attempt to evenly distribute
|
|
// outbound connections across worker threads. Only used by envoy proxies.
|
|
BalanceOutboundConnections string `json:",omitempty" alias:"balance_outbound_connections"`
|
|
}
|
|
|
|
// DestinationConfig represents a virtual service, i.e. one that is external to Consul
|
|
type DestinationConfig struct {
|
|
// Addresses of the endpoint; hostname or IP
|
|
Addresses []string `json:",omitempty"`
|
|
|
|
// Port allowed within this endpoint
|
|
Port int `json:",omitempty"`
|
|
}
|
|
|
|
type PassiveHealthCheck struct {
|
|
// Interval between health check analysis sweeps. Each sweep may remove
|
|
// hosts or return hosts to the pool.
|
|
Interval time.Duration `json:",omitempty"`
|
|
|
|
// MaxFailures is the count of consecutive failures that results in a host
|
|
// being removed from the pool.
|
|
MaxFailures uint32 `alias:"max_failures"`
|
|
|
|
// EnforcingConsecutive5xx is the % chance that a host will be actually ejected
|
|
// when an outlier status is detected through consecutive 5xx.
|
|
// This setting can be used to disable ejection or to ramp it up slowly.
|
|
EnforcingConsecutive5xx *uint32 `json:",omitempty" alias:"enforcing_consecutive_5xx"`
|
|
|
|
// The maximum % of an upstream cluster that can be ejected due to outlier detection.
|
|
// Defaults to 10% but will eject at least one host regardless of the value.
|
|
MaxEjectionPercent *uint32 `json:",omitempty" alias:"max_ejection_percent"`
|
|
|
|
// The base time that a host is ejected for. The real time is equal to the base time
|
|
// multiplied by the number of times the host has been ejected and is capped by
|
|
// max_ejection_time (Default 300s). Defaults to 30000ms or 30s.
|
|
BaseEjectionTime *time.Duration `json:",omitempty" alias:"base_ejection_time"`
|
|
}
|
|
|
|
// UpstreamLimits describes the limits that are associated with a specific
|
|
// upstream of a service instance.
|
|
type UpstreamLimits struct {
|
|
// MaxConnections is the maximum number of connections the local proxy can
|
|
// make to the upstream service.
|
|
MaxConnections *int `alias:"max_connections"`
|
|
|
|
// MaxPendingRequests is the maximum number of requests that will be queued
|
|
// waiting for an available connection. This is mostly applicable to HTTP/1.1
|
|
// clusters since all HTTP/2 requests are streamed over a single
|
|
// connection.
|
|
MaxPendingRequests *int `alias:"max_pending_requests"`
|
|
|
|
// MaxConcurrentRequests is the maximum number of in-flight requests that will be allowed
|
|
// to the upstream cluster at a point in time. This is mostly applicable to HTTP/2
|
|
// clusters since all HTTP/1.1 requests are limited by MaxConnections.
|
|
MaxConcurrentRequests *int `alias:"max_concurrent_requests"`
|
|
}
|
|
|
|
// RateLimits is rate limiting configuration that is applied to
|
|
// inbound traffic for a service.
|
|
// Rate limiting is a Consul enterprise feature.
|
|
type RateLimits struct {
|
|
InstanceLevel InstanceLevelRateLimits `alias:"instance_level"`
|
|
}
|
|
|
|
// InstanceLevelRateLimits represents rate limit configuration
|
|
// that are applied per service instance.
|
|
type InstanceLevelRateLimits struct {
|
|
// RequestsPerSecond is the average number of requests per second that can be
|
|
// made without being throttled. This field is required if RequestsMaxBurst
|
|
// is set. The allowed number of requests may exceed RequestsPerSecond up to
|
|
// the value specified in RequestsMaxBurst.
|
|
//
|
|
// Internally, this is the refill rate of the token bucket used for rate limiting.
|
|
RequestsPerSecond int `alias:"requests_per_second"`
|
|
|
|
// RequestsMaxBurst is the maximum number of requests that can be sent
|
|
// in a burst. Should be equal to or greater than RequestsPerSecond.
|
|
// If unset, defaults to RequestsPerSecond.
|
|
//
|
|
// Internally, this is the maximum size of the token bucket used for rate limiting.
|
|
RequestsMaxBurst int `alias:"requests_max_burst"`
|
|
|
|
// Routes is a list of rate limits applied to specific routes.
|
|
// For a given request, the first matching route will be applied, if any
|
|
// Overrides any top-level configuration.
|
|
Routes []InstanceLevelRouteRateLimits
|
|
}
|
|
|
|
// InstanceLevelRouteRateLimits represents rate limit configuration
|
|
// applied to a route matching one of PathExact/PathPrefix/PathRegex.
|
|
type InstanceLevelRouteRateLimits struct {
|
|
PathExact string `alias:"path_exact"`
|
|
PathPrefix string `alias:"path_prefix"`
|
|
PathRegex string `alias:"path_regex"`
|
|
|
|
RequestsPerSecond int `alias:"requests_per_second"`
|
|
RequestsMaxBurst int `alias:"requests_max_burst"`
|
|
}
|
|
|
|
type ServiceConfigEntry struct {
|
|
Kind string
|
|
Name string
|
|
Partition string `json:",omitempty"`
|
|
Namespace string `json:",omitempty"`
|
|
Protocol string `json:",omitempty"`
|
|
Mode ProxyMode `json:",omitempty"`
|
|
TransparentProxy *TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
|
|
MutualTLSMode MutualTLSMode `json:",omitempty" alias:"mutual_tls_mode"`
|
|
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
|
|
Expose ExposeConfig `json:",omitempty"`
|
|
ExternalSNI string `json:",omitempty" alias:"external_sni"`
|
|
UpstreamConfig *UpstreamConfiguration `json:",omitempty" alias:"upstream_config"`
|
|
Destination *DestinationConfig `json:",omitempty"`
|
|
MaxInboundConnections int `json:",omitempty" alias:"max_inbound_connections"`
|
|
LocalConnectTimeoutMs int `json:",omitempty" alias:"local_connect_timeout_ms"`
|
|
LocalRequestTimeoutMs int `json:",omitempty" alias:"local_request_timeout_ms"`
|
|
BalanceInboundConnections string `json:",omitempty" alias:"balance_inbound_connections"`
|
|
RateLimits *RateLimits `json:",omitempty" alias:"rate_limits"`
|
|
EnvoyExtensions []EnvoyExtension `json:",omitempty" alias:"envoy_extensions"`
|
|
Meta map[string]string `json:",omitempty"`
|
|
CreateIndex uint64
|
|
ModifyIndex uint64
|
|
}
|
|
|
|
func (s *ServiceConfigEntry) GetKind() string { return s.Kind }
|
|
func (s *ServiceConfigEntry) GetName() string { return s.Name }
|
|
func (s *ServiceConfigEntry) GetPartition() string { return s.Partition }
|
|
func (s *ServiceConfigEntry) GetNamespace() string { return s.Namespace }
|
|
func (s *ServiceConfigEntry) GetMeta() map[string]string { return s.Meta }
|
|
func (s *ServiceConfigEntry) GetCreateIndex() uint64 { return s.CreateIndex }
|
|
func (s *ServiceConfigEntry) GetModifyIndex() uint64 { return s.ModifyIndex }
|
|
|
|
type ProxyConfigEntry struct {
|
|
Kind string
|
|
Name string
|
|
Partition string `json:",omitempty"`
|
|
Namespace string `json:",omitempty"`
|
|
Mode ProxyMode `json:",omitempty"`
|
|
TransparentProxy *TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
|
|
MutualTLSMode MutualTLSMode `json:",omitempty" alias:"mutual_tls_mode"`
|
|
Config map[string]interface{} `json:",omitempty"`
|
|
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
|
|
Expose ExposeConfig `json:",omitempty"`
|
|
AccessLogs *AccessLogsConfig `json:",omitempty" alias:"access_logs"`
|
|
EnvoyExtensions []EnvoyExtension `json:",omitempty" alias:"envoy_extensions"`
|
|
FailoverPolicy *ServiceResolverFailoverPolicy `json:",omitempty" alias:"failover_policy"`
|
|
PrioritizeByLocality *ServiceResolverPrioritizeByLocality `json:",omitempty" alias:"prioritize_by_locality"`
|
|
|
|
Meta map[string]string `json:",omitempty"`
|
|
CreateIndex uint64
|
|
ModifyIndex uint64
|
|
}
|
|
|
|
func (p *ProxyConfigEntry) GetKind() string { return p.Kind }
|
|
func (p *ProxyConfigEntry) GetName() string { return ProxyConfigGlobal }
|
|
func (p *ProxyConfigEntry) GetPartition() string { return p.Partition }
|
|
func (p *ProxyConfigEntry) GetNamespace() string { return p.Namespace }
|
|
func (p *ProxyConfigEntry) GetMeta() map[string]string { return p.Meta }
|
|
func (p *ProxyConfigEntry) GetCreateIndex() uint64 { return p.CreateIndex }
|
|
func (p *ProxyConfigEntry) GetModifyIndex() uint64 { return p.ModifyIndex }
|
|
|
|
func makeConfigEntry(kind, name string) (ConfigEntry, error) {
|
|
switch kind {
|
|
case ServiceDefaults:
|
|
return &ServiceConfigEntry{Kind: kind, Name: name}, nil
|
|
case ProxyDefaults:
|
|
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
|
|
case IngressGateway:
|
|
return &IngressGatewayConfigEntry{Kind: kind, Name: name}, nil
|
|
case TerminatingGateway:
|
|
return &TerminatingGatewayConfigEntry{Kind: kind, Name: name}, nil
|
|
case ServiceIntentions:
|
|
return &ServiceIntentionsConfigEntry{Kind: kind, Name: name}, nil
|
|
case MeshConfig:
|
|
return &MeshConfigEntry{}, nil
|
|
case ExportedServices:
|
|
return &ExportedServicesConfigEntry{Name: name}, nil
|
|
case SamenessGroup:
|
|
return &SamenessGroupConfigEntry{Kind: kind, Name: name}, nil
|
|
case APIGateway:
|
|
return &APIGatewayConfigEntry{Kind: kind, Name: name}, nil
|
|
case TCPRoute:
|
|
return &TCPRouteConfigEntry{Kind: kind, Name: name}, nil
|
|
case FileSystemCertificate:
|
|
return &FileSystemCertificateConfigEntry{Kind: kind, Name: name}, nil
|
|
case InlineCertificate:
|
|
return &InlineCertificateConfigEntry{Kind: kind, Name: name}, nil
|
|
case HTTPRoute:
|
|
return &HTTPRouteConfigEntry{Kind: kind, Name: name}, nil
|
|
case RateLimitIPConfig:
|
|
return &RateLimitIPConfigEntry{Kind: kind, Name: name}, nil
|
|
case JWTProvider:
|
|
return &JWTProviderConfigEntry{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
|
|
|
|
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.ComposeDecodeHookFunc(
|
|
mapstructure.StringToTimeDurationHookFunc(),
|
|
mapstructure.StringToTimeHookFunc(time.RFC3339),
|
|
),
|
|
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)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// 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 := conf.c.doRequest(r)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
defer closeResponseBody(resp)
|
|
if err := requireOK(resp); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
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 := conf.c.doRequest(r)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
defer closeResponseBody(resp)
|
|
if err := requireOK(resp); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
qm := &QueryMeta{}
|
|
parseQueryMeta(resp, qm)
|
|
qm.RequestTime = rtt
|
|
|
|
var raw []map[string]interface{}
|
|
if err := decodeBody(resp, &raw); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
entries, err := decodeConfigEntrySlice(raw)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
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 := conf.c.doRequest(r)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
defer closeResponseBody(resp)
|
|
if err := requireOK(resp); err != nil {
|
|
return false, nil, err
|
|
}
|
|
|
|
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) {
|
|
_, wm, err := conf.delete(kind, name, nil, w)
|
|
return wm, err
|
|
}
|
|
|
|
// DeleteCAS performs a Check-And-Set deletion of the given config entry, and
|
|
// returns true if it was successful. If the provided index no longer matches
|
|
// the entry's ModifyIndex (i.e. it was modified by another process) then the
|
|
// operation will fail and return false.
|
|
func (conf *ConfigEntries) DeleteCAS(kind, name string, index uint64, w *WriteOptions) (bool, *WriteMeta, error) {
|
|
return conf.delete(kind, name, map[string]string{"cas": strconv.FormatUint(index, 10)}, w)
|
|
}
|
|
|
|
func (conf *ConfigEntries) delete(kind, name string, params map[string]string, w *WriteOptions) (bool, *WriteMeta, error) {
|
|
if kind == "" || name == "" {
|
|
return false, 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)
|
|
for param, value := range params {
|
|
r.params.Set(param, value)
|
|
}
|
|
|
|
rtt, resp, err := conf.c.doRequest(r)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
defer closeResponseBody(resp)
|
|
|
|
if err := requireOK(resp); err != nil {
|
|
return false, nil, err
|
|
}
|
|
|
|
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
|
|
}
|