func IsRetryableError(err error) bool {
	if err == nil {
		return false
	}
	if _, ok := err.(net.Error); ok {
		return true
	}
	return strings.Contains(err.Error(), serverError)
}
IsRetryableError returns true for 500 errors from the Consul servers, and
network connection errors. These are usually retryable at a later time.
This applies to reads but NOT to writes. This may return true for errors
on writes that may have still gone through, so do not use this to retry
any write operations.
package api
import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"
	"github.com/hashicorp/go-cleanhttp"
	"github.com/hashicorp/go-hclog"
	"github.com/hashicorp/go-rootcerts"
)
const (
	HTTPAddrEnvName = "CONSUL_HTTP_ADDR"
	HTTPTokenEnvName = "CONSUL_HTTP_TOKEN"
	HTTPTokenFileEnvName = "CONSUL_HTTP_TOKEN_FILE"
	HTTPAuthEnvName = "CONSUL_HTTP_AUTH"
	HTTPSSLEnvName = "CONSUL_HTTP_SSL"
	HTTPCAFile = "CONSUL_CACERT"
	HTTPCAPath = "CONSUL_CAPATH"
	HTTPClientCert = "CONSUL_CLIENT_CERT"
	HTTPClientKey = "CONSUL_CLIENT_KEY"
	HTTPTLSServerName = "CONSUL_TLS_SERVER_NAME"
	HTTPSSLVerifyEnvName = "CONSUL_HTTP_SSL_VERIFY"
	GRPCAddrEnvName = "CONSUL_GRPC_ADDR"
	HTTPNamespaceEnvName = "CONSUL_NAMESPACE"
)
type QueryOptions struct {
	Namespace string
	Datacenter string
	AllowStale bool
	RequireConsistent bool
	UseCache bool
	MaxAge time.Duration
	StaleIfError time.Duration
	WaitIndex uint64
	WaitHash string
	WaitTime time.Duration
	Token string
	Near string
	NodeMeta map[string]string
	RelayFactor uint8
	LocalOnly bool
	Connect bool
	ctx context.Context
	Filter string
}
func (o *QueryOptions) Context() context.Context {
	if o != nil && o.ctx != nil {
		return o.ctx
	}
	return context.Background()
}
func (o *QueryOptions) WithContext(ctx context.Context) *QueryOptions {
	o2 := new(QueryOptions)
	if o != nil {
		*o2 = *o
	}
	o2.ctx = ctx
	return o2
}
type WriteOptions struct {
	Namespace string
	Datacenter string
	Token string
	RelayFactor uint8
	ctx context.Context
}
func (o *WriteOptions) Context() context.Context {
	if o != nil && o.ctx != nil {
		return o.ctx
	}
	return context.Background()
}
func (o *WriteOptions) WithContext(ctx context.Context) *WriteOptions {
	o2 := new(WriteOptions)
	if o != nil {
		*o2 = *o
	}
	o2.ctx = ctx
	return o2
}
type QueryMeta struct {
	LastIndex uint64
	LastContentHash string
	LastContact time.Duration
	KnownLeader bool
	RequestTime time.Duration
	AddressTranslationEnabled bool
	CacheHit bool
	CacheAge time.Duration
}
type WriteMeta struct {
	RequestTime time.Duration
}
type HttpBasicAuth struct {
	Username string
	Password string
}
type Config struct {
	Address string
	Scheme string
	Datacenter string
	Transport *http.Transport
	HttpClient *http.Client
	HttpAuth *HttpBasicAuth
	WaitTime time.Duration
	Token string
	TokenFile string
	Namespace string
	TLSConfig TLSConfig
}
type TLSConfig struct {
	Address string
	CAFile string
	CAPath string
	CAPem []byte
	CertFile string
	CertPEM []byte
	KeyFile string
	KeyPEM []byte
	InsecureSkipVerify bool
}
func DefaultConfig() *Config {
	return defaultConfig(nil, cleanhttp.DefaultPooledTransport)
}
func DefaultConfigWithLogger(logger hclog.Logger) *Config {
	return defaultConfig(logger, cleanhttp.DefaultPooledTransport)
}
func DefaultNonPooledConfig() *Config {
	return defaultConfig(nil, cleanhttp.DefaultTransport)
}
func defaultConfig(logger hclog.Logger, transportFn func() *http.Transport) *Config {
	if logger == nil {
		logger = hclog.New(&hclog.LoggerOptions{
			Name: "consul-api",
		})
	}
	config := &Config{
		Address:   "127.0.0.1:8500",
		Scheme:    "http",
		Transport: transportFn(),
	}
	if addr := os.Getenv(HTTPAddrEnvName); addr != "" {
		config.Address = addr
	}
	if tokenFile := os.Getenv(HTTPTokenFileEnvName); tokenFile != "" {
		config.TokenFile = tokenFile
	}
	if token := os.Getenv(HTTPTokenEnvName); token != "" {
		config.Token = token
	}
	if auth := os.Getenv(HTTPAuthEnvName); auth != "" {
		var username, password string
		if strings.Contains(auth, ":") {
			split := strings.SplitN(auth, ":", 2)
			username = split[0]
			password = split[1]
		} else {
			username = auth
		}
		config.HttpAuth = &HttpBasicAuth{
			Username: username,
			Password: password,
		}
	}
	if ssl := os.Getenv(HTTPSSLEnvName); ssl != "" {
		enabled, err := strconv.ParseBool(ssl)
		if err != nil {
			logger.Warn(fmt.Sprintf("could not parse %s", HTTPSSLEnvName), "error", err)
		}
		if enabled {
			config.Scheme = "https"
		}
	}
	if v := os.Getenv(HTTPTLSServerName); v != "" {
		config.TLSConfig.Address = v
	}
	if v := os.Getenv(HTTPCAFile); v != "" {
		config.TLSConfig.CAFile = v
	}
	if v := os.Getenv(HTTPCAPath); v != "" {
		config.TLSConfig.CAPath = v
	}
	if v := os.Getenv(HTTPClientCert); v != "" {
		config.TLSConfig.CertFile = v
	}
	if v := os.Getenv(HTTPClientKey); v != "" {
		config.TLSConfig.KeyFile = v
	}
	if v := os.Getenv(HTTPSSLVerifyEnvName); v != "" {
		doVerify, err := strconv.ParseBool(v)
		if err != nil {
			logger.Warn(fmt.Sprintf("could not parse %s", HTTPSSLVerifyEnvName), "error", err)
		}
		if !doVerify {
			config.TLSConfig.InsecureSkipVerify = true
		}
	}
	if v := os.Getenv(HTTPNamespaceEnvName); v != "" {
		config.Namespace = v
	}
	return config
}
func SetupTLSConfig(tlsConfig *TLSConfig) (*tls.Config, error) {
	tlsClientConfig := &tls.Config{
		InsecureSkipVerify: tlsConfig.InsecureSkipVerify,
	}
	if tlsConfig.Address != "" {
		server := tlsConfig.Address
		hasPort := strings.LastIndex(server, ":") > strings.LastIndex(server, "]")
		if hasPort {
			var err error
			server, _, err = net.SplitHostPort(server)
			if err != nil {
				return nil, err
			}
		}
		tlsClientConfig.ServerName = server
	}
	if 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 != "" || len(tlsConfig.CAPem) != 0 {
		rootConfig := &rootcerts.Config{
			CAFile:        tlsConfig.CAFile,
			CAPath:        tlsConfig.CAPath,
			CACertificate: tlsConfig.CAPem,
		}
		if err := rootcerts.ConfigureTLS(tlsClientConfig, rootConfig); err != nil {
			return nil, err
		}
	}
	return tlsClientConfig, nil
}
func (c *Config) GenerateEnv() []string {
	env := make([]string, 0, 10)
	env = append(env,
		fmt.Sprintf("%s=%s", HTTPAddrEnvName, c.Address),
		fmt.Sprintf("%s=%s", HTTPTokenEnvName, c.Token),
		fmt.Sprintf("%s=%s", HTTPTokenFileEnvName, c.TokenFile),
		fmt.Sprintf("%s=%t", HTTPSSLEnvName, c.Scheme == "https"),
		fmt.Sprintf("%s=%s", HTTPCAFile, c.TLSConfig.CAFile),
		fmt.Sprintf("%s=%s", HTTPCAPath, c.TLSConfig.CAPath),
		fmt.Sprintf("%s=%s", HTTPClientCert, c.TLSConfig.CertFile),
		fmt.Sprintf("%s=%s", HTTPClientKey, c.TLSConfig.KeyFile),
		fmt.Sprintf("%s=%s", HTTPTLSServerName, c.TLSConfig.Address),
		fmt.Sprintf("%s=%t", HTTPSSLVerifyEnvName, !c.TLSConfig.InsecureSkipVerify))
	if c.HttpAuth != nil {
		env = append(env, fmt.Sprintf("%s=%s:%s", HTTPAuthEnvName, c.HttpAuth.Username, c.HttpAuth.Password))
	} else {
		env = append(env, fmt.Sprintf("%s=", HTTPAuthEnvName))
	}
	return env
}
type Client struct {
	config Config
}
func NewClient(config *Config) (*Client, error) {
	defConfig := DefaultConfig()
	if len(config.Address) == 0 {
		config.Address = defConfig.Address
	}
	if len(config.Scheme) == 0 {
		config.Scheme = defConfig.Scheme
	}
	if config.Transport == nil {
		config.Transport = defConfig.Transport
	}
	if config.TLSConfig.Address == "" {
		config.TLSConfig.Address = defConfig.TLSConfig.Address
	}
	if config.TLSConfig.CAFile == "" {
		config.TLSConfig.CAFile = defConfig.TLSConfig.CAFile
	}
	if config.TLSConfig.CAPath == "" {
		config.TLSConfig.CAPath = defConfig.TLSConfig.CAPath
	}
	if config.TLSConfig.CertFile == "" {
		config.TLSConfig.CertFile = defConfig.TLSConfig.CertFile
	}
	if config.TLSConfig.KeyFile == "" {
		config.TLSConfig.KeyFile = defConfig.TLSConfig.KeyFile
	}
	if !config.TLSConfig.InsecureSkipVerify {
		config.TLSConfig.InsecureSkipVerify = defConfig.TLSConfig.InsecureSkipVerify
	}
	if config.HttpClient == nil {
		var err error
		config.HttpClient, err = NewHttpClient(config.Transport, config.TLSConfig)
		if err != nil {
			return nil, err
		}
	}
	parts := strings.SplitN(config.Address, "://", 2)
	if len(parts) == 2 {
		switch parts[0] {
		case "http":
			config.Scheme = "http"
		case "https":
			config.Scheme = "https"
		case "unix":
			trans := cleanhttp.DefaultTransport()
			trans.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
				return net.Dial("unix", parts[1])
			}
			config.HttpClient = &http.Client{
				Transport: trans,
			}
		default:
			return nil, fmt.Errorf("Unknown protocol scheme: %s", parts[0])
		}
		config.Address = parts[1]
	}
	if config.TokenFile != "" {
		data, err := ioutil.ReadFile(config.TokenFile)
		if err != nil {
			return nil, fmt.Errorf("Error loading token file: %s", err)
		}
		if token := strings.TrimSpace(string(data)); token != "" {
			config.Token = token
		}
	}
	if config.Token == "" {
		config.Token = defConfig.Token
	}
	return &Client{config: *config}, nil
}
func NewHttpClient(transport *http.Transport, tlsConf TLSConfig) (*http.Client, error) {
	client := &http.Client{
		Transport: transport,
	}
	if transport.TLSClientConfig == nil {
		tlsClientConfig, err := SetupTLSConfig(&tlsConf)
		if err != nil {
			return nil, err
		}
		transport.TLSClientConfig = tlsClientConfig
	}
	return client, nil
}
type request struct {
	config *Config
	method string
	url    *url.URL
	params url.Values
	body   io.Reader
	header http.Header
	obj    interface{}
	ctx    context.Context
}
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)
	}
	if q.AllowStale {
		r.params.Set("stale", "")
	}
	if q.RequireConsistent {
		r.params.Set("consistent", "")
	}
	if q.WaitIndex != 0 {
		r.params.Set("index", strconv.FormatUint(q.WaitIndex, 10))
	}
	if q.WaitTime != 0 {
		r.params.Set("wait", durToMsec(q.WaitTime))
	}
	if q.WaitHash != "" {
		r.params.Set("hash", q.WaitHash)
	}
	if q.Token != "" {
		r.header.Set("X-Consul-Token", q.Token)
	}
	if q.Near != "" {
		r.params.Set("near", q.Near)
	}
	if q.Filter != "" {
		r.params.Set("filter", q.Filter)
	}
	if len(q.NodeMeta) > 0 {
		for key, value := range q.NodeMeta {
			r.params.Add("node-meta", key+":"+value)
		}
	}
	if q.RelayFactor != 0 {
		r.params.Set("relay-factor", strconv.Itoa(int(q.RelayFactor)))
	}
	if q.LocalOnly {
		r.params.Set("local-only", fmt.Sprintf("%t", q.LocalOnly))
	}
	if q.Connect {
		r.params.Set("connect", "true")
	}
	if q.UseCache && !q.RequireConsistent {
		r.params.Set("cached", "")
		cc := []string{}
		if q.MaxAge > 0 {
			cc = append(cc, fmt.Sprintf("max-age=%.0f", q.MaxAge.Seconds()))
		}
		if q.StaleIfError > 0 {
			cc = append(cc, fmt.Sprintf("stale-if-error=%.0f", q.StaleIfError.Seconds()))
		}
		if len(cc) > 0 {
			r.header.Set("Cache-Control", strings.Join(cc, ", "))
		}
	}
	r.ctx = q.ctx
}
func durToMsec(dur time.Duration) string {
	ms := dur / time.Millisecond
	if dur > 0 && ms == 0 {
		ms = 1
	}
	return fmt.Sprintf("%dms", ms)
}
const serverError = "Unexpected response code: 500"