mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
691 lines
26 KiB
691 lines
26 KiB
// 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 |
|
}
|
|
|