package config

import (
	"bytes"
	"crypto/tls"
	"encoding/base64"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/hashicorp/consul/agent/structs"
	"github.com/hashicorp/consul/lib"
	"github.com/hashicorp/consul/testutil"
	"github.com/hashicorp/consul/types"
	"github.com/pascaldekloe/goe/verify"
	"github.com/stretchr/testify/require"
)

type configTest struct {
	desc           string
	args           []string
	pre, post      func()
	json, jsontail []string
	hcl, hcltail   []string
	skipformat     bool
	privatev4      func() ([]*net.IPAddr, error)
	publicv6       func() ([]*net.IPAddr, error)
	patch          func(rt *RuntimeConfig)
	err            string
	warns          []string
	hostname       func() (string, error)
}

// TestConfigFlagsAndEdgecases tests the command line flags and
// edgecases for the config parsing. It provides a test structure which
// checks for warnings on deprecated fields and flags.  These tests
// should check one option at a time if possible and should use generic
// values, e.g. 'a' or 1 instead of 'servicex' or 3306.

func TestConfigFlagsAndEdgecases(t *testing.T) {
	dataDir := testutil.TempDir(t, "consul")
	defer os.RemoveAll(dataDir)

	tests := []configTest{
		// ------------------------------------------------------------
		// cmd line flags
		//

		{
			desc: "-advertise",
			args: []string{
				`-advertise=1.2.3.4`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.AdvertiseAddrLAN = ipAddr("1.2.3.4")
				rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
				rt.RPCAdvertiseAddr = tcpAddr("1.2.3.4:8300")
				rt.SerfAdvertiseAddrLAN = tcpAddr("1.2.3.4:8301")
				rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:8302")
				rt.TaggedAddresses = map[string]string{
					"lan": "1.2.3.4",
					"wan": "1.2.3.4",
				}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-advertise-wan",
			args: []string{
				`-advertise-wan=1.2.3.4`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
				rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:8302")
				rt.TaggedAddresses = map[string]string{
					"lan": "10.0.0.1",
					"wan": "1.2.3.4",
				}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-advertise and -advertise-wan",
			args: []string{
				`-advertise=1.2.3.4`,
				`-advertise-wan=5.6.7.8`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.AdvertiseAddrLAN = ipAddr("1.2.3.4")
				rt.AdvertiseAddrWAN = ipAddr("5.6.7.8")
				rt.RPCAdvertiseAddr = tcpAddr("1.2.3.4:8300")
				rt.SerfAdvertiseAddrLAN = tcpAddr("1.2.3.4:8301")
				rt.SerfAdvertiseAddrWAN = tcpAddr("5.6.7.8:8302")
				rt.TaggedAddresses = map[string]string{
					"lan": "1.2.3.4",
					"wan": "5.6.7.8",
				}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-bind",
			args: []string{
				`-bind=1.2.3.4`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.BindAddr = ipAddr("1.2.3.4")
				rt.AdvertiseAddrLAN = ipAddr("1.2.3.4")
				rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
				rt.RPCAdvertiseAddr = tcpAddr("1.2.3.4:8300")
				rt.RPCBindAddr = tcpAddr("1.2.3.4:8300")
				rt.SerfAdvertiseAddrLAN = tcpAddr("1.2.3.4:8301")
				rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:8302")
				rt.SerfBindAddrLAN = tcpAddr("1.2.3.4:8301")
				rt.SerfBindAddrWAN = tcpAddr("1.2.3.4:8302")
				rt.TaggedAddresses = map[string]string{
					"lan": "1.2.3.4",
					"wan": "1.2.3.4",
				}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-bootstrap",
			args: []string{
				`-bootstrap`,
				`-server`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.Bootstrap = true
				rt.ServerMode = true
				rt.LeaveOnTerm = false
				rt.SkipLeaveOnInt = true
				rt.DataDir = dataDir
			},
			warns: []string{"bootstrap = true: do not enable unless necessary"},
		},
		{
			desc: "-bootstrap-expect",
			args: []string{
				`-bootstrap-expect=3`,
				`-server`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.BootstrapExpect = 3
				rt.ServerMode = true
				rt.LeaveOnTerm = false
				rt.SkipLeaveOnInt = true
				rt.DataDir = dataDir
			},
			warns: []string{"bootstrap_expect > 0: expecting 3 servers"},
		},
		{
			desc: "-client",
			args: []string{
				`-client=1.2.3.4`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.ClientAddrs = []*net.IPAddr{ipAddr("1.2.3.4")}
				rt.DNSAddrs = []net.Addr{tcpAddr("1.2.3.4:8600"), udpAddr("1.2.3.4:8600")}
				rt.HTTPAddrs = []net.Addr{tcpAddr("1.2.3.4:8500")}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-config-dir",
			args: []string{
				`-data-dir=` + dataDir,
				`-config-dir`, filepath.Join(dataDir, "conf.d"),
			},
			patch: func(rt *RuntimeConfig) {
				rt.Datacenter = "a"
				rt.DataDir = dataDir
			},
			pre: func() {
				writeFile(filepath.Join(dataDir, "conf.d/conf.json"), []byte(`{"datacenter":"a"}`))
			},
		},
		{
			desc: "-config-file json",
			args: []string{
				`-data-dir=` + dataDir,
				`-config-file`, filepath.Join(dataDir, "conf.json"),
			},
			patch: func(rt *RuntimeConfig) {
				rt.Datacenter = "a"
				rt.DataDir = dataDir
			},
			pre: func() {
				writeFile(filepath.Join(dataDir, "conf.json"), []byte(`{"datacenter":"a"}`))
			},
		},
		{
			desc: "-config-file hcl and json",
			args: []string{
				`-data-dir=` + dataDir,
				`-config-file`, filepath.Join(dataDir, "conf.hcl"),
				`-config-file`, filepath.Join(dataDir, "conf.json"),
			},
			patch: func(rt *RuntimeConfig) {
				rt.Datacenter = "b"
				rt.DataDir = dataDir
			},
			pre: func() {
				writeFile(filepath.Join(dataDir, "conf.hcl"), []byte(`datacenter = "a"`))
				writeFile(filepath.Join(dataDir, "conf.json"), []byte(`{"datacenter":"b"}`))
			},
		},
		{
			desc: "-data-dir empty",
			args: []string{
				`-data-dir=`,
			},
			err: "data_dir cannot be empty",
		},
		{
			desc: "-data-dir non-directory",
			args: []string{
				`-data-dir=runtime_test.go`,
			},
			err: `data_dir "runtime_test.go" is not a directory`,
		},
		{
			desc: "-datacenter",
			args: []string{
				`-datacenter=a`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.Datacenter = "a"
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-datacenter empty",
			args: []string{
				`-datacenter=`,
				`-data-dir=` + dataDir,
			},
			err: "datacenter cannot be empty",
		},
		{
			desc: "-dev",
			args: []string{
				`-dev`,
			},
			patch: func(rt *RuntimeConfig) {
				rt.AdvertiseAddrLAN = ipAddr("127.0.0.1")
				rt.AdvertiseAddrWAN = ipAddr("127.0.0.1")
				rt.BindAddr = ipAddr("127.0.0.1")
				rt.ConnectEnabled = true
				rt.DevMode = true
				rt.DisableAnonymousSignature = true
				rt.DisableKeyringFile = true
				rt.EnableDebug = true
				rt.EnableUI = true
				rt.LeaveOnTerm = false
				rt.LogLevel = "DEBUG"
				rt.RPCAdvertiseAddr = tcpAddr("127.0.0.1:8300")
				rt.RPCBindAddr = tcpAddr("127.0.0.1:8300")
				rt.SerfAdvertiseAddrLAN = tcpAddr("127.0.0.1:8301")
				rt.SerfAdvertiseAddrWAN = tcpAddr("127.0.0.1:8302")
				rt.SerfBindAddrLAN = tcpAddr("127.0.0.1:8301")
				rt.SerfBindAddrWAN = tcpAddr("127.0.0.1:8302")
				rt.ServerMode = true
				rt.SkipLeaveOnInt = true
				rt.TaggedAddresses = map[string]string{"lan": "127.0.0.1", "wan": "127.0.0.1"}
				rt.ConsulCoordinateUpdatePeriod = 100 * time.Millisecond
				rt.ConsulRaftElectionTimeout = 52 * time.Millisecond
				rt.ConsulRaftHeartbeatTimeout = 35 * time.Millisecond
				rt.ConsulRaftLeaderLeaseTimeout = 20 * time.Millisecond
				rt.GossipLANGossipInterval = 100 * time.Millisecond
				rt.GossipLANProbeInterval = 100 * time.Millisecond
				rt.GossipLANProbeTimeout = 100 * time.Millisecond
				rt.GossipLANSuspicionMult = 3
				rt.GossipWANGossipInterval = 100 * time.Millisecond
				rt.GossipWANProbeInterval = 100 * time.Millisecond
				rt.GossipWANProbeTimeout = 100 * time.Millisecond
				rt.GossipWANSuspicionMult = 3
				rt.ConsulServerHealthInterval = 10 * time.Millisecond
				rt.GRPCPort = 8502
				rt.GRPCAddrs = []net.Addr{tcpAddr("127.0.0.1:8502")}
			},
		},
		{
			desc: "-disable-host-node-id",
			args: []string{
				`-disable-host-node-id`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.DisableHostNodeID = true
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-disable-keyring-file",
			args: []string{
				`-disable-keyring-file`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.DisableKeyringFile = true
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-dns-port",
			args: []string{
				`-dns-port=123`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.DNSPort = 123
				rt.DNSAddrs = []net.Addr{tcpAddr("127.0.0.1:123"), udpAddr("127.0.0.1:123")}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-domain",
			args: []string{
				`-domain=a`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.DNSDomain = "a"
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-enable-script-checks",
			args: []string{
				`-enable-script-checks`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.EnableLocalScriptChecks = true
				rt.EnableRemoteScriptChecks = true
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-encrypt",
			args: []string{
				`-encrypt=i0P+gFTkLPg0h53eNYjydg==`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.EncryptKey = "i0P+gFTkLPg0h53eNYjydg=="
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-config-format disabled, skip unknown files",
			args: []string{
				`-data-dir=` + dataDir,
				`-config-dir`, filepath.Join(dataDir, "conf"),
			},
			patch: func(rt *RuntimeConfig) {
				rt.Datacenter = "a"
				rt.DataDir = dataDir
			},
			pre: func() {
				writeFile(filepath.Join(dataDir, "conf", "valid.json"), []byte(`{"datacenter":"a"}`))
				writeFile(filepath.Join(dataDir, "conf", "invalid.skip"), []byte(`NOPE`))
			},
		},
		{
			desc: "-config-format=json",
			args: []string{
				`-data-dir=` + dataDir,
				`-config-format=json`,
				`-config-file`, filepath.Join(dataDir, "conf"),
			},
			patch: func(rt *RuntimeConfig) {
				rt.Datacenter = "a"
				rt.DataDir = dataDir
			},
			pre: func() {
				writeFile(filepath.Join(dataDir, "conf"), []byte(`{"datacenter":"a"}`))
			},
		},
		{
			desc: "-config-format=hcl",
			args: []string{
				`-data-dir=` + dataDir,
				`-config-format=hcl`,
				`-config-file`, filepath.Join(dataDir, "conf"),
			},
			patch: func(rt *RuntimeConfig) {
				rt.Datacenter = "a"
				rt.DataDir = dataDir
			},
			pre: func() {
				writeFile(filepath.Join(dataDir, "conf"), []byte(`datacenter = "a"`))
			},
		},
		{
			desc: "-config-format invalid",
			args: []string{
				`-config-format=foobar`,
			},
			err: "-config-format must be either 'hcl' or 'json'",
		},
		{
			desc: "-http-port",
			args: []string{
				`-http-port=123`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.HTTPPort = 123
				rt.HTTPAddrs = []net.Addr{tcpAddr("127.0.0.1:123")}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-join",
			args: []string{
				`-join=a`,
				`-join=b`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.StartJoinAddrsLAN = []string{"a", "b"}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-join-wan",
			args: []string{
				`-join-wan=a`,
				`-join-wan=b`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.StartJoinAddrsWAN = []string{"a", "b"}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-log-level",
			args: []string{
				`-log-level=a`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.LogLevel = "a"
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-node",
			args: []string{
				`-node=a`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.NodeName = "a"
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-node-id",
			args: []string{
				`-node-id=a`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.NodeID = "a"
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-node-meta",
			args: []string{
				`-node-meta=a:b`,
				`-node-meta=c:d`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.NodeMeta = map[string]string{"a": "b", "c": "d"}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-non-voting-server",
			args: []string{
				`-non-voting-server`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.NonVotingServer = true
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-pid-file",
			args: []string{
				`-pid-file=a`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.PidFile = "a"
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-protocol",
			args: []string{
				`-protocol=1`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.RPCProtocol = 1
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-raft-protocol",
			args: []string{
				`-raft-protocol=1`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.RaftProtocol = 1
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-recursor",
			args: []string{
				`-recursor=1.2.3.4`,
				`-recursor=5.6.7.8`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.DNSRecursors = []string{"1.2.3.4", "5.6.7.8"}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-rejoin",
			args: []string{
				`-rejoin`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.RejoinAfterLeave = true
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-retry-interval",
			args: []string{
				`-retry-interval=5s`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.RetryJoinIntervalLAN = 5 * time.Second
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-retry-interval-wan",
			args: []string{
				`-retry-interval-wan=5s`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.RetryJoinIntervalWAN = 5 * time.Second
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-retry-join",
			args: []string{
				`-retry-join=a`,
				`-retry-join=b`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.RetryJoinLAN = []string{"a", "b"}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-retry-join-wan",
			args: []string{
				`-retry-join-wan=a`,
				`-retry-join-wan=b`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.RetryJoinWAN = []string{"a", "b"}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-retry-max",
			args: []string{
				`-retry-max=1`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.RetryJoinMaxAttemptsLAN = 1
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-retry-max-wan",
			args: []string{
				`-retry-max-wan=1`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.RetryJoinMaxAttemptsWAN = 1
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-serf-lan-bind",
			args: []string{
				`-serf-lan-bind=1.2.3.4`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.SerfBindAddrLAN = tcpAddr("1.2.3.4:8301")
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-serf-lan-port",
			args: []string{
				`-serf-lan-port=123`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.SerfPortLAN = 123
				rt.SerfAdvertiseAddrLAN = tcpAddr("10.0.0.1:123")
				rt.SerfBindAddrLAN = tcpAddr("0.0.0.0:123")
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-serf-wan-bind",
			args: []string{
				`-serf-wan-bind=1.2.3.4`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.SerfBindAddrWAN = tcpAddr("1.2.3.4:8302")
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-serf-wan-port",
			args: []string{
				`-serf-wan-port=123`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.SerfPortWAN = 123
				rt.SerfAdvertiseAddrWAN = tcpAddr("10.0.0.1:123")
				rt.SerfBindAddrWAN = tcpAddr("0.0.0.0:123")
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-server",
			args: []string{
				`-server`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.ServerMode = true
				rt.LeaveOnTerm = false
				rt.SkipLeaveOnInt = true
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-server-port",
			args: []string{
				`-server-port=123`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.ServerPort = 123
				rt.RPCAdvertiseAddr = tcpAddr("10.0.0.1:123")
				rt.RPCBindAddr = tcpAddr("0.0.0.0:123")
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-syslog",
			args: []string{
				`-syslog`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.EnableSyslog = true
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-ui",
			args: []string{
				`-ui`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.EnableUI = true
				rt.DataDir = dataDir
			},
		},
		{
			desc: "-ui-dir",
			args: []string{
				`-ui-dir=a`,
				`-data-dir=` + dataDir,
			},
			patch: func(rt *RuntimeConfig) {
				rt.UIDir = "a"
				rt.DataDir = dataDir
			},
		},

		// ------------------------------------------------------------
		// ports and addresses
		//

		{
			desc: "bind addr any v4",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "bind_addr":"0.0.0.0" }`},
			hcl:  []string{`bind_addr = "0.0.0.0"`},
			patch: func(rt *RuntimeConfig) {
				rt.AdvertiseAddrLAN = ipAddr("10.0.0.1")
				rt.AdvertiseAddrWAN = ipAddr("10.0.0.1")
				rt.BindAddr = ipAddr("0.0.0.0")
				rt.RPCAdvertiseAddr = tcpAddr("10.0.0.1:8300")
				rt.RPCBindAddr = tcpAddr("0.0.0.0:8300")
				rt.SerfAdvertiseAddrLAN = tcpAddr("10.0.0.1:8301")
				rt.SerfAdvertiseAddrWAN = tcpAddr("10.0.0.1:8302")
				rt.SerfBindAddrLAN = tcpAddr("0.0.0.0:8301")
				rt.SerfBindAddrWAN = tcpAddr("0.0.0.0:8302")
				rt.TaggedAddresses = map[string]string{
					"lan": "10.0.0.1",
					"wan": "10.0.0.1",
				}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "bind addr any v6",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "bind_addr":"::" }`},
			hcl:  []string{`bind_addr = "::"`},
			patch: func(rt *RuntimeConfig) {
				rt.AdvertiseAddrLAN = ipAddr("dead:beef::1")
				rt.AdvertiseAddrWAN = ipAddr("dead:beef::1")
				rt.BindAddr = ipAddr("::")
				rt.RPCAdvertiseAddr = tcpAddr("[dead:beef::1]:8300")
				rt.RPCBindAddr = tcpAddr("[::]:8300")
				rt.SerfAdvertiseAddrLAN = tcpAddr("[dead:beef::1]:8301")
				rt.SerfAdvertiseAddrWAN = tcpAddr("[dead:beef::1]:8302")
				rt.SerfBindAddrLAN = tcpAddr("[::]:8301")
				rt.SerfBindAddrWAN = tcpAddr("[::]:8302")
				rt.TaggedAddresses = map[string]string{
					"lan": "dead:beef::1",
					"wan": "dead:beef::1",
				}
				rt.DataDir = dataDir
			},
			publicv6: func() ([]*net.IPAddr, error) {
				return []*net.IPAddr{ipAddr("dead:beef::1")}, nil
			},
		},
		{
			desc: "bind addr any and advertise set should not detect",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "bind_addr":"0.0.0.0", "advertise_addr": "1.2.3.4" }`},
			hcl:  []string{`bind_addr = "0.0.0.0" advertise_addr = "1.2.3.4"`},
			patch: func(rt *RuntimeConfig) {
				rt.AdvertiseAddrLAN = ipAddr("1.2.3.4")
				rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
				rt.BindAddr = ipAddr("0.0.0.0")
				rt.RPCAdvertiseAddr = tcpAddr("1.2.3.4:8300")
				rt.RPCBindAddr = tcpAddr("0.0.0.0:8300")
				rt.SerfAdvertiseAddrLAN = tcpAddr("1.2.3.4:8301")
				rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:8302")
				rt.SerfBindAddrLAN = tcpAddr("0.0.0.0:8301")
				rt.SerfBindAddrWAN = tcpAddr("0.0.0.0:8302")
				rt.TaggedAddresses = map[string]string{
					"lan": "1.2.3.4",
					"wan": "1.2.3.4",
				}
				rt.DataDir = dataDir
			},
			privatev4: func() ([]*net.IPAddr, error) {
				return nil, fmt.Errorf("should not detect advertise_addr")
			},
		},
		{
			desc: "client addr and ports == 0",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{
					"client_addr":"0.0.0.0",
					"ports":{}
				}`},
			hcl: []string{`
					client_addr = "0.0.0.0"
					ports {}
				`},
			patch: func(rt *RuntimeConfig) {
				rt.ClientAddrs = []*net.IPAddr{ipAddr("0.0.0.0")}
				rt.DNSAddrs = []net.Addr{tcpAddr("0.0.0.0:8600"), udpAddr("0.0.0.0:8600")}
				rt.HTTPAddrs = []net.Addr{tcpAddr("0.0.0.0:8500")}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "client addr and ports < 0",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{
					"client_addr":"0.0.0.0",
					"ports": { "dns":-1, "http":-2, "https":-3, "grpc":-4 }
				}`},
			hcl: []string{`
					client_addr = "0.0.0.0"
					ports { dns = -1 http = -2 https = -3 grpc = -4 }
				`},
			patch: func(rt *RuntimeConfig) {
				rt.ClientAddrs = []*net.IPAddr{ipAddr("0.0.0.0")}
				rt.DNSPort = -1
				rt.DNSAddrs = nil
				rt.HTTPPort = -1
				rt.HTTPAddrs = nil
				// HTTPS and gRPC default to disabled so shouldn't be different from
				// default rt.
				rt.DataDir = dataDir
			},
		},
		{
			desc: "client addr and ports > 0",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{
					"client_addr":"0.0.0.0",
					"ports":{ "dns": 1, "http": 2, "https": 3, "grpc": 4 }
				}`},
			hcl: []string{`
					client_addr = "0.0.0.0"
					ports { dns = 1 http = 2 https = 3 grpc = 4 }
				`},
			patch: func(rt *RuntimeConfig) {
				rt.ClientAddrs = []*net.IPAddr{ipAddr("0.0.0.0")}
				rt.DNSPort = 1
				rt.DNSAddrs = []net.Addr{tcpAddr("0.0.0.0:1"), udpAddr("0.0.0.0:1")}
				rt.HTTPPort = 2
				rt.HTTPAddrs = []net.Addr{tcpAddr("0.0.0.0:2")}
				rt.HTTPSPort = 3
				rt.HTTPSAddrs = []net.Addr{tcpAddr("0.0.0.0:3")}
				rt.GRPCPort = 4
				rt.GRPCAddrs = []net.Addr{tcpAddr("0.0.0.0:4")}
				rt.DataDir = dataDir
			},
		},

		{
			desc: "client addr, addresses and ports == 0",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{
					"client_addr":"0.0.0.0",
					"addresses": { "dns": "1.1.1.1", "http": "2.2.2.2", "https": "3.3.3.3", "grpc": "4.4.4.4" },
					"ports":{}
				}`},
			hcl: []string{`
					client_addr = "0.0.0.0"
					addresses = { dns = "1.1.1.1" http = "2.2.2.2" https = "3.3.3.3" grpc = "4.4.4.4" }
					ports {}
				`},
			patch: func(rt *RuntimeConfig) {
				rt.ClientAddrs = []*net.IPAddr{ipAddr("0.0.0.0")}
				rt.DNSAddrs = []net.Addr{tcpAddr("1.1.1.1:8600"), udpAddr("1.1.1.1:8600")}
				rt.HTTPAddrs = []net.Addr{tcpAddr("2.2.2.2:8500")}
				// HTTPS and gRPC default to disabled so shouldn't be different from
				// default rt.
				rt.DataDir = dataDir
			},
		},
		{
			desc: "client addr, addresses and ports < 0",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{
					"client_addr":"0.0.0.0",
					"addresses": { "dns": "1.1.1.1", "http": "2.2.2.2", "https": "3.3.3.3", "grpc": "4.4.4.4" },
					"ports": { "dns":-1, "http":-2, "https":-3, "grpc":-4 }
				}`},
			hcl: []string{`
					client_addr = "0.0.0.0"
					addresses = { dns = "1.1.1.1" http = "2.2.2.2" https = "3.3.3.3" grpc = "4.4.4.4" }
					ports { dns = -1 http = -2 https = -3 grpc = -4 }
				`},
			patch: func(rt *RuntimeConfig) {
				rt.ClientAddrs = []*net.IPAddr{ipAddr("0.0.0.0")}
				rt.DNSPort = -1
				rt.DNSAddrs = nil
				rt.HTTPPort = -1
				rt.HTTPAddrs = nil
				// HTTPS and gRPC default to disabled so shouldn't be different from
				// default rt.
				rt.DataDir = dataDir
			},
		},
		{
			desc: "client addr, addresses and ports",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{
					"client_addr": "0.0.0.0",
					"addresses": { "dns": "1.1.1.1", "http": "2.2.2.2", "https": "3.3.3.3", "grpc": "4.4.4.4" },
					"ports":{ "dns":1, "http":2, "https":3, "grpc":4 }
				}`},
			hcl: []string{`
					client_addr = "0.0.0.0"
					addresses = { dns = "1.1.1.1" http = "2.2.2.2" https = "3.3.3.3" grpc = "4.4.4.4" }
					ports { dns = 1 http = 2 https = 3 grpc = 4 }
				`},
			patch: func(rt *RuntimeConfig) {
				rt.ClientAddrs = []*net.IPAddr{ipAddr("0.0.0.0")}
				rt.DNSPort = 1
				rt.DNSAddrs = []net.Addr{tcpAddr("1.1.1.1:1"), udpAddr("1.1.1.1:1")}
				rt.HTTPPort = 2
				rt.HTTPAddrs = []net.Addr{tcpAddr("2.2.2.2:2")}
				rt.HTTPSPort = 3
				rt.HTTPSAddrs = []net.Addr{tcpAddr("3.3.3.3:3")}
				rt.GRPCPort = 4
				rt.GRPCAddrs = []net.Addr{tcpAddr("4.4.4.4:4")}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "client template and ports",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{
					"client_addr": "{{ printf \"1.2.3.4 2001:db8::1\" }}",
					"ports":{ "dns":1, "http":2, "https":3, "grpc":4 }
				}`},
			hcl: []string{`
					client_addr = "{{ printf \"1.2.3.4 2001:db8::1\" }}"
					ports { dns = 1 http = 2 https = 3 grpc = 4 }
				`},
			patch: func(rt *RuntimeConfig) {
				rt.ClientAddrs = []*net.IPAddr{ipAddr("1.2.3.4"), ipAddr("2001:db8::1")}
				rt.DNSPort = 1
				rt.DNSAddrs = []net.Addr{tcpAddr("1.2.3.4:1"), tcpAddr("[2001:db8::1]:1"), udpAddr("1.2.3.4:1"), udpAddr("[2001:db8::1]:1")}
				rt.HTTPPort = 2
				rt.HTTPAddrs = []net.Addr{tcpAddr("1.2.3.4:2"), tcpAddr("[2001:db8::1]:2")}
				rt.HTTPSPort = 3
				rt.HTTPSAddrs = []net.Addr{tcpAddr("1.2.3.4:3"), tcpAddr("[2001:db8::1]:3")}
				rt.GRPCPort = 4
				rt.GRPCAddrs = []net.Addr{tcpAddr("1.2.3.4:4"), tcpAddr("[2001:db8::1]:4")}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "client, address template and ports",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{
					"client_addr": "{{ printf \"1.2.3.4 2001:db8::1\" }}",
					"addresses": {
						"dns": "{{ printf \"1.1.1.1 2001:db8::10 \" }}",
						"http": "{{ printf \"2.2.2.2 unix://http 2001:db8::20 \" }}",
						"https": "{{ printf \"3.3.3.3 unix://https 2001:db8::30 \" }}",
						"grpc": "{{ printf \"4.4.4.4 unix://grpc 2001:db8::40 \" }}"
					},
					"ports":{ "dns":1, "http":2, "https":3, "grpc":4 }
				}`},
			hcl: []string{`
					client_addr = "{{ printf \"1.2.3.4 2001:db8::1\" }}"
					addresses = {
						dns = "{{ printf \"1.1.1.1 2001:db8::10 \" }}"
						http = "{{ printf \"2.2.2.2 unix://http 2001:db8::20 \" }}"
						https = "{{ printf \"3.3.3.3 unix://https 2001:db8::30 \" }}"
						grpc = "{{ printf \"4.4.4.4 unix://grpc 2001:db8::40 \" }}"
					}
					ports { dns = 1 http = 2 https = 3 grpc = 4 }
				`},
			patch: func(rt *RuntimeConfig) {
				rt.ClientAddrs = []*net.IPAddr{ipAddr("1.2.3.4"), ipAddr("2001:db8::1")}
				rt.DNSPort = 1
				rt.DNSAddrs = []net.Addr{tcpAddr("1.1.1.1:1"), tcpAddr("[2001:db8::10]:1"), udpAddr("1.1.1.1:1"), udpAddr("[2001:db8::10]:1")}
				rt.HTTPPort = 2
				rt.HTTPAddrs = []net.Addr{tcpAddr("2.2.2.2:2"), unixAddr("unix://http"), tcpAddr("[2001:db8::20]:2")}
				rt.HTTPSPort = 3
				rt.HTTPSAddrs = []net.Addr{tcpAddr("3.3.3.3:3"), unixAddr("unix://https"), tcpAddr("[2001:db8::30]:3")}
				rt.GRPCPort = 4
				rt.GRPCAddrs = []net.Addr{tcpAddr("4.4.4.4:4"), unixAddr("unix://grpc"), tcpAddr("[2001:db8::40]:4")}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "advertise address lan template",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "advertise_addr": "{{ printf \"1.2.3.4\" }}" }`},
			hcl:  []string{`advertise_addr = "{{ printf \"1.2.3.4\" }}"`},
			patch: func(rt *RuntimeConfig) {
				rt.AdvertiseAddrLAN = ipAddr("1.2.3.4")
				rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
				rt.RPCAdvertiseAddr = tcpAddr("1.2.3.4:8300")
				rt.SerfAdvertiseAddrLAN = tcpAddr("1.2.3.4:8301")
				rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:8302")
				rt.TaggedAddresses = map[string]string{
					"lan": "1.2.3.4",
					"wan": "1.2.3.4",
				}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "advertise address wan template",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "advertise_addr_wan": "{{ printf \"1.2.3.4\" }}" }`},
			hcl:  []string{`advertise_addr_wan = "{{ printf \"1.2.3.4\" }}"`},
			patch: func(rt *RuntimeConfig) {
				rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
				rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:8302")
				rt.TaggedAddresses = map[string]string{
					"lan": "10.0.0.1",
					"wan": "1.2.3.4",
				}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "advertise address lan with ports",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{
				"ports": {
					"server": 1000,
					"serf_lan": 2000,
					"serf_wan": 3000
				},
				"advertise_addr": "1.2.3.4"
			}`},
			hcl: []string{`
				ports {
					server = 1000
					serf_lan = 2000
					serf_wan = 3000
				}
				advertise_addr = "1.2.3.4"
			`},
			patch: func(rt *RuntimeConfig) {
				rt.AdvertiseAddrLAN = ipAddr("1.2.3.4")
				rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
				rt.RPCAdvertiseAddr = tcpAddr("1.2.3.4:1000")
				rt.RPCBindAddr = tcpAddr("0.0.0.0:1000")
				rt.SerfAdvertiseAddrLAN = tcpAddr("1.2.3.4:2000")
				rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:3000")
				rt.SerfBindAddrLAN = tcpAddr("0.0.0.0:2000")
				rt.SerfBindAddrWAN = tcpAddr("0.0.0.0:3000")
				rt.SerfPortLAN = 2000
				rt.SerfPortWAN = 3000
				rt.ServerPort = 1000
				rt.TaggedAddresses = map[string]string{
					"lan": "1.2.3.4",
					"wan": "1.2.3.4",
				}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "advertise address wan with ports",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{
				"ports": {
					"server": 1000,
					"serf_lan": 2000,
					"serf_wan": 3000
				},
				"advertise_addr_wan": "1.2.3.4"
			}`},
			hcl: []string{`
				ports {
					server = 1000
					serf_lan = 2000
					serf_wan = 3000
				}
				advertise_addr_wan = "1.2.3.4"
			`},
			patch: func(rt *RuntimeConfig) {
				rt.AdvertiseAddrLAN = ipAddr("10.0.0.1")
				rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
				rt.RPCAdvertiseAddr = tcpAddr("10.0.0.1:1000")
				rt.RPCBindAddr = tcpAddr("0.0.0.0:1000")
				rt.SerfAdvertiseAddrLAN = tcpAddr("10.0.0.1:2000")
				rt.SerfAdvertiseAddrWAN = tcpAddr("1.2.3.4:3000")
				rt.SerfBindAddrLAN = tcpAddr("0.0.0.0:2000")
				rt.SerfBindAddrWAN = tcpAddr("0.0.0.0:3000")
				rt.SerfPortLAN = 2000
				rt.SerfPortWAN = 3000
				rt.ServerPort = 1000
				rt.TaggedAddresses = map[string]string{
					"lan": "10.0.0.1",
					"wan": "1.2.3.4",
				}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "allow disabling serf wan port",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{
				"ports": {
					"serf_wan": -1
				},
				"advertise_addr_wan": "1.2.3.4"
			}`},
			hcl: []string{`
				ports {
					serf_wan = -1
				}
				advertise_addr_wan = "1.2.3.4"
			`},
			patch: func(rt *RuntimeConfig) {
				rt.AdvertiseAddrWAN = ipAddr("1.2.3.4")
				rt.SerfAdvertiseAddrWAN = nil
				rt.SerfBindAddrWAN = nil
				rt.TaggedAddresses = map[string]string{
					"lan": "10.0.0.1",
					"wan": "1.2.3.4",
				}
				rt.DataDir = dataDir
				rt.SerfPortWAN = -1
			},
		},
		{
			desc: "serf bind address lan template",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "serf_lan": "{{ printf \"1.2.3.4\" }}" }`},
			hcl:  []string{`serf_lan = "{{ printf \"1.2.3.4\" }}"`},
			patch: func(rt *RuntimeConfig) {
				rt.SerfBindAddrLAN = tcpAddr("1.2.3.4:8301")
				rt.DataDir = dataDir
			},
		},
		{
			desc: "serf bind address wan template",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "serf_wan": "{{ printf \"1.2.3.4\" }}" }`},
			hcl:  []string{`serf_wan = "{{ printf \"1.2.3.4\" }}"`},
			patch: func(rt *RuntimeConfig) {
				rt.SerfBindAddrWAN = tcpAddr("1.2.3.4:8302")
				rt.DataDir = dataDir
			},
		},
		{
			desc: "dns recursor templates with deduplication",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "recursors": [ "{{ printf \"5.6.7.8:9999\" }}", "{{ printf \"1.2.3.4\" }}", "{{ printf \"5.6.7.8:9999\" }}" ] }`},
			hcl:  []string{`recursors = [ "{{ printf \"5.6.7.8:9999\" }}", "{{ printf \"1.2.3.4\" }}", "{{ printf \"5.6.7.8:9999\" }}" ] `},
			patch: func(rt *RuntimeConfig) {
				rt.DNSRecursors = []string{"5.6.7.8:9999", "1.2.3.4"}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "start_join address template",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "start_join": ["{{ printf \"1.2.3.4 4.3.2.1\" }}"] }`},
			hcl:  []string{`start_join = ["{{ printf \"1.2.3.4 4.3.2.1\" }}"]`},
			patch: func(rt *RuntimeConfig) {
				rt.StartJoinAddrsLAN = []string{"1.2.3.4", "4.3.2.1"}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "start_join_wan address template",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "start_join_wan": ["{{ printf \"1.2.3.4 4.3.2.1\" }}"] }`},
			hcl:  []string{`start_join_wan = ["{{ printf \"1.2.3.4 4.3.2.1\" }}"]`},
			patch: func(rt *RuntimeConfig) {
				rt.StartJoinAddrsWAN = []string{"1.2.3.4", "4.3.2.1"}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "retry_join address template",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "retry_join": ["{{ printf \"1.2.3.4 4.3.2.1\" }}"] }`},
			hcl:  []string{`retry_join = ["{{ printf \"1.2.3.4 4.3.2.1\" }}"]`},
			patch: func(rt *RuntimeConfig) {
				rt.RetryJoinLAN = []string{"1.2.3.4", "4.3.2.1"}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "retry_join_wan address template",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "retry_join_wan": ["{{ printf \"1.2.3.4 4.3.2.1\" }}"] }`},
			hcl:  []string{`retry_join_wan = ["{{ printf \"1.2.3.4 4.3.2.1\" }}"]`},
			patch: func(rt *RuntimeConfig) {
				rt.RetryJoinWAN = []string{"1.2.3.4", "4.3.2.1"}
				rt.DataDir = dataDir
			},
		},

		// ------------------------------------------------------------
		// precedence rules
		//

		{
			desc: "precedence: merge order",
			args: []string{`-data-dir=` + dataDir},
			json: []string{
				`{
						"bootstrap": true,
						"bootstrap_expect": 1,
						"datacenter": "a",
						"start_join": ["a", "b"],
						"node_meta": {"a":"b"}
					}`,
				`{
						"bootstrap": false,
						"bootstrap_expect": 0,
						"datacenter":"b",
						"start_join": ["c", "d"],
						"node_meta": {"a":"c"}
					}`,
			},
			hcl: []string{
				`
					bootstrap = true
					bootstrap_expect = 1
					datacenter = "a"
					start_join = ["a", "b"]
					node_meta = { "a" = "b" }
					`,
				`
					bootstrap = false
					bootstrap_expect = 0
					datacenter = "b"
					start_join = ["c", "d"]
					node_meta = { "a" = "c" }
					`,
			},
			patch: func(rt *RuntimeConfig) {
				rt.Bootstrap = false
				rt.BootstrapExpect = 0
				rt.Datacenter = "b"
				rt.StartJoinAddrsLAN = []string{"a", "b", "c", "d"}
				rt.NodeMeta = map[string]string{"a": "c"}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "precedence: flag before file",
			json: []string{
				`{
						"advertise_addr": "1.2.3.4",
						"advertise_addr_wan": "5.6.7.8",
						"bootstrap":true,
						"bootstrap_expect": 3,
						"datacenter":"a",
						"node_meta": {"a":"b"},
						"recursors":["1.2.3.5", "5.6.7.9"],
						"serf_lan": "a",
						"serf_wan": "a",
						"start_join":["a", "b"]
					}`,
			},
			hcl: []string{
				`
					advertise_addr = "1.2.3.4"
					advertise_addr_wan = "5.6.7.8"
					bootstrap = true
					bootstrap_expect = 3
					datacenter = "a"
					node_meta = { "a" = "b" }
					recursors = ["1.2.3.5", "5.6.7.9"]
					serf_lan = "a"
					serf_wan = "a"
					start_join = ["a", "b"]
					`,
			},
			args: []string{
				`-advertise=1.1.1.1`,
				`-advertise-wan=2.2.2.2`,
				`-bootstrap=false`,
				`-bootstrap-expect=0`,
				`-datacenter=b`,
				`-data-dir=` + dataDir,
				`-join`, `c`, `-join=d`,
				`-node-meta=a:c`,
				`-recursor`, `1.2.3.6`, `-recursor=5.6.7.10`,
				`-serf-lan-bind=3.3.3.3`,
				`-serf-wan-bind=4.4.4.4`,
			},
			patch: func(rt *RuntimeConfig) {
				rt.AdvertiseAddrLAN = ipAddr("1.1.1.1")
				rt.AdvertiseAddrWAN = ipAddr("2.2.2.2")
				rt.RPCAdvertiseAddr = tcpAddr("1.1.1.1:8300")
				rt.SerfAdvertiseAddrLAN = tcpAddr("1.1.1.1:8301")
				rt.SerfAdvertiseAddrWAN = tcpAddr("2.2.2.2:8302")
				rt.Datacenter = "b"
				rt.DNSRecursors = []string{"1.2.3.6", "5.6.7.10", "1.2.3.5", "5.6.7.9"}
				rt.NodeMeta = map[string]string{"a": "c"}
				rt.SerfBindAddrLAN = tcpAddr("3.3.3.3:8301")
				rt.SerfBindAddrWAN = tcpAddr("4.4.4.4:8302")
				rt.StartJoinAddrsLAN = []string{"c", "d", "a", "b"}
				rt.TaggedAddresses = map[string]string{
					"lan": "1.1.1.1",
					"wan": "2.2.2.2",
				}
				rt.DataDir = dataDir
			},
		},

		// ------------------------------------------------------------
		// transformations
		//

		{
			desc: "raft performance scaling",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "performance": { "raft_multiplier": 9} }`},
			hcl:  []string{`performance = { raft_multiplier=9 }`},
			patch: func(rt *RuntimeConfig) {
				rt.ConsulRaftElectionTimeout = 9 * 1000 * time.Millisecond
				rt.ConsulRaftHeartbeatTimeout = 9 * 1000 * time.Millisecond
				rt.ConsulRaftLeaderLeaseTimeout = 9 * 500 * time.Millisecond
				rt.DataDir = dataDir
			},
		},

		// ------------------------------------------------------------
		// validations
		//

		{
			desc: "invalid input",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`this is not JSON`},
			hcl:  []string{`*** 0123 this is not HCL`},
			err:  "Error parsing",
		},
		{
			desc: "datacenter is lower-cased",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "datacenter": "A" }`},
			hcl:  []string{`datacenter = "A"`},
			patch: func(rt *RuntimeConfig) {
				rt.Datacenter = "a"
				rt.DataDir = dataDir
			},
		},
		{
			desc: "acl_datacenter is lower-cased",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "acl_datacenter": "A" }`},
			hcl:  []string{`acl_datacenter = "A"`},
			patch: func(rt *RuntimeConfig) {
				rt.ACLsEnabled = true
				rt.ACLDatacenter = "a"
				rt.DataDir = dataDir
				rt.PrimaryDatacenter = "a"
			},
			warns: []string{`The 'acl_datacenter' field is deprecated. Use the 'primary_datacenter' field instead.`},
		},
		{
			desc: "acl_replication_token enables acl replication",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "acl_replication_token": "a" }`},
			hcl:  []string{`acl_replication_token = "a"`},
			patch: func(rt *RuntimeConfig) {
				rt.ACLReplicationToken = "a"
				rt.ACLTokenReplication = true
				rt.DataDir = dataDir
			},
		},
		{
			desc: "advertise address detect fails v4",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "bind_addr": "0.0.0.0"}`},
			hcl:  []string{`bind_addr = "0.0.0.0"`},
			privatev4: func() ([]*net.IPAddr, error) {
				return nil, errors.New("some error")
			},
			err: "Error detecting private IPv4 address: some error",
		},
		{
			desc: "advertise address detect none v4",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "bind_addr": "0.0.0.0"}`},
			hcl:  []string{`bind_addr = "0.0.0.0"`},
			privatev4: func() ([]*net.IPAddr, error) {
				return nil, nil
			},
			err: "No private IPv4 address found",
		},
		{
			desc: "advertise address detect multiple v4",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "bind_addr": "0.0.0.0"}`},
			hcl:  []string{`bind_addr = "0.0.0.0"`},
			privatev4: func() ([]*net.IPAddr, error) {
				return []*net.IPAddr{ipAddr("1.1.1.1"), ipAddr("2.2.2.2")}, nil
			},
			err: "Multiple private IPv4 addresses found. Please configure one",
		},
		{
			desc: "advertise address detect fails v6",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "bind_addr": "::"}`},
			hcl:  []string{`bind_addr = "::"`},
			publicv6: func() ([]*net.IPAddr, error) {
				return nil, errors.New("some error")
			},
			err: "Error detecting public IPv6 address: some error",
		},
		{
			desc: "advertise address detect none v6",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "bind_addr": "::"}`},
			hcl:  []string{`bind_addr = "::"`},
			publicv6: func() ([]*net.IPAddr, error) {
				return nil, nil
			},
			err: "No public IPv6 address found",
		},
		{
			desc: "advertise address detect multiple v6",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "bind_addr": "::"}`},
			hcl:  []string{`bind_addr = "::"`},
			publicv6: func() ([]*net.IPAddr, error) {
				return []*net.IPAddr{ipAddr("dead:beef::1"), ipAddr("dead:beef::2")}, nil
			},
			err: "Multiple public IPv6 addresses found. Please configure one",
		},
		{
			desc:     "ae_interval invalid == 0",
			args:     []string{`-data-dir=` + dataDir},
			jsontail: []string{`{ "ae_interval": "0s" }`},
			hcltail:  []string{`ae_interval = "0s"`},
			err:      `ae_interval cannot be 0s. Must be positive`,
		},
		{
			desc:     "ae_interval invalid < 0",
			args:     []string{`-data-dir=` + dataDir},
			jsontail: []string{`{ "ae_interval": "-1s" }`},
			hcltail:  []string{`ae_interval = "-1s"`},
			err:      `ae_interval cannot be -1s. Must be positive`,
		},
		{
			desc: "acl_datacenter invalid",
			args: []string{
				`-datacenter=a`,
				`-data-dir=` + dataDir,
			},
			json:  []string{`{ "acl_datacenter": "%" }`},
			hcl:   []string{`acl_datacenter = "%"`},
			err:   `acl_datacenter cannot be "%". Please use only [a-z0-9-_]`,
			warns: []string{`The 'acl_datacenter' field is deprecated. Use the 'primary_datacenter' field instead.`},
		},
		{
			desc: "autopilot.max_trailing_logs invalid",
			args: []string{
				`-datacenter=a`,
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "autopilot": { "max_trailing_logs": -1 } }`},
			hcl:  []string{`autopilot = { max_trailing_logs = -1 }`},
			err:  "autopilot.max_trailing_logs cannot be -1. Must be greater than or equal to zero",
		},
		{
			desc: "bind_addr cannot be empty",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "bind_addr": "" }`},
			hcl:  []string{`bind_addr = ""`},
			err:  "bind_addr cannot be empty",
		},
		{
			desc: "bind_addr does not allow multiple addresses",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "bind_addr": "1.1.1.1 2.2.2.2" }`},
			hcl:  []string{`bind_addr = "1.1.1.1 2.2.2.2"`},
			err:  "bind_addr cannot contain multiple addresses",
		},
		{
			desc: "bind_addr cannot be a unix socket",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "bind_addr": "unix:///foo" }`},
			hcl:  []string{`bind_addr = "unix:///foo"`},
			err:  "bind_addr cannot be a unix socket",
		},
		{
			desc: "bootstrap without server",
			args: []string{
				`-datacenter=a`,
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "bootstrap": true }`},
			hcl:  []string{`bootstrap = true`},
			err:  "'bootstrap = true' requires 'server = true'",
		},
		{
			desc: "bootstrap-expect without server",
			args: []string{
				`-datacenter=a`,
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "bootstrap_expect": 3 }`},
			hcl:  []string{`bootstrap_expect = 3`},
			err:  "'bootstrap_expect > 0' requires 'server = true'",
		},
		{
			desc: "bootstrap-expect invalid",
			args: []string{
				`-datacenter=a`,
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "bootstrap_expect": -1 }`},
			hcl:  []string{`bootstrap_expect = -1`},
			err:  "bootstrap_expect cannot be -1. Must be greater than or equal to zero",
		},
		{
			desc: "bootstrap-expect and dev mode",
			args: []string{
				`-dev`,
				`-datacenter=a`,
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "bootstrap_expect": 3, "server": true }`},
			hcl:  []string{`bootstrap_expect = 3 server = true`},
			err:  "'bootstrap_expect > 0' not allowed in dev mode",
		},
		{
			desc: "bootstrap-expect and bootstrap",
			args: []string{
				`-datacenter=a`,
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "bootstrap": true, "bootstrap_expect": 3, "server": true }`},
			hcl:  []string{`bootstrap = true bootstrap_expect = 3 server = true`},
			err:  "'bootstrap_expect > 0' and 'bootstrap = true' are mutually exclusive",
		},
		{
			desc: "bootstrap-expect=1 equals bootstrap",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "bootstrap_expect": 1, "server": true }`},
			hcl:  []string{`bootstrap_expect = 1 server = true`},
			patch: func(rt *RuntimeConfig) {
				rt.Bootstrap = true
				rt.BootstrapExpect = 0
				rt.LeaveOnTerm = false
				rt.ServerMode = true
				rt.SkipLeaveOnInt = true
				rt.DataDir = dataDir
			},
			warns: []string{"BootstrapExpect is set to 1; this is the same as Bootstrap mode.", "bootstrap = true: do not enable unless necessary"},
		},
		{
			desc: "bootstrap-expect=2 warning",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "bootstrap_expect": 2, "server": true }`},
			hcl:  []string{`bootstrap_expect = 2 server = true`},
			patch: func(rt *RuntimeConfig) {
				rt.BootstrapExpect = 2
				rt.LeaveOnTerm = false
				rt.ServerMode = true
				rt.SkipLeaveOnInt = true
				rt.DataDir = dataDir
			},
			warns: []string{
				`bootstrap_expect = 2: A cluster with 2 servers will provide no failure tolerance. See https://www.consul.io/docs/internals/consensus.html#deployment-table`,
				`bootstrap_expect > 0: expecting 2 servers`,
			},
		},
		{
			desc: "bootstrap-expect > 2 but even warning",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "bootstrap_expect": 4, "server": true }`},
			hcl:  []string{`bootstrap_expect = 4 server = true`},
			patch: func(rt *RuntimeConfig) {
				rt.BootstrapExpect = 4
				rt.LeaveOnTerm = false
				rt.ServerMode = true
				rt.SkipLeaveOnInt = true
				rt.DataDir = dataDir
			},
			warns: []string{
				`bootstrap_expect is even number: A cluster with an even number of servers does not achieve optimum fault tolerance. See https://www.consul.io/docs/internals/consensus.html#deployment-table`,
				`bootstrap_expect > 0: expecting 4 servers`,
			},
		},
		{
			desc: "client mode sets LeaveOnTerm and SkipLeaveOnInt correctly",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "server": false }`},
			hcl:  []string{` server = false`},
			patch: func(rt *RuntimeConfig) {
				rt.LeaveOnTerm = true
				rt.ServerMode = false
				rt.SkipLeaveOnInt = false
				rt.DataDir = dataDir
			},
		},
		{
			desc: "client does not allow socket",
			args: []string{
				`-datacenter=a`,
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "client_addr": "unix:///foo" }`},
			hcl:  []string{`client_addr = "unix:///foo"`},
			err:  "client_addr cannot be a unix socket",
		},
		{
			desc: "datacenter invalid",
			args: []string{`-data-dir=` + dataDir},
			json: []string{`{ "datacenter": "%" }`},
			hcl:  []string{`datacenter = "%"`},
			err:  `datacenter cannot be "%". Please use only [a-z0-9-_]`,
		},
		{
			desc: "dns does not allow socket",
			args: []string{
				`-datacenter=a`,
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "addresses": {"dns": "unix:///foo" } }`},
			hcl:  []string{`addresses = { dns = "unix:///foo" }`},
			err:  "DNS address cannot be a unix socket",
		},
		{
			desc: "ui and ui_dir",
			args: []string{
				`-datacenter=a`,
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "ui": true, "ui_dir": "a" }`},
			hcl:  []string{`ui = true ui_dir = "a"`},
			err: "Both the ui and ui-dir flags were specified, please provide only one.\n" +
				"If trying to use your own web UI resources, use the ui-dir flag.\n" +
				"If using Consul version 0.7.0 or later, the web UI is included in the binary so use ui to enable it",
		},

		// test ANY address failures
		// to avoid combinatory explosion for tests use 0.0.0.0, :: or [::] but not all of them
		{
			desc: "advertise_addr any",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "advertise_addr": "0.0.0.0" }`},
			hcl:  []string{`advertise_addr = "0.0.0.0"`},
			err:  "Advertise address cannot be 0.0.0.0, :: or [::]",
		},
		{
			desc: "advertise_addr_wan any",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "advertise_addr_wan": "::" }`},
			hcl:  []string{`advertise_addr_wan = "::"`},
			err:  "Advertise WAN address cannot be 0.0.0.0, :: or [::]",
		},
		{
			desc: "recursors any",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "recursors": ["::"] }`},
			hcl:  []string{`recursors = ["::"]`},
			err:  "DNS recursor address cannot be 0.0.0.0, :: or [::]",
		},
		{
			desc: "dns_config.udp_answer_limit invalid",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "dns_config": { "udp_answer_limit": -1 } }`},
			hcl:  []string{`dns_config = { udp_answer_limit = -1 }`},
			err:  "dns_config.udp_answer_limit cannot be -1. Must be greater than or equal to zero",
		},
		{
			desc: "dns_config.a_record_limit invalid",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "dns_config": { "a_record_limit": -1 } }`},
			hcl:  []string{`dns_config = { a_record_limit = -1 }`},
			err:  "dns_config.a_record_limit cannot be -1. Must be greater than or equal to zero",
		},
		{
			desc: "performance.raft_multiplier < 0",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "performance": { "raft_multiplier": -1 } }`},
			hcl:  []string{`performance = { raft_multiplier = -1 }`},
			err:  `performance.raft_multiplier cannot be -1. Must be between 1 and 10`,
		},
		{
			desc: "performance.raft_multiplier == 0",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "performance": { "raft_multiplier": 0 } }`},
			hcl:  []string{`performance = { raft_multiplier = 0 }`},
			err:  `performance.raft_multiplier cannot be 0. Must be between 1 and 10`,
		},
		{
			desc: "performance.raft_multiplier > 10",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "performance": { "raft_multiplier": 20 } }`},
			hcl:  []string{`performance = { raft_multiplier = 20 }`},
			err:  `performance.raft_multiplier cannot be 20. Must be between 1 and 10`,
		},
		{
			desc: "node_name invalid",
			args: []string{
				`-data-dir=` + dataDir,
				`-node=`,
			},
			hostname: func() (string, error) { return "", nil },
			err:      "node_name cannot be empty",
		},
		{
			desc: "node_meta key too long",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{
				`{ "dns_config": { "udp_answer_limit": 1 } }`,
				`{ "node_meta": { "` + randomString(130) + `": "a" } }`,
			},
			hcl: []string{
				`dns_config = { udp_answer_limit = 1 }`,
				`node_meta = { "` + randomString(130) + `" = "a" }`,
			},
			err: "Key is too long (limit: 128 characters)",
		},
		{
			desc: "node_meta value too long",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{
				`{ "dns_config": { "udp_answer_limit": 1 } }`,
				`{ "node_meta": { "a": "` + randomString(520) + `" } }`,
			},
			hcl: []string{
				`dns_config = { udp_answer_limit = 1 }`,
				`node_meta = { "a" = "` + randomString(520) + `" }`,
			},
			err: "Value is too long (limit: 512 characters)",
		},
		{
			desc: "node_meta too many keys",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{
				`{ "dns_config": { "udp_answer_limit": 1 } }`,
				`{ "node_meta": {` + metaPairs(70, "json") + `} }`,
			},
			hcl: []string{
				`dns_config = { udp_answer_limit = 1 }`,
				`node_meta = {` + metaPairs(70, "hcl") + ` }`,
			},
			err: "Node metadata cannot contain more than 64 key/value pairs",
		},
		{
			desc: "unique listeners dns vs http",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{
					"client_addr": "1.2.3.4",
					"ports": { "dns": 1000, "http": 1000 }
				}`},
			hcl: []string{`
					client_addr = "1.2.3.4"
					ports = { dns = 1000 http = 1000 }
				`},
			err: "HTTP address 1.2.3.4:1000 already configured for DNS",
		},
		{
			desc: "unique listeners dns vs https",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{
					"client_addr": "1.2.3.4",
					"ports": { "dns": 1000, "https": 1000 }
				}`},
			hcl: []string{`
					client_addr = "1.2.3.4"
					ports = { dns = 1000 https = 1000 }
				`},
			err: "HTTPS address 1.2.3.4:1000 already configured for DNS",
		},
		{
			desc: "unique listeners http vs https",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{
					"client_addr": "1.2.3.4",
					"ports": { "http": 1000, "https": 1000 }
				}`},
			hcl: []string{`
					client_addr = "1.2.3.4"
					ports = { http = 1000 https = 1000 }
				`},
			err: "HTTPS address 1.2.3.4:1000 already configured for HTTP",
		},
		{
			desc: "unique advertise addresses HTTP vs RPC",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{
					"addresses": { "http": "10.0.0.1" },
					"ports": { "http": 1000, "server": 1000 }
				}`},
			hcl: []string{`
					addresses = { http = "10.0.0.1" }
					ports = { http = 1000 server = 1000 }
				`},
			err: "RPC Advertise address 10.0.0.1:1000 already configured for HTTP",
		},
		{
			desc: "unique advertise addresses RPC vs Serf LAN",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{
					"ports": { "server": 1000, "serf_lan": 1000 }
				}`},
			hcl: []string{`
					ports = { server = 1000 serf_lan = 1000 }
				`},
			err: "Serf Advertise LAN address 10.0.0.1:1000 already configured for RPC Advertise",
		},
		{
			desc: "unique advertise addresses RPC vs Serf WAN",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{
					"ports": { "server": 1000, "serf_wan": 1000 }
				}`},
			hcl: []string{`
					ports = { server = 1000 serf_wan = 1000 }
				`},
			err: "Serf Advertise WAN address 10.0.0.1:1000 already configured for RPC Advertise",
		},
		{
			desc: "sidecar_service can't have ID",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{
				  "service": {
						"name": "web",
						"port": 1234,
						"connect": {
							"sidecar_service": {
								"ID": "random-sidecar-id"
							}
						}
					}
				}`},
			hcl: []string{`
				service {
					name = "web"
					port = 1234
					connect {
						sidecar_service {
							ID = "random-sidecar-id"
						}
					}
				}
			`},
			err: "sidecar_service can't specify an ID",
		},
		{
			desc: "sidecar_service can't have nested sidecar",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{
				  "service": {
						"name": "web",
						"port": 1234,
						"connect": {
							"sidecar_service": {
								"connect": {
									"sidecar_service": {}
								}
							}
						}
					}
				}`},
			hcl: []string{`
				service {
					name = "web"
					port = 1234
					connect {
						sidecar_service {
							connect {
								sidecar_service {
								}
							}
						}
					}
				}
			`},
			err: "sidecar_service can't have a nested sidecar_service",
		},
		{
			desc: "sidecar_service can't have managed proxy",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{
				  "service": {
						"name": "web",
						"port": 1234,
						"connect": {
							"sidecar_service": {
								"connect": {
									"proxy": {}
								}
							}
						}
					}
				}`},
			hcl: []string{`
				service {
					name = "web"
					port = 1234
					connect {
						sidecar_service {
							connect {
								proxy {
								}
							}
						}
					}
				}
			`},
			err: "sidecar_service can't have a managed proxy",
		},
		{
			desc: "telemetry.prefix_filter cannot be empty",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{
					"telemetry": { "prefix_filter": [""] }
				}`},
			hcl: []string{`
					telemetry = { prefix_filter = [""] }
				`},
			patch: func(rt *RuntimeConfig) {
				rt.DataDir = dataDir
			},
			warns: []string{"Cannot have empty filter rule in prefix_filter"},
		},
		{
			desc: "telemetry.prefix_filter must start with + or -",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{
					"telemetry": { "prefix_filter": ["+foo", "-bar", "nix"] }
				}`},
			hcl: []string{`
					telemetry = { prefix_filter = ["+foo", "-bar", "nix"] }
				`},
			patch: func(rt *RuntimeConfig) {
				rt.DataDir = dataDir
				rt.Telemetry.AllowedPrefixes = []string{"foo"}
				rt.Telemetry.BlockedPrefixes = []string{"bar"}
			},
			warns: []string{`Filter rule must begin with either '+' or '-': "nix"`},
		},
		{
			desc: "encrypt has invalid key",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "encrypt": "this is not a valid key" }`},
			hcl:  []string{` encrypt = "this is not a valid key" `},
			err:  "encrypt has invalid key: illegal base64 data at input byte 4",
		},
		{
			desc: "encrypt given but LAN keyring exists",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "encrypt": "i0P+gFTkLPg0h53eNYjydg==" }`},
			hcl:  []string{` encrypt = "i0P+gFTkLPg0h53eNYjydg==" `},
			patch: func(rt *RuntimeConfig) {
				rt.EncryptKey = "i0P+gFTkLPg0h53eNYjydg=="
				rt.DataDir = dataDir
			},
			pre: func() {
				writeFile(filepath.Join(dataDir, SerfLANKeyring), []byte("i0P+gFTkLPg0h53eNYjydg=="))
			},
			warns: []string{`WARNING: LAN keyring exists but -encrypt given, using keyring`},
		},
		{
			desc: "encrypt given but WAN keyring exists",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{ "encrypt": "i0P+gFTkLPg0h53eNYjydg==", "server": true }`},
			hcl:  []string{` encrypt = "i0P+gFTkLPg0h53eNYjydg==" server = true `},
			patch: func(rt *RuntimeConfig) {
				rt.EncryptKey = "i0P+gFTkLPg0h53eNYjydg=="
				rt.ServerMode = true
				rt.LeaveOnTerm = false
				rt.SkipLeaveOnInt = true
				rt.DataDir = dataDir
			},
			pre: func() {
				writeFile(filepath.Join(dataDir, SerfWANKeyring), []byte("i0P+gFTkLPg0h53eNYjydg=="))
			},
			warns: []string{`WARNING: WAN keyring exists but -encrypt given, using keyring`},
		},
		{
			desc: "multiple check files",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{
				`{ "check": { "name": "a", "args": ["/bin/true"] } }`,
				`{ "check": { "name": "b", "args": ["/bin/false"] } }`,
			},
			hcl: []string{
				`check = { name = "a" args = ["/bin/true"] }`,
				`check = { name = "b" args = ["/bin/false"] }`,
			},
			patch: func(rt *RuntimeConfig) {
				rt.Checks = []*structs.CheckDefinition{
					&structs.CheckDefinition{Name: "a", ScriptArgs: []string{"/bin/true"}},
					&structs.CheckDefinition{Name: "b", ScriptArgs: []string{"/bin/false"}},
				}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "grpc check",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{
				`{ "check": { "name": "a", "grpc": "localhost:12345/foo", "grpc_use_tls": true } }`,
			},
			hcl: []string{
				`check = { name = "a" grpc = "localhost:12345/foo", grpc_use_tls = true }`,
			},
			patch: func(rt *RuntimeConfig) {
				rt.Checks = []*structs.CheckDefinition{
					&structs.CheckDefinition{Name: "a", GRPC: "localhost:12345/foo", GRPCUseTLS: true},
				}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "alias check with no node",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{
				`{ "check": { "name": "a", "alias_service": "foo" } }`,
			},
			hcl: []string{
				`check = { name = "a", alias_service = "foo" }`,
			},
			patch: func(rt *RuntimeConfig) {
				rt.Checks = []*structs.CheckDefinition{
					&structs.CheckDefinition{Name: "a", AliasService: "foo"},
				}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "multiple service files",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{
				`{ "service": { "name": "a", "port": 80 } }`,
				`{ "service": { "name": "b", "port": 90, "meta": {"my": "value"}, "weights": {"passing": 13} } }`,
			},
			hcl: []string{
				`service = { name = "a" port = 80 }`,
				`service = { name = "b" port = 90 meta={my="value"}, weights={passing=13}}`,
			},
			patch: func(rt *RuntimeConfig) {
				rt.Services = []*structs.ServiceDefinition{
					&structs.ServiceDefinition{Name: "a", Port: 80, Weights: &structs.Weights{
						Passing: 1,
						Warning: 1,
					}},
					&structs.ServiceDefinition{Name: "b", Port: 90, Meta: map[string]string{"my": "value"}, Weights: &structs.Weights{
						Passing: 13,
						Warning: 1,
					}},
				}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "service with wrong meta: too long key",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{
				`{ "service": { "name": "a", "port": 80, "meta": { "` + randomString(520) + `": "metaValue" } } }`,
			},
			hcl: []string{
				`service = { name = "a" port = 80, meta={` + randomString(520) + `="metaValue"} }`,
			},
			err: `Key is too long`,
		},
		{
			desc: "service with wrong meta: too long value",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{
				`{ "service": { "name": "a", "port": 80, "meta": { "a": "` + randomString(520) + `" } } }`,
			},
			hcl: []string{
				`service = { name = "a" port = 80, meta={a="` + randomString(520) + `"} }`,
			},
			err: `Value is too long`,
		},
		{
			desc: "service with wrong meta: too many meta",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{
				`{ "service": { "name": "a", "port": 80, "meta": { ` + metaPairs(70, "json") + `} } }`,
			},
			hcl: []string{
				`service = { name = "a" port = 80 meta={` + metaPairs(70, "hcl") + `} }`,
			},
			err: `invalid meta for service a: Node metadata cannot contain more than 64 key`,
		},
		{
			desc: "translated keys",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{
				`{
					"service": {
						"name": "a",
						"port": 80,
						"enable_tag_override": true,
						"check": {
							"id": "x",
							"name": "y",
							"DockerContainerID": "z",
							"DeregisterCriticalServiceAfter": "10s",
							"ScriptArgs": ["a", "b"]
						}
					}
				}`,
			},
			hcl: []string{
				`service = {
					name = "a"
					port = 80
					enable_tag_override = true
					check = {
						id = "x"
						name = "y"
						DockerContainerID = "z"
						DeregisterCriticalServiceAfter = "10s"
						ScriptArgs = ["a", "b"]
					}
				}`,
			},
			patch: func(rt *RuntimeConfig) {
				rt.Services = []*structs.ServiceDefinition{
					&structs.ServiceDefinition{
						Name:              "a",
						Port:              80,
						EnableTagOverride: true,
						Checks: []*structs.CheckType{
							&structs.CheckType{
								CheckID:                        types.CheckID("x"),
								Name:                           "y",
								DockerContainerID:              "z",
								DeregisterCriticalServiceAfter: 10 * time.Second,
								ScriptArgs:                     []string{"a", "b"},
							},
						},
						Weights: &structs.Weights{
							Passing: 1,
							Warning: 1,
						},
					},
				}
				rt.DataDir = dataDir
			},
		},
		{
			desc: "ignore snapshot_agent sub-object",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{
				`{ "snapshot_agent": { "dont": "care" } }`,
			},
			hcl: []string{
				`snapshot_agent = { dont = "care" }`,
			},
			patch: func(rt *RuntimeConfig) {
				rt.DataDir = dataDir
			},
		},

		{
			desc: "Service managed proxy 'upstreams'",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{
				`{
						"service": {
							"name": "web",
							"port": 8080,
							"connect": {
								"proxy": {
									"upstreams": [{
										"destination_name": "db",
										"local_bind_port": 1234
									}]
								}
							}
						}
					}`,
			},
			hcl: []string{
				`service {
					name = "web"
					port = 8080
					connect {
						proxy {
							upstreams {
								destination_name = "db"
								local_bind_port = 1234
							}
						}
					}
				}`,
			},
			patch: func(rt *RuntimeConfig) {
				rt.DataDir = dataDir
				rt.Services = []*structs.ServiceDefinition{
					&structs.ServiceDefinition{
						Name: "web",
						Port: 8080,
						Connect: &structs.ServiceConnect{
							Proxy: &structs.ServiceDefinitionConnectProxy{
								Upstreams: structs.Upstreams{
									{
										DestinationName: "db",
										DestinationType: structs.UpstreamDestTypeService,
										LocalBindPort:   1234,
									},
								},
							},
						},
						Weights: &structs.Weights{
							Passing: 1,
							Warning: 1,
						},
					},
				}
			},
		},

		{
			desc: "Multiple service managed proxy 'upstreams'",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{
				`{
						"service": {
							"name": "web",
							"port": 8080,
							"connect": {
								"proxy": {
									"upstreams": [{
										"destination_name": "db",
										"local_bind_port": 1234
									}, {
										"destination_name": "cache",
										"local_bind_port": 2345
									}]
								}
							}
						}
					}`,
			},
			hcl: []string{
				`service {
					name = "web"
					port = 8080
					connect {
						proxy {
							upstreams = [
								{
									destination_name = "db"
									local_bind_port = 1234
								},
							  {
									destination_name = "cache"
									local_bind_port = 2345
								}
							]
						}
					}
				}`,
			},
			patch: func(rt *RuntimeConfig) {
				rt.DataDir = dataDir
				rt.Services = []*structs.ServiceDefinition{
					&structs.ServiceDefinition{
						Name: "web",
						Port: 8080,
						Connect: &structs.ServiceConnect{
							Proxy: &structs.ServiceDefinitionConnectProxy{
								Upstreams: structs.Upstreams{
									{
										DestinationName: "db",
										DestinationType: structs.UpstreamDestTypeService,
										LocalBindPort:   1234,
									},
									{
										DestinationName: "cache",
										DestinationType: structs.UpstreamDestTypeService,
										LocalBindPort:   2345,
									},
								},
							},
						},
						Weights: &structs.Weights{
							Passing: 1,
							Warning: 1,
						},
					},
				}
			},
		},

		{
			desc: "enabling Connect allow_managed_root",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{
				`{ "connect": { "proxy": { "allow_managed_root": true } } }`,
			},
			hcl: []string{
				`connect { proxy { allow_managed_root = true } }`,
			},
			patch: func(rt *RuntimeConfig) {
				rt.DataDir = dataDir
				rt.ConnectProxyAllowManagedRoot = true
			},
		},

		{
			desc: "enabling Connect allow_managed_api_registration",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{
				`{ "connect": { "proxy": { "allow_managed_api_registration": true } } }`,
			},
			hcl: []string{
				`connect { proxy { allow_managed_api_registration = true } }`,
			},
			patch: func(rt *RuntimeConfig) {
				rt.DataDir = dataDir
				rt.ConnectProxyAllowManagedAPIRegistration = true
			},
		},

		{
			// This tests that we correct added the nested paths to arrays of objects
			// to the exceptions in patchSliceOfMaps in config.go (for single service)
			desc: "service.connectsidecar_service with checks and upstreams",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{
				  "service": {
						"name": "web",
						"port": 1234,
						"connect": {
							"sidecar_service": {
								"port": 2345,
								"checks": [
									{
										"TCP": "127.0.0.1:2345",
										"Interval": "10s"
									}
								],
								"proxy": {
									"upstreams": [
										{
											"destination_name": "db",
											"local_bind_port": 7000
										}
									]
								}
							}
						}
					}
				}`},
			hcl: []string{`
				service {
					name = "web"
					port = 1234
					connect {
						sidecar_service {
							port = 2345
							checks = [
								{
									tcp = "127.0.0.1:2345"
									interval = "10s"
								}
							]
							proxy {
								upstreams = [
									{
										destination_name = "db"
										local_bind_port = 7000
									},
								]
							}
						}
					}
				}
			`},
			patch: func(rt *RuntimeConfig) {
				rt.DataDir = dataDir
				rt.Services = []*structs.ServiceDefinition{
					{
						Name: "web",
						Port: 1234,
						Connect: &structs.ServiceConnect{
							SidecarService: &structs.ServiceDefinition{
								Port: 2345,
								Checks: structs.CheckTypes{
									{
										TCP:      "127.0.0.1:2345",
										Interval: 10 * time.Second,
									},
								},
								Proxy: &structs.ConnectProxyConfig{
									Upstreams: structs.Upstreams{
										structs.Upstream{
											DestinationType: "service",
											DestinationName: "db",
											LocalBindPort:   7000,
										},
									},
								},
								Weights: &structs.Weights{
									Passing: 1,
									Warning: 1,
								},
							},
						},
						Weights: &structs.Weights{
							Passing: 1,
							Warning: 1,
						},
					},
				}
			},
		},
		{
			// This tests that we correct added the nested paths to arrays of objects
			// to the exceptions in patchSliceOfMaps in config.go (for service*s*)
			desc: "services.connect.sidecar_service with checks and upstreams",
			args: []string{
				`-data-dir=` + dataDir,
			},
			json: []string{`{
				  "services": [{
						"name": "web",
						"port": 1234,
						"connect": {
							"sidecar_service": {
								"port": 2345,
								"checks": [
									{
										"TCP": "127.0.0.1:2345",
										"Interval": "10s"
									}
								],
								"proxy": {
									"upstreams": [
										{
											"destination_name": "db",
											"local_bind_port": 7000
										}
									]
								}
							}
						}
					}]
				}`},
			hcl: []string{`
				services = [{
					name = "web"
					port = 1234
					connect {
						sidecar_service {
							port = 2345
							checks = [
								{
									tcp = "127.0.0.1:2345"
									interval = "10s"
								}
							]
							proxy {
								upstreams = [
									{
										destination_name = "db"
										local_bind_port = 7000
									},
								]
							}
						}
					}
				}]
			`},
			patch: func(rt *RuntimeConfig) {
				rt.DataDir = dataDir
				rt.Services = []*structs.ServiceDefinition{
					{
						Name: "web",
						Port: 1234,
						Connect: &structs.ServiceConnect{
							SidecarService: &structs.ServiceDefinition{
								Port: 2345,
								Checks: structs.CheckTypes{
									{
										TCP:      "127.0.0.1:2345",
										Interval: 10 * time.Second,
									},
								},
								Proxy: &structs.ConnectProxyConfig{
									Upstreams: structs.Upstreams{
										structs.Upstream{
											DestinationType: "service",
											DestinationName: "db",
											LocalBindPort:   7000,
										},
									},
								},
								Weights: &structs.Weights{
									Passing: 1,
									Warning: 1,
								},
							},
						},
						Weights: &structs.Weights{
							Passing: 1,
							Warning: 1,
						},
					},
				}
			},
		},
	}

	testConfig(t, tests, dataDir)
}

func testConfig(t *testing.T, tests []configTest, dataDir string) {
	for _, tt := range tests {
		for pass, format := range []string{"json", "hcl"} {
			// clean data dir before every test
			cleanDir(dataDir)

			// when we test only flags then there are no JSON or HCL
			// sources and we need to make only one pass over the
			// tests.
			flagsOnly := len(tt.json) == 0 && len(tt.hcl) == 0
			if flagsOnly && pass > 0 {
				continue
			}

			// json and hcl sources need to be in sync
			// to make sure we're generating the same config
			if len(tt.json) != len(tt.hcl) && !tt.skipformat {
				t.Fatal(tt.desc, ": JSON and HCL test case out of sync")
			}

			// select the source
			srcs, tails := tt.json, tt.jsontail
			if format == "hcl" {
				srcs, tails = tt.hcl, tt.hcltail
			}

			// If we're skipping a format and the current format is empty,
			// then skip it!
			if tt.skipformat && len(srcs) == 0 {
				continue
			}

			// build the description
			var desc []string
			if !flagsOnly {
				desc = append(desc, format)
			}
			if tt.desc != "" {
				desc = append(desc, tt.desc)
			}

			t.Run(strings.Join(desc, ":"), func(t *testing.T) {
				// first parse the flags
				flags := Flags{}
				fs := flag.NewFlagSet("", flag.ContinueOnError)
				AddFlags(fs, &flags)
				err := fs.Parse(tt.args)
				if err != nil {
					t.Fatalf("ParseFlags failed: %s", err)
				}
				flags.Args = fs.Args()

				if tt.pre != nil {
					tt.pre()
				}
				defer func() {
					if tt.post != nil {
						tt.post()
					}
				}()

				// Then create a builder with the flags.
				b, err := NewBuilder(flags)
				if err != nil {
					t.Fatal("NewBuilder", err)
				}

				// mock the hostname function unless a mock is provided
				b.Hostname = tt.hostname
				if b.Hostname == nil {
					b.Hostname = func() (string, error) { return "nodex", nil }
				}

				// mock the ip address detection
				privatev4 := tt.privatev4
				if privatev4 == nil {
					privatev4 = func() ([]*net.IPAddr, error) {
						return []*net.IPAddr{ipAddr("10.0.0.1")}, nil
					}
				}
				publicv6 := tt.publicv6
				if publicv6 == nil {
					publicv6 = func() ([]*net.IPAddr, error) {
						return []*net.IPAddr{ipAddr("dead:beef::1")}, nil
					}
				}
				b.GetPrivateIPv4 = privatev4
				b.GetPublicIPv6 = publicv6

				// read the source fragements
				for i, data := range srcs {
					b.Sources = append(b.Sources, Source{
						Name:   fmt.Sprintf("src-%d.%s", i, format),
						Format: format,
						Data:   data,
					})
				}
				for i, data := range tails {
					b.Tail = append(b.Tail, Source{
						Name:   fmt.Sprintf("tail-%d.%s", i, format),
						Format: format,
						Data:   data,
					})
				}

				// build/merge the config fragments
				rt, err := b.BuildAndValidate()
				if err == nil && tt.err != "" {
					t.Fatalf("got no error want %q", tt.err)
				}
				if err != nil && tt.err == "" {
					t.Fatalf("got error %s want nil", err)
				}
				if err == nil && tt.err != "" {
					t.Fatalf("got nil want error to contain %q", tt.err)
				}
				if err != nil && tt.err != "" && !strings.Contains(err.Error(), tt.err) {
					t.Fatalf("error %q does not contain %q", err.Error(), tt.err)
				}

				// check the warnings
				if !verify.Values(t, "warnings", b.Warnings, tt.warns) {
					t.FailNow()
				}

				// stop if we expected an error
				if tt.err != "" {
					return
				}

				// build a default configuration, then patch the fields we expect to change
				// and compare it with the generated configuration. Since the expected
				// runtime config has been validated we do not need to validate it again.
				x, err := NewBuilder(Flags{})
				if err != nil {
					t.Fatal(err)
				}
				x.Hostname = b.Hostname
				x.GetPrivateIPv4 = func() ([]*net.IPAddr, error) { return []*net.IPAddr{ipAddr("10.0.0.1")}, nil }
				x.GetPublicIPv6 = func() ([]*net.IPAddr, error) { return []*net.IPAddr{ipAddr("dead:beef::1")}, nil }
				patchedRT, err := x.Build()
				if err != nil {
					t.Fatalf("build default failed: %s", err)
				}
				if tt.patch != nil {
					tt.patch(&patchedRT)
				}
				// if err := x.Validate(wantRT); err != nil {
				// 	t.Fatalf("validate default failed: %s", err)
				// }
				if got, want := rt, patchedRT; !verify.Values(t, "", got, want) {
					t.FailNow()
				}
			})
		}
	}
}

// TestFullConfig tests the conversion from a fully populated JSON or
// HCL config file to a RuntimeConfig structure. All fields must be set
// to a unique non-zero value.
//
// To aid populating the fields the following bash functions can be used
// to generate random strings and ints:
//
//   random-int() { echo $RANDOM }
//   random-string() { base64 /dev/urandom | tr -d '/+' | fold -w ${1:-32} | head -n 1 }
//
// To generate a random string of length 8 run the following command in
// a terminal:
//
//   random-string 8
//
func TestFullConfig(t *testing.T) {
	dataDir := testutil.TempDir(t, "consul")
	defer os.RemoveAll(dataDir)

	flagSrc := []string{`-dev`}
	src := map[string]string{
		"json": `{
			"acl_agent_master_token": "furuQD0b",
			"acl_agent_token": "cOshLOQ2",
			"acl_datacenter": "m3urck3z",
			"acl_default_policy": "ArK3WIfE",
			"acl_down_policy": "vZXMfMP0",
			"acl_enforce_version_8": true,
			"acl_enable_key_list_policy": true,
			"acl_master_token": "C1Q1oIwh",
			"acl_replication_token": "LMmgy5dO",
			"acl_token": "O1El0wan",
			"acl_ttl": "18060s",
			"acl" : {
				"enabled" : true,
				"down_policy" : "03eb2aee",
				"default_policy" : "72c2e7a0",
				"enable_key_list_policy": false,
				"policy_ttl": "1123s",
				"token_ttl": "3321s",
				"enable_token_replication" : true,
				"tokens" : {
					"master" : "8a19ac27",
					"agent_master" : "64fd0e08",
					"replication" : "5795983a",
					"agent" : "bed2377c",
					"default" : "418fdff1"
				}
			},
			"addresses": {
				"dns": "93.95.95.81",
				"http": "83.39.91.39",
				"https": "95.17.17.19",
				"grpc": "32.31.61.91"
			},
			"advertise_addr": "17.99.29.16",
			"advertise_addr_wan": "78.63.37.19",
			"autopilot": {
				"cleanup_dead_servers": true,
				"disable_upgrade_migration": true,
				"last_contact_threshold": "12705s",
				"max_trailing_logs": 17849,
				"redundancy_zone_tag": "3IsufDJf",
				"server_stabilization_time": "23057s",
				"upgrade_version_tag": "W9pDwFAL"
			},
			"bind_addr": "16.99.34.17",
			"bootstrap": true,
			"bootstrap_expect": 53,
			"ca_file": "erA7T0PM",
			"ca_path": "mQEN1Mfp",
			"cert_file": "7s4QAzDk",
			"check": {
				"id": "fZaCAXww",
				"name": "OOM2eo0f",
				"notes": "zXzXI9Gt",
				"service_id": "L8G0QNmR",
				"token": "oo4BCTgJ",
				"status": "qLykAl5u",
				"args": ["f3BemRjy", "e5zgpef7"],
				"http": "29B93haH",
				"header": {
					"hBq0zn1q": [ "2a9o9ZKP", "vKwA5lR6" ],
					"f3r6xFtM": [ "RyuIdDWv", "QbxEcIUM" ]
				},
				"method": "Dou0nGT5",
				"tcp": "JY6fTTcw",
				"interval": "18714s",
				"docker_container_id": "qF66POS9",
				"shell": "sOnDy228",
				"tls_skip_verify": true,
				"timeout": "5954s",
				"ttl": "30044s",
				"deregister_critical_service_after": "13209s"
			},
			"checks": [
				{
					"id": "uAjE6m9Z",
					"name": "QsZRGpYr",
					"notes": "VJ7Sk4BY",
					"service_id": "lSulPcyz",
					"token": "toO59sh8",
					"status": "9RlWsXMV",
					"args": ["4BAJttck", "4D2NPtTQ"],
					"http": "dohLcyQ2",
					"header": {
						"ZBfTin3L": [ "1sDbEqYG", "lJGASsWK" ],
						"Ui0nU99X": [ "LMccm3Qe", "k5H5RggQ" ]
					},
					"method": "aldrIQ4l",
					"tcp": "RJQND605",
					"interval": "22164s",
					"docker_container_id": "ipgdFtjd",
					"shell": "qAeOYy0M",
					"tls_skip_verify": true,
					"timeout": "1813s",
					"ttl": "21743s",
					"deregister_critical_service_after": "14232s"
				},
				{
					"id": "Cqq95BhP",
					"name": "3qXpkS0i",
					"notes": "sb5qLTex",
					"service_id": "CmUUcRna",
					"token": "a3nQzHuy",
					"status": "irj26nf3",
					"args": ["9s526ogY", "gSlOHj1w"],
					"http": "yzhgsQ7Y",
					"header": {
						"zcqwA8dO": [ "qb1zx0DL", "sXCxPFsD" ],
						"qxvdnSE9": [ "6wBPUYdF", "YYh8wtSZ" ]
					},
					"method": "gLrztrNw",
					"tcp": "4jG5casb",
					"interval": "28767s",
					"docker_container_id": "THW6u7rL",
					"shell": "C1Zt3Zwh",
					"tls_skip_verify": true,
					"timeout": "18506s",
					"ttl": "31006s",
					"deregister_critical_service_after": "2366s"
				}
			],
			"check_update_interval": "16507s",
			"client_addr": "93.83.18.19",
			"connect": {
				"ca_provider": "consul",
				"ca_config": {
					"RotationPeriod": "90h",
					"LeafCertTTL": "1h"
				},
				"enabled": true,
				"proxy_defaults": {
					"exec_mode": "script",
					"daemon_command": ["consul", "connect", "proxy"],
					"script_command": ["proxyctl.sh"],
					"config": {
						"foo": "bar",
						"connect_timeout_ms": 1000,
						"pedantic_mode": true
					}
				}
			},
			"gossip_lan" : {
				"gossip_nodes": 6,
				"gossip_interval" : "25252s",
				"retransmit_mult" : 1234,
				"suspicion_mult"  : 1235,
				"probe_interval"  : "101ms",
				"probe_timeout"   : "102ms"
			},
			"gossip_wan" : {
				"gossip_nodes" : 2,
				"gossip_interval" : "6966s",
				"retransmit_mult" : 16384,
				"suspicion_mult"  : 16385,
				"probe_interval" : "103ms",
				"probe_timeout"  : "104ms"
			},
			"data_dir": "` + dataDir + `",
			"datacenter": "rzo029wg",
			"disable_anonymous_signature": true,
			"disable_coordinates": true,
			"disable_host_node_id": true,
			"disable_http_unprintable_char_filter": true,
			"disable_keyring_file": true,
			"disable_remote_exec": true,
			"disable_update_check": true,
			"discard_check_output": true,
			"discovery_max_stale": "5s",
			"domain": "7W1xXSqd",
			"dns_config": {
				"allow_stale": true,
				"a_record_limit": 29907,
				"disable_compression": true,
				"enable_truncate": true,
				"max_stale": "29685s",
				"node_ttl": "7084s",
				"only_passing": true,
				"recursor_timeout": "4427s",
				"service_ttl": {
					"*": "32030s"
				},
				"udp_answer_limit": 29909
			},
			"enable_acl_replication": true,
			"enable_agent_tls_for_checks": true,
			"enable_debug": true,
			"enable_script_checks": true,
			"enable_local_script_checks": true,
			"enable_syslog": true,
			"encrypt": "A4wELWqH",
			"encrypt_verify_incoming": true,
			"encrypt_verify_outgoing": true,
			"http_config": {
				"block_endpoints": [ "RBvAFcGD", "fWOWFznh" ],
				"response_headers": {
					"M6TKa9NP": "xjuxjOzQ",
					"JRCrHZed": "rl0mTx81"
				}
			},
			"key_file": "IEkkwgIA",
			"leave_on_terminate": true,
			"limits": {
				"rpc_rate": 12029.43,
				"rpc_max_burst": 44848
			},
			"log_level": "k1zo9Spt",
			"node_id": "AsUIlw99",
			"node_meta": {
				"5mgGQMBk": "mJLtVMSG",
				"A7ynFMJB": "0Nx6RGab"
			},
			"node_name": "otlLxGaI",
			"non_voting_server": true,
			"performance": {
				"leave_drain_time": "8265s",
				"raft_multiplier": 5,
				"rpc_hold_timeout": "15707s"
			},
			"pid_file": "43xN80Km",
			"ports": {
				"dns": 7001,
				"http": 7999,
				"https": 15127,
				"server": 3757,
				"grpc": 4881,
				"proxy_min_port": 2000,
				"proxy_max_port": 3000,
				"sidecar_min_port": 8888,
				"sidecar_max_port": 9999
			},
			"protocol": 30793,
			"primary_datacenter": "ejtmd43d",
			"raft_protocol": 19016,
			"raft_snapshot_threshold": 16384,
			"raft_snapshot_interval": "30s",
			"reconnect_timeout": "23739s",
			"reconnect_timeout_wan": "26694s",
			"recursors": [ "63.38.39.58", "92.49.18.18" ],
			"rejoin_after_leave": true,
			"retry_interval": "8067s",
			"retry_interval_wan": "28866s",
			"retry_join": [ "pbsSFY7U", "l0qLtWij" ],
			"retry_join_wan": [ "PFsR02Ye", "rJdQIhER" ],
			"retry_max": 913,
			"retry_max_wan": 23160,
			"segment": "BC2NhTDi",
			"segments": [
				{
					"name": "PExYMe2E",
					"bind": "36.73.36.19",
					"port": 38295,
					"rpc_listener": true,
					"advertise": "63.39.19.18"
				},
				{
					"name": "UzCvJgup",
					"bind": "37.58.38.19",
					"port": 39292,
					"rpc_listener": true,
					"advertise": "83.58.26.27"
				}
			],
			"serf_lan": "99.43.63.15",
			"serf_wan": "67.88.33.19",
			"server": true,
			"server_name": "Oerr9n1G",
			"service": {
				"id": "dLOXpSCI",
				"name": "o1ynPkp0",
				"meta": {
					"mymeta": "data"
				},
				"tags": ["nkwshvM5", "NTDWn3ek"],
				"address": "cOlSOhbp",
				"token": "msy7iWER",
				"port": 24237,
				"weights": {
					"passing": 100,
					"warning": 1
				},
				"enable_tag_override": true,
				"check": {
					"id": "RMi85Dv8",
					"name": "iehanzuq",
					"status": "rCvn53TH",
					"notes": "fti5lfF3",
					"args": ["16WRUmwS", "QWk7j7ae"],
					"http": "dl3Fgme3",
					"header": {
						"rjm4DEd3": ["2m3m2Fls"],
						"l4HwQ112": ["fk56MNlo", "dhLK56aZ"]
					},
					"method": "9afLm3Mj",
					"tcp": "fjiLFqVd",
					"interval": "23926s",
					"docker_container_id": "dO5TtRHk",
					"shell": "e6q2ttES",
					"tls_skip_verify": true,
					"timeout": "38483s",
					"ttl": "10943s",
					"deregister_critical_service_after": "68787s"
				},
				"checks": [
					{
						"id": "Zv99e9Ka",
						"name": "sgV4F7Pk",
						"notes": "yP5nKbW0",
						"status": "7oLMEyfu",
						"args": ["5wEZtZpv", "0Ihyk8cS"],
						"http": "KyDjGY9H",
						"header": {
							"gv5qefTz": [ "5Olo2pMG", "PvvKWQU5" ],
							"SHOVq1Vv": [ "jntFhyym", "GYJh32pp" ]
						},
						"method": "T66MFBfR",
						"tcp": "bNnNfx2A",
						"interval": "22224s",
						"docker_container_id": "ipgdFtjd",
						"shell": "omVZq7Sz",
						"tls_skip_verify": true,
						"timeout": "18913s",
						"ttl": "44743s",
						"deregister_critical_service_after": "8482s"
					},
					{
						"id": "G79O6Mpr",
						"name": "IEqrzrsd",
						"notes": "SVqApqeM",
						"status": "XXkVoZXt",
						"args": ["wD05Bvao", "rLYB7kQC"],
						"http": "kyICZsn8",
						"header": {
							"4ebP5vL4": [ "G20SrL5Q", "DwPKlMbo" ],
							"p2UI34Qz": [ "UsG1D0Qh", "NHhRiB6s" ]
						},
						"method": "ciYHWors",
						"tcp": "FfvCwlqH",
						"interval": "12356s",
						"docker_container_id": "HBndBU6R",
						"shell": "hVI33JjA",
						"tls_skip_verify": true,
						"timeout": "38282s",
						"ttl": "1181s",
						"deregister_critical_service_after": "4992s"
					}
				],
				"connect": {
					"native": true
				}
			},
			"services": [
				{
					"id": "wI1dzxS4",
					"name": "7IszXMQ1",
					"tags": ["0Zwg8l6v", "zebELdN5"],
					"address": "9RhqPSPB",
					"token": "myjKJkWH",
					"port": 72219,
					"enable_tag_override": true,
					"check": {
						"id": "qmfeO5if",
						"name": "atDGP7n5",
						"status": "pDQKEhWL",
						"notes": "Yt8EDLev",
						"args": ["81EDZLPa", "bPY5X8xd"],
						"http": "qzHYvmJO",
						"header": {
							"UkpmZ3a3": ["2dfzXuxZ"],
							"cVFpko4u": ["gGqdEB6k", "9LsRo22u"]
						},
						"method": "X5DrovFc",
						"tcp": "ICbxkpSF",
						"interval": "24392s",
						"docker_container_id": "ZKXr68Yb",
						"shell": "CEfzx0Fo",
						"tls_skip_verify": true,
						"timeout": "38333s",
						"ttl": "57201s",
						"deregister_critical_service_after": "44214s"
					},
					"connect": {
						"sidecar_service": {}
					}
				},
				{
					"id": "MRHVMZuD",
					"name": "6L6BVfgH",
					"tags": ["7Ale4y6o", "PMBW08hy"],
					"address": "R6H6g8h0",
					"token": "ZgY8gjMI",
					"port": 38292,
					"weights": {
						"passing": 1979,
						"warning": 6
					},
					"enable_tag_override": true,
					"checks": [
						{
							"id": "GTti9hCo",
							"name": "9OOS93ne",
							"notes": "CQy86DH0",
							"status": "P0SWDvrk",
							"args": ["EXvkYIuG", "BATOyt6h"],
							"http": "u97ByEiW",
							"header": {
								"MUlReo8L": [ "AUZG7wHG", "gsN0Dc2N" ],
								"1UJXjVrT": [ "OJgxzTfk", "xZZrFsq7" ]
							},
							"method": "5wkAxCUE",
							"tcp": "MN3oA9D2",
							"interval": "32718s",
							"docker_container_id": "cU15LMet",
							"shell": "nEz9qz2l",
							"tls_skip_verify": true,
							"timeout": "34738s",
							"ttl": "22773s",
							"deregister_critical_service_after": "84282s"
						},
						{
							"id": "UHsDeLxG",
							"name": "PQSaPWlT",
							"notes": "jKChDOdl",
							"status": "5qFz6OZn",
							"args": ["NMtYWlT9", "vj74JXsm"],
							"http": "1LBDJhw4",
							"header": {
								"cXPmnv1M": [ "imDqfaBx", "NFxZ1bQe" ],
								"vr7wY7CS": [ "EtCoNPPL", "9vAarJ5s" ]
							},
							"method": "wzByP903",
							"tcp": "2exjZIGE",
							"interval": "5656s",
							"docker_container_id": "5tDBWpfA",
							"shell": "rlTpLM8s",
							"tls_skip_verify": true,
							"timeout": "4868s",
							"ttl": "11222s",
							"deregister_critical_service_after": "68482s"
						}
					],
					"connect": {
						"proxy": {
							"exec_mode": "daemon",
							"command": ["awesome-proxy"],
							"config": {
								"foo": "qux"
							}
						}
					}
				},
				{
					"id": "Kh81CPF6",
					"kind": "connect-proxy",
					"name": "Kh81CPF6-proxy",
					"port": 31471,
					"proxy": {
						"config": {
								"cedGGtZf": "pWrUNiWw"
						},
						"destination_service_id": "6L6BVfgH-id",
						"destination_service_name": "6L6BVfgH",
						"local_service_address": "127.0.0.2",
						"local_service_port": 23759,
						"upstreams": [
							{
								"destination_name": "KPtAj2cb",
								"local_bind_port": 4051,
								"config": {
									"kzRnZOyd": "nUNKoL8H"
								}
							},
							{
								"destination_name": "KSd8HsRl",
								"destination_namespace": "9nakw0td",
								"destination_type": "prepared_query",
								"local_bind_address": "127.24.88.0",
								"local_bind_port": 11884
							}
						]
					}
				}
			],
			"session_ttl_min": "26627s",
			"skip_leave_on_interrupt": true,
			"start_join": [ "LR3hGDoG", "MwVpZ4Up" ],
			"start_join_wan": [ "EbFSc3nA", "kwXTh623" ],
			"syslog_facility": "hHv79Uia",
			"tagged_addresses": {
				"7MYgHrYH": "dALJAhLD",
				"h6DdBy6K": "ebrr9zZ8"
			},
			"telemetry": {
				"circonus_api_app": "p4QOTe9j",
				"circonus_api_token": "E3j35V23",
				"circonus_api_url": "mEMjHpGg",
				"circonus_broker_id": "BHlxUhed",
				"circonus_broker_select_tag": "13xy1gHm",
				"circonus_check_display_name": "DRSlQR6n",
				"circonus_check_force_metric_activation": "Ua5FGVYf",
				"circonus_check_id": "kGorutad",
				"circonus_check_instance_id": "rwoOL6R4",
				"circonus_check_search_tag": "ovT4hT4f",
				"circonus_check_tags": "prvO4uBl",
				"circonus_submission_interval": "DolzaflP",
				"circonus_submission_url": "gTcbS93G",
				"disable_hostname": true,
				"dogstatsd_addr": "0wSndumK",
				"dogstatsd_tags": [ "3N81zSUB","Xtj8AnXZ" ],
				"filter_default": true,
				"prefix_filter": [ "+oJotS8XJ","-cazlEhGn" ],
				"metrics_prefix": "ftO6DySn",
				"prometheus_retention_time": "15s",
				"statsd_address": "drce87cy",
				"statsite_address": "HpFwKB8R"
			},
			"tls_cipher_suites": "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
			"tls_min_version": "pAOWafkR",
			"tls_prefer_server_cipher_suites": true,
			"translate_wan_addrs": true,
			"ui": true,
			"ui_dir": "11IFzAUn",
			"unix_sockets": {
				"group": "8pFodrV8",
				"mode": "E8sAwOv4",
				"user": "E0nB1DwA"
			},
			"verify_incoming": true,
			"verify_incoming_https": true,
			"verify_incoming_rpc": true,
			"verify_outgoing": true,
			"verify_server_hostname": true,
			"watches": [
				{
					"type": "key",
					"datacenter": "GyE6jpeW",
					"key": "j9lF1Tve",
					"handler": "90N7S4LN"
				}, {
					"type": "keyprefix",
					"datacenter": "fYrl3F5d",
					"key": "sl3Dffu7",
					"args": ["dltjDJ2a", "flEa7C2d"]
				}
			]
		}`,
		"hcl": `
			acl_agent_master_token = "furuQD0b"
			acl_agent_token = "cOshLOQ2"
			acl_datacenter = "m3urck3z"
			acl_default_policy = "ArK3WIfE"
			acl_down_policy = "vZXMfMP0"
			acl_enforce_version_8 = true
		        acl_enable_key_list_policy = true
			acl_master_token = "C1Q1oIwh"
			acl_replication_token = "LMmgy5dO"
			acl_token = "O1El0wan"
			acl_ttl = "18060s"
			acl = {
				enabled = true
				down_policy = "03eb2aee"
				default_policy = "72c2e7a0"
				enable_key_list_policy = false
				policy_ttl = "1123s"
				token_ttl = "3321s"
				enable_token_replication = true
				tokens = {
					master = "8a19ac27",
					agent_master = "64fd0e08",
					replication = "5795983a",
					agent = "bed2377c",
					default = "418fdff1"
				}
			}
			addresses = {
				dns = "93.95.95.81"
				http = "83.39.91.39"
				https = "95.17.17.19"
				grpc = "32.31.61.91"
			}
			advertise_addr = "17.99.29.16"
			advertise_addr_wan = "78.63.37.19"
			autopilot = {
				cleanup_dead_servers = true
				disable_upgrade_migration = true
				last_contact_threshold = "12705s"
				max_trailing_logs = 17849
				redundancy_zone_tag = "3IsufDJf"
				server_stabilization_time = "23057s"
				upgrade_version_tag = "W9pDwFAL"
			}
			bind_addr = "16.99.34.17"
			bootstrap = true
			bootstrap_expect = 53
			ca_file = "erA7T0PM"
			ca_path = "mQEN1Mfp"
			cert_file = "7s4QAzDk"
			check = {
				id = "fZaCAXww"
				name = "OOM2eo0f"
				notes = "zXzXI9Gt"
				service_id = "L8G0QNmR"
				token = "oo4BCTgJ"
				status = "qLykAl5u"
				args = ["f3BemRjy", "e5zgpef7"]
				http = "29B93haH"
				header = {
					hBq0zn1q = [ "2a9o9ZKP", "vKwA5lR6" ]
					f3r6xFtM = [ "RyuIdDWv", "QbxEcIUM" ]
				}
				method = "Dou0nGT5"
				tcp = "JY6fTTcw"
				interval = "18714s"
				docker_container_id = "qF66POS9"
				shell = "sOnDy228"
				tls_skip_verify = true
				timeout = "5954s"
				ttl = "30044s"
				deregister_critical_service_after = "13209s"
			},
			checks = [
				{
					id = "uAjE6m9Z"
					name = "QsZRGpYr"
					notes = "VJ7Sk4BY"
					service_id = "lSulPcyz"
					token = "toO59sh8"
					status = "9RlWsXMV"
					args = ["4BAJttck", "4D2NPtTQ"]
					http = "dohLcyQ2"
					header = {
						"ZBfTin3L" = [ "1sDbEqYG", "lJGASsWK" ]
						"Ui0nU99X" = [ "LMccm3Qe", "k5H5RggQ" ]
					}
					method = "aldrIQ4l"
					tcp = "RJQND605"
					interval = "22164s"
					docker_container_id = "ipgdFtjd"
					shell = "qAeOYy0M"
					tls_skip_verify = true
					timeout = "1813s"
					ttl = "21743s"
					deregister_critical_service_after = "14232s"
				},
				{
					id = "Cqq95BhP"
					name = "3qXpkS0i"
					notes = "sb5qLTex"
					service_id = "CmUUcRna"
					token = "a3nQzHuy"
					status = "irj26nf3"
					args = ["9s526ogY", "gSlOHj1w"]
					http = "yzhgsQ7Y"
					header = {
						"zcqwA8dO" = [ "qb1zx0DL", "sXCxPFsD" ]
						"qxvdnSE9" = [ "6wBPUYdF", "YYh8wtSZ" ]
					}
					method = "gLrztrNw"
					tcp = "4jG5casb"
					interval = "28767s"
					docker_container_id = "THW6u7rL"
					shell = "C1Zt3Zwh"
					tls_skip_verify = true
					timeout = "18506s"
					ttl = "31006s"
					deregister_critical_service_after = "2366s"
				}
			]
			check_update_interval = "16507s"
			client_addr = "93.83.18.19"
			connect {
				ca_provider = "consul"
				ca_config {
					rotation_period = "90h"
					leaf_cert_ttl = "1h"
				}
				enabled = true
				proxy_defaults {
					exec_mode = "script"
					daemon_command = ["consul", "connect", "proxy"]
					script_command = ["proxyctl.sh"]
					config = {
						foo = "bar"
						# hack float since json parses numbers as float and we have to
						# assert against the same thing
						connect_timeout_ms = 1000.0
						pedantic_mode = true
					}
				}
			}
			gossip_lan {
				gossip_nodes    = 6
				gossip_interval = "25252s"
				retransmit_mult = 1234
				suspicion_mult  = 1235
				probe_interval  = "101ms"
				probe_timeout   = "102ms"
			}
			gossip_wan {
				gossip_nodes    = 2
				gossip_interval = "6966s"
				retransmit_mult = 16384
				suspicion_mult  = 16385
				probe_interval  = "103ms"
				probe_timeout   = "104ms"
			}
			data_dir = "` + dataDir + `"
			datacenter = "rzo029wg"
			disable_anonymous_signature = true
			disable_coordinates = true
			disable_host_node_id = true
			disable_http_unprintable_char_filter = true
			disable_keyring_file = true
			disable_remote_exec = true
			disable_update_check = true
			discard_check_output = true
			discovery_max_stale = "5s"
			domain = "7W1xXSqd"
			dns_config {
				allow_stale = true
				a_record_limit = 29907
				disable_compression = true
				enable_truncate = true
				max_stale = "29685s"
				node_ttl = "7084s"
				only_passing = true
				recursor_timeout = "4427s"
				service_ttl = {
					"*" = "32030s"
				}
				udp_answer_limit = 29909
			}
			enable_acl_replication = true
			enable_agent_tls_for_checks = true
			enable_debug = true
			enable_script_checks = true
			enable_local_script_checks = true
			enable_syslog = true
			encrypt = "A4wELWqH"
			encrypt_verify_incoming = true
			encrypt_verify_outgoing = true
			http_config {
				block_endpoints = [ "RBvAFcGD", "fWOWFznh" ]
				response_headers = {
					"M6TKa9NP" = "xjuxjOzQ"
					"JRCrHZed" = "rl0mTx81"
				}
			}
			key_file = "IEkkwgIA"
			leave_on_terminate = true
			limits {
				rpc_rate = 12029.43
				rpc_max_burst = 44848
			}
			log_level = "k1zo9Spt"
			node_id = "AsUIlw99"
			node_meta {
				"5mgGQMBk" = "mJLtVMSG"
				"A7ynFMJB" = "0Nx6RGab"
			}
			node_name = "otlLxGaI"
			non_voting_server = true
			performance {
				leave_drain_time = "8265s"
				raft_multiplier = 5
				rpc_hold_timeout = "15707s"
			}
			pid_file = "43xN80Km"
			ports {
				dns = 7001,
				http = 7999,
				https = 15127
				server = 3757
				grpc = 4881
				proxy_min_port = 2000
				proxy_max_port = 3000
				sidecar_min_port = 8888
				sidecar_max_port = 9999
			}
			protocol = 30793
			primary_datacenter = "ejtmd43d"
			raft_protocol = 19016
			raft_snapshot_threshold = 16384
			raft_snapshot_interval = "30s"
			reconnect_timeout = "23739s"
			reconnect_timeout_wan = "26694s"
			recursors = [ "63.38.39.58", "92.49.18.18" ]
			rejoin_after_leave = true
			retry_interval = "8067s"
			retry_interval_wan = "28866s"
			retry_join = [ "pbsSFY7U", "l0qLtWij" ]
			retry_join_wan = [ "PFsR02Ye", "rJdQIhER" ]
			retry_max = 913
			retry_max_wan = 23160
			segment = "BC2NhTDi"
			segments = [
				{
					name = "PExYMe2E"
					bind = "36.73.36.19"
					port = 38295
					rpc_listener = true
					advertise = "63.39.19.18"
				},
				{
					name = "UzCvJgup"
					bind = "37.58.38.19"
					port = 39292
					rpc_listener = true
					advertise = "83.58.26.27"
				}
			]
			serf_lan = "99.43.63.15"
			serf_wan = "67.88.33.19"
			server = true
			server_name = "Oerr9n1G"
			service = {
				id = "dLOXpSCI"
				name = "o1ynPkp0"
				meta = {
					mymeta = "data"
				}
				tags = ["nkwshvM5", "NTDWn3ek"]
				address = "cOlSOhbp"
				token = "msy7iWER"
				port = 24237
				weights = {
					passing = 100,
					warning = 1
				}
				enable_tag_override = true
				check = {
					id = "RMi85Dv8"
					name = "iehanzuq"
					status = "rCvn53TH"
					notes = "fti5lfF3"
					args = ["16WRUmwS", "QWk7j7ae"]
					http = "dl3Fgme3"
					header = {
						rjm4DEd3 = [ "2m3m2Fls" ]
						l4HwQ112 = [ "fk56MNlo", "dhLK56aZ" ]
					}
					method = "9afLm3Mj"
					tcp = "fjiLFqVd"
					interval = "23926s"
					docker_container_id = "dO5TtRHk"
					shell = "e6q2ttES"
					tls_skip_verify = true
					timeout = "38483s"
					ttl = "10943s"
					deregister_critical_service_after = "68787s"
				}
				checks = [
					{
						id = "Zv99e9Ka"
						name = "sgV4F7Pk"
						notes = "yP5nKbW0"
						status = "7oLMEyfu"
						args = ["5wEZtZpv", "0Ihyk8cS"]
						http = "KyDjGY9H"
						header = {
							"gv5qefTz" = [ "5Olo2pMG", "PvvKWQU5" ]
							"SHOVq1Vv" = [ "jntFhyym", "GYJh32pp" ]
						}
						method = "T66MFBfR"
						tcp = "bNnNfx2A"
						interval = "22224s"
						docker_container_id = "ipgdFtjd"
						shell = "omVZq7Sz"
						tls_skip_verify = true
						timeout = "18913s"
						ttl = "44743s"
						deregister_critical_service_after = "8482s"
					},
					{
						id = "G79O6Mpr"
						name = "IEqrzrsd"
						notes = "SVqApqeM"
						status = "XXkVoZXt"
						args = ["wD05Bvao", "rLYB7kQC"]
						http = "kyICZsn8"
						header = {
							"4ebP5vL4" = [ "G20SrL5Q", "DwPKlMbo" ]
							"p2UI34Qz" = [ "UsG1D0Qh", "NHhRiB6s" ]
						}
						method = "ciYHWors"
						tcp = "FfvCwlqH"
						interval = "12356s"
						docker_container_id = "HBndBU6R"
						shell = "hVI33JjA"
						tls_skip_verify = true
						timeout = "38282s"
						ttl = "1181s"
						deregister_critical_service_after = "4992s"
					}
				]
				connect {
					native = true
				}
			}
			services = [
				{
					id = "wI1dzxS4"
					name = "7IszXMQ1"
					tags = ["0Zwg8l6v", "zebELdN5"]
					address = "9RhqPSPB"
					token = "myjKJkWH"
					port = 72219
					enable_tag_override = true
					check = {
						id = "qmfeO5if"
						name = "atDGP7n5"
						status = "pDQKEhWL"
						notes = "Yt8EDLev"
						args = ["81EDZLPa", "bPY5X8xd"]
						http = "qzHYvmJO"
						header = {
							UkpmZ3a3 = [ "2dfzXuxZ" ]
							cVFpko4u = [ "gGqdEB6k", "9LsRo22u" ]
						}
						method = "X5DrovFc"
						tcp = "ICbxkpSF"
						interval = "24392s"
						docker_container_id = "ZKXr68Yb"
						shell = "CEfzx0Fo"
						tls_skip_verify = true
						timeout = "38333s"
						ttl = "57201s"
						deregister_critical_service_after = "44214s"
					}
					connect {
						sidecar_service {}
					}
				},
				{
					id = "MRHVMZuD"
					name = "6L6BVfgH"
					tags = ["7Ale4y6o", "PMBW08hy"]
					address = "R6H6g8h0"
					token = "ZgY8gjMI"
					port = 38292
					weights = {
						passing = 1979,
						warning = 6
					}
					enable_tag_override = true
					checks = [
						{
							id = "GTti9hCo"
							name = "9OOS93ne"
							notes = "CQy86DH0"
							status = "P0SWDvrk"
							args = ["EXvkYIuG", "BATOyt6h"]
							http = "u97ByEiW"
							header = {
								"MUlReo8L" = [ "AUZG7wHG", "gsN0Dc2N" ]
								"1UJXjVrT" = [ "OJgxzTfk", "xZZrFsq7" ]
							}
							method = "5wkAxCUE"
							tcp = "MN3oA9D2"
							interval = "32718s"
							docker_container_id = "cU15LMet"
							shell = "nEz9qz2l"
							tls_skip_verify = true
							timeout = "34738s"
							ttl = "22773s"
							deregister_critical_service_after = "84282s"
						},
						{
							id = "UHsDeLxG"
							name = "PQSaPWlT"
							notes = "jKChDOdl"
							status = "5qFz6OZn"
							args = ["NMtYWlT9", "vj74JXsm"]
							http = "1LBDJhw4"
							header = {
								"cXPmnv1M" = [ "imDqfaBx", "NFxZ1bQe" ],
								"vr7wY7CS" = [ "EtCoNPPL", "9vAarJ5s" ]
							}
							method = "wzByP903"
							tcp = "2exjZIGE"
							interval = "5656s"
							docker_container_id = "5tDBWpfA"
							shell = "rlTpLM8s"
							tls_skip_verify = true
							timeout = "4868s"
							ttl = "11222s"
							deregister_critical_service_after = "68482s"
						}
					]
					connect {
						proxy {
							exec_mode = "daemon"
							command = ["awesome-proxy"]
							config = {
								foo = "qux"
							}
						}
					}
				},
				{
					id = "Kh81CPF6"
					name = "Kh81CPF6-proxy"
					port = 31471
					kind = "connect-proxy"
					proxy {
						destination_service_name = "6L6BVfgH"
						destination_service_id = "6L6BVfgH-id"
						local_service_address = "127.0.0.2"
						local_service_port = 23759
						config {
							cedGGtZf = "pWrUNiWw"
						}
						upstreams = [
							{
								destination_name = "KPtAj2cb"
								local_bind_port = 4051
								config {
									kzRnZOyd = "nUNKoL8H"
								}
							},
							{
								destination_type = "prepared_query"
								destination_namespace = "9nakw0td"
								destination_name = "KSd8HsRl"
								local_bind_port = 11884
								local_bind_address = "127.24.88.0"
							},
						]
					}
				}
			]
			session_ttl_min = "26627s"
			skip_leave_on_interrupt = true
			start_join = [ "LR3hGDoG", "MwVpZ4Up" ]
			start_join_wan = [ "EbFSc3nA", "kwXTh623" ]
			syslog_facility = "hHv79Uia"
			tagged_addresses = {
				"7MYgHrYH" = "dALJAhLD"
				"h6DdBy6K" = "ebrr9zZ8"
			}
			telemetry {
				circonus_api_app = "p4QOTe9j"
				circonus_api_token = "E3j35V23"
				circonus_api_url = "mEMjHpGg"
				circonus_broker_id = "BHlxUhed"
				circonus_broker_select_tag = "13xy1gHm"
				circonus_check_display_name = "DRSlQR6n"
				circonus_check_force_metric_activation = "Ua5FGVYf"
				circonus_check_id = "kGorutad"
				circonus_check_instance_id = "rwoOL6R4"
				circonus_check_search_tag = "ovT4hT4f"
				circonus_check_tags = "prvO4uBl"
				circonus_submission_interval = "DolzaflP"
				circonus_submission_url = "gTcbS93G"
				disable_hostname = true
				dogstatsd_addr = "0wSndumK"
				dogstatsd_tags = [ "3N81zSUB","Xtj8AnXZ" ]
				filter_default = true
				prefix_filter = [ "+oJotS8XJ","-cazlEhGn" ]
				metrics_prefix = "ftO6DySn"
				prometheus_retention_time = "15s"
				statsd_address = "drce87cy"
				statsite_address = "HpFwKB8R"
			}
			tls_cipher_suites = "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
			tls_min_version = "pAOWafkR"
			tls_prefer_server_cipher_suites = true
			translate_wan_addrs = true
			ui = true
			ui_dir = "11IFzAUn"
			unix_sockets = {
				group = "8pFodrV8"
				mode = "E8sAwOv4"
				user = "E0nB1DwA"
			}
			verify_incoming = true
			verify_incoming_https = true
			verify_incoming_rpc = true
			verify_outgoing = true
			verify_server_hostname = true
			watches = [{
				type = "key"
				datacenter = "GyE6jpeW"
				key = "j9lF1Tve"
				handler = "90N7S4LN"
			}, {
				type = "keyprefix"
				datacenter = "fYrl3F5d"
				key = "sl3Dffu7"
				args = ["dltjDJ2a", "flEa7C2d"]
			}]
		`}

	tail := map[string][]Source{
		"json": []Source{
			{
				Name:   "tail.non-user.json",
				Format: "json",
				Data: `
				{
					"acl_disabled_ttl": "957s",
					"acl" : {
						"disabled_ttl" : "957s"
					},
					"ae_interval": "10003s",
					"check_deregister_interval_min": "27870s",
					"check_reap_interval": "10662s",
					"discovery_max_stale": "5s",
					"segment_limit": 24705,
					"segment_name_limit": 27046,
					"sync_coordinate_interval_min": "27983s",
					"sync_coordinate_rate_target": 137.81
				}`,
			},
			{
				Name:   "tail.consul.json",
				Format: "json",
				Data: `
				{
					"consul": {
						"coordinate": {
							"update_batch_size": 9244,
							"update_max_batches": 15164,
							"update_period": "25093s"
						},
						"raft": {
							"election_timeout": "31947s",
							"heartbeat_timeout": "25699s",
							"leader_lease_timeout": "15351s"
						},
						"server": {
							"health_interval": "17455s"
						}
					}
				}`,
			},
		},
		"hcl": []Source{
			{
				Name:   "tail.non-user.hcl",
				Format: "hcl",
				Data: `
					acl_disabled_ttl = "957s"
					acl = {
						disabled_ttl = "957s"
					}
					ae_interval = "10003s"
					check_deregister_interval_min = "27870s"
					check_reap_interval = "10662s"
					discovery_max_stale = "5s"
					segment_limit = 24705
					segment_name_limit = 27046
					sync_coordinate_interval_min = "27983s"
					sync_coordinate_rate_target = 137.81
				`,
			},
			{
				Name:   "tail.consul.hcl",
				Format: "hcl",
				Data: `
					consul = {
						coordinate = {
							update_batch_size = 9244
							update_max_batches = 15164
							update_period = "25093s"
						}
						raft = {
							election_timeout = "31947s"
							heartbeat_timeout = "25699s"
							leader_lease_timeout = "15351s"
						}
						server = {
							health_interval = "17455s"
						}
					}
				`,
			},
		},
	}

	want := RuntimeConfig{
		// non-user configurable values
		ACLDisabledTTL:             957 * time.Second,
		AEInterval:                 10003 * time.Second,
		CheckDeregisterIntervalMin: 27870 * time.Second,
		CheckReapInterval:          10662 * time.Second,
		SegmentLimit:               24705,
		SegmentNameLimit:           27046,
		SyncCoordinateIntervalMin:  27983 * time.Second,
		SyncCoordinateRateTarget:   137.81,

		Revision:          "JNtPSav3",
		Version:           "R909Hblt",
		VersionPrerelease: "ZT1JOQLn",

		// consul configuration
		ConsulCoordinateUpdateBatchSize:  9244,
		ConsulCoordinateUpdateMaxBatches: 15164,
		ConsulCoordinateUpdatePeriod:     25093 * time.Second,
		ConsulRaftElectionTimeout:        5 * 31947 * time.Second,
		ConsulRaftHeartbeatTimeout:       5 * 25699 * time.Second,
		ConsulRaftLeaderLeaseTimeout:     5 * 15351 * time.Second,
		GossipLANGossipInterval:          25252 * time.Second,
		GossipLANGossipNodes:             6,
		GossipLANProbeInterval:           101 * time.Millisecond,
		GossipLANProbeTimeout:            102 * time.Millisecond,
		GossipLANSuspicionMult:           1235,
		GossipLANRetransmitMult:          1234,
		GossipWANGossipInterval:          6966 * time.Second,
		GossipWANGossipNodes:             2,
		GossipWANProbeInterval:           103 * time.Millisecond,
		GossipWANProbeTimeout:            104 * time.Millisecond,
		GossipWANSuspicionMult:           16385,
		GossipWANRetransmitMult:          16384,
		ConsulServerHealthInterval:       17455 * time.Second,

		// user configurable values

		ACLAgentMasterToken:              "64fd0e08",
		ACLAgentToken:                    "bed2377c",
		ACLsEnabled:                      true,
		ACLDatacenter:                    "ejtmd43d",
		ACLDefaultPolicy:                 "72c2e7a0",
		ACLDownPolicy:                    "03eb2aee",
		ACLEnforceVersion8:               true,
		ACLEnableKeyListPolicy:           false,
		ACLMasterToken:                   "8a19ac27",
		ACLReplicationToken:              "5795983a",
		ACLTokenTTL:                      3321 * time.Second,
		ACLPolicyTTL:                     1123 * time.Second,
		ACLToken:                         "418fdff1",
		ACLTokenReplication:              true,
		AdvertiseAddrLAN:                 ipAddr("17.99.29.16"),
		AdvertiseAddrWAN:                 ipAddr("78.63.37.19"),
		AutopilotCleanupDeadServers:      true,
		AutopilotDisableUpgradeMigration: true,
		AutopilotLastContactThreshold:    12705 * time.Second,
		AutopilotMaxTrailingLogs:         17849,
		AutopilotRedundancyZoneTag:       "3IsufDJf",
		AutopilotServerStabilizationTime: 23057 * time.Second,
		AutopilotUpgradeVersionTag:       "W9pDwFAL",
		BindAddr:                         ipAddr("16.99.34.17"),
		Bootstrap:                        true,
		BootstrapExpect:                  53,
		CAFile:                           "erA7T0PM",
		CAPath:                           "mQEN1Mfp",
		CertFile:                         "7s4QAzDk",
		Checks: []*structs.CheckDefinition{
			&structs.CheckDefinition{
				ID:         "uAjE6m9Z",
				Name:       "QsZRGpYr",
				Notes:      "VJ7Sk4BY",
				ServiceID:  "lSulPcyz",
				Token:      "toO59sh8",
				Status:     "9RlWsXMV",
				ScriptArgs: []string{"4BAJttck", "4D2NPtTQ"},
				HTTP:       "dohLcyQ2",
				Header: map[string][]string{
					"ZBfTin3L": []string{"1sDbEqYG", "lJGASsWK"},
					"Ui0nU99X": []string{"LMccm3Qe", "k5H5RggQ"},
				},
				Method:                         "aldrIQ4l",
				TCP:                            "RJQND605",
				Interval:                       22164 * time.Second,
				DockerContainerID:              "ipgdFtjd",
				Shell:                          "qAeOYy0M",
				TLSSkipVerify:                  true,
				Timeout:                        1813 * time.Second,
				TTL:                            21743 * time.Second,
				DeregisterCriticalServiceAfter: 14232 * time.Second,
			},
			&structs.CheckDefinition{
				ID:         "Cqq95BhP",
				Name:       "3qXpkS0i",
				Notes:      "sb5qLTex",
				ServiceID:  "CmUUcRna",
				Token:      "a3nQzHuy",
				Status:     "irj26nf3",
				ScriptArgs: []string{"9s526ogY", "gSlOHj1w"},
				HTTP:       "yzhgsQ7Y",
				Header: map[string][]string{
					"zcqwA8dO": []string{"qb1zx0DL", "sXCxPFsD"},
					"qxvdnSE9": []string{"6wBPUYdF", "YYh8wtSZ"},
				},
				Method:                         "gLrztrNw",
				TCP:                            "4jG5casb",
				Interval:                       28767 * time.Second,
				DockerContainerID:              "THW6u7rL",
				Shell:                          "C1Zt3Zwh",
				TLSSkipVerify:                  true,
				Timeout:                        18506 * time.Second,
				TTL:                            31006 * time.Second,
				DeregisterCriticalServiceAfter: 2366 * time.Second,
			},
			&structs.CheckDefinition{
				ID:         "fZaCAXww",
				Name:       "OOM2eo0f",
				Notes:      "zXzXI9Gt",
				ServiceID:  "L8G0QNmR",
				Token:      "oo4BCTgJ",
				Status:     "qLykAl5u",
				ScriptArgs: []string{"f3BemRjy", "e5zgpef7"},
				HTTP:       "29B93haH",
				Header: map[string][]string{
					"hBq0zn1q": {"2a9o9ZKP", "vKwA5lR6"},
					"f3r6xFtM": {"RyuIdDWv", "QbxEcIUM"},
				},
				Method:                         "Dou0nGT5",
				TCP:                            "JY6fTTcw",
				Interval:                       18714 * time.Second,
				DockerContainerID:              "qF66POS9",
				Shell:                          "sOnDy228",
				TLSSkipVerify:                  true,
				Timeout:                        5954 * time.Second,
				TTL:                            30044 * time.Second,
				DeregisterCriticalServiceAfter: 13209 * time.Second,
			},
		},
		CheckUpdateInterval:     16507 * time.Second,
		ClientAddrs:             []*net.IPAddr{ipAddr("93.83.18.19")},
		ConnectEnabled:          true,
		ConnectProxyBindMinPort: 2000,
		ConnectProxyBindMaxPort: 3000,
		ConnectSidecarMinPort:   8888,
		ConnectSidecarMaxPort:   9999,
		ConnectCAProvider:       "consul",
		ConnectCAConfig: map[string]interface{}{
			"RotationPeriod": "90h",
			"LeafCertTTL":    "1h",
		},
		ConnectProxyAllowManagedRoot:            false,
		ConnectProxyAllowManagedAPIRegistration: false,
		ConnectProxyDefaultExecMode:             "script",
		ConnectProxyDefaultDaemonCommand:        []string{"consul", "connect", "proxy"},
		ConnectProxyDefaultScriptCommand:        []string{"proxyctl.sh"},
		ConnectProxyDefaultConfig: map[string]interface{}{
			"foo":                "bar",
			"connect_timeout_ms": float64(1000),
			"pedantic_mode":      true,
		},
		ConnectReplicationToken:          "5795983a",
		DNSAddrs:                         []net.Addr{tcpAddr("93.95.95.81:7001"), udpAddr("93.95.95.81:7001")},
		DNSARecordLimit:                  29907,
		DNSAllowStale:                    true,
		DNSDisableCompression:            true,
		DNSDomain:                        "7W1xXSqd",
		DNSEnableTruncate:                true,
		DNSMaxStale:                      29685 * time.Second,
		DNSNodeTTL:                       7084 * time.Second,
		DNSOnlyPassing:                   true,
		DNSPort:                          7001,
		DNSRecursorTimeout:               4427 * time.Second,
		DNSRecursors:                     []string{"63.38.39.58", "92.49.18.18"},
		DNSSOA:                           RuntimeSOAConfig{Refresh: 3600, Retry: 600, Expire: 86400, Minttl: 0},
		DNSServiceTTL:                    map[string]time.Duration{"*": 32030 * time.Second},
		DNSUDPAnswerLimit:                29909,
		DNSNodeMetaTXT:                   true,
		DataDir:                          dataDir,
		Datacenter:                       "rzo029wg",
		DevMode:                          true,
		DisableAnonymousSignature:        true,
		DisableCoordinates:               true,
		DisableHostNodeID:                true,
		DisableHTTPUnprintableCharFilter: true,
		DisableKeyringFile:               true,
		DisableRemoteExec:                true,
		DisableUpdateCheck:               true,
		DiscardCheckOutput:               true,
		DiscoveryMaxStale:                5 * time.Second,
		EnableAgentTLSForChecks:          true,
		EnableDebug:                      true,
		EnableRemoteScriptChecks:         true,
		EnableLocalScriptChecks:          true,
		EnableSyslog:                     true,
		EnableUI:                         true,
		EncryptKey:                       "A4wELWqH",
		EncryptVerifyIncoming:            true,
		EncryptVerifyOutgoing:            true,
		GRPCPort:                         4881,
		GRPCAddrs:                        []net.Addr{tcpAddr("32.31.61.91:4881")},
		HTTPAddrs:                        []net.Addr{tcpAddr("83.39.91.39:7999")},
		HTTPBlockEndpoints:               []string{"RBvAFcGD", "fWOWFznh"},
		HTTPPort:                         7999,
		HTTPResponseHeaders:              map[string]string{"M6TKa9NP": "xjuxjOzQ", "JRCrHZed": "rl0mTx81"},
		HTTPSAddrs:                       []net.Addr{tcpAddr("95.17.17.19:15127")},
		HTTPSPort:                        15127,
		KeyFile:                          "IEkkwgIA",
		LeaveDrainTime:                   8265 * time.Second,
		LeaveOnTerm:                      true,
		LogLevel:                         "k1zo9Spt",
		NodeID:                           types.NodeID("AsUIlw99"),
		NodeMeta:                         map[string]string{"5mgGQMBk": "mJLtVMSG", "A7ynFMJB": "0Nx6RGab"},
		NodeName:                         "otlLxGaI",
		NonVotingServer:                  true,
		PidFile:                          "43xN80Km",
		PrimaryDatacenter:                "ejtmd43d",
		RPCAdvertiseAddr:                 tcpAddr("17.99.29.16:3757"),
		RPCBindAddr:                      tcpAddr("16.99.34.17:3757"),
		RPCHoldTimeout:                   15707 * time.Second,
		RPCProtocol:                      30793,
		RPCRateLimit:                     12029.43,
		RPCMaxBurst:                      44848,
		RaftProtocol:                     19016,
		RaftSnapshotThreshold:            16384,
		RaftSnapshotInterval:             30 * time.Second,
		ReconnectTimeoutLAN:              23739 * time.Second,
		ReconnectTimeoutWAN:              26694 * time.Second,
		RejoinAfterLeave:                 true,
		RetryJoinIntervalLAN:             8067 * time.Second,
		RetryJoinIntervalWAN:             28866 * time.Second,
		RetryJoinLAN:                     []string{"pbsSFY7U", "l0qLtWij"},
		RetryJoinMaxAttemptsLAN:          913,
		RetryJoinMaxAttemptsWAN:          23160,
		RetryJoinWAN:                     []string{"PFsR02Ye", "rJdQIhER"},
		SegmentName:                      "BC2NhTDi",
		Segments: []structs.NetworkSegment{
			{
				Name:        "PExYMe2E",
				Bind:        tcpAddr("36.73.36.19:38295"),
				Advertise:   tcpAddr("63.39.19.18:38295"),
				RPCListener: true,
			},
			{
				Name:        "UzCvJgup",
				Bind:        tcpAddr("37.58.38.19:39292"),
				Advertise:   tcpAddr("83.58.26.27:39292"),
				RPCListener: true,
			},
		},
		SerfPortLAN: 8301,
		SerfPortWAN: 8302,
		ServerMode:  true,
		ServerName:  "Oerr9n1G",
		ServerPort:  3757,
		Services: []*structs.ServiceDefinition{
			{
				ID:      "wI1dzxS4",
				Name:    "7IszXMQ1",
				Tags:    []string{"0Zwg8l6v", "zebELdN5"},
				Address: "9RhqPSPB",
				Token:   "myjKJkWH",
				Port:    72219,
				Weights: &structs.Weights{
					Passing: 1,
					Warning: 1,
				},
				EnableTagOverride: true,
				Checks: []*structs.CheckType{
					&structs.CheckType{
						CheckID:    "qmfeO5if",
						Name:       "atDGP7n5",
						Status:     "pDQKEhWL",
						Notes:      "Yt8EDLev",
						ScriptArgs: []string{"81EDZLPa", "bPY5X8xd"},
						HTTP:       "qzHYvmJO",
						Header: map[string][]string{
							"UkpmZ3a3": {"2dfzXuxZ"},
							"cVFpko4u": {"gGqdEB6k", "9LsRo22u"},
						},
						Method:                         "X5DrovFc",
						TCP:                            "ICbxkpSF",
						Interval:                       24392 * time.Second,
						DockerContainerID:              "ZKXr68Yb",
						Shell:                          "CEfzx0Fo",
						TLSSkipVerify:                  true,
						Timeout:                        38333 * time.Second,
						TTL:                            57201 * time.Second,
						DeregisterCriticalServiceAfter: 44214 * time.Second,
					},
				},
				// Note that although this SidecarService is only syntax sugar for
				// registering another service, that has to happen in the agent code so
				// it can make intelligent decisions about automatic port assignments
				// etc. So we expect config just to pass it through verbatim.
				Connect: &structs.ServiceConnect{
					SidecarService: &structs.ServiceDefinition{
						Weights: &structs.Weights{
							Passing: 1,
							Warning: 1,
						},
					},
				},
			},
			{
				ID:      "MRHVMZuD",
				Name:    "6L6BVfgH",
				Tags:    []string{"7Ale4y6o", "PMBW08hy"},
				Address: "R6H6g8h0",
				Token:   "ZgY8gjMI",
				Port:    38292,
				Weights: &structs.Weights{
					Passing: 1979,
					Warning: 6,
				},
				EnableTagOverride: true,
				Checks: structs.CheckTypes{
					&structs.CheckType{
						CheckID:    "GTti9hCo",
						Name:       "9OOS93ne",
						Notes:      "CQy86DH0",
						Status:     "P0SWDvrk",
						ScriptArgs: []string{"EXvkYIuG", "BATOyt6h"},
						HTTP:       "u97ByEiW",
						Header: map[string][]string{
							"MUlReo8L": {"AUZG7wHG", "gsN0Dc2N"},
							"1UJXjVrT": {"OJgxzTfk", "xZZrFsq7"},
						},
						Method:                         "5wkAxCUE",
						TCP:                            "MN3oA9D2",
						Interval:                       32718 * time.Second,
						DockerContainerID:              "cU15LMet",
						Shell:                          "nEz9qz2l",
						TLSSkipVerify:                  true,
						Timeout:                        34738 * time.Second,
						TTL:                            22773 * time.Second,
						DeregisterCriticalServiceAfter: 84282 * time.Second,
					},
					&structs.CheckType{
						CheckID:    "UHsDeLxG",
						Name:       "PQSaPWlT",
						Notes:      "jKChDOdl",
						Status:     "5qFz6OZn",
						ScriptArgs: []string{"NMtYWlT9", "vj74JXsm"},
						HTTP:       "1LBDJhw4",
						Header: map[string][]string{
							"cXPmnv1M": {"imDqfaBx", "NFxZ1bQe"},
							"vr7wY7CS": {"EtCoNPPL", "9vAarJ5s"},
						},
						Method:                         "wzByP903",
						TCP:                            "2exjZIGE",
						Interval:                       5656 * time.Second,
						DockerContainerID:              "5tDBWpfA",
						Shell:                          "rlTpLM8s",
						TLSSkipVerify:                  true,
						Timeout:                        4868 * time.Second,
						TTL:                            11222 * time.Second,
						DeregisterCriticalServiceAfter: 68482 * time.Second,
					},
				},
				Connect: &structs.ServiceConnect{
					Proxy: &structs.ServiceDefinitionConnectProxy{
						ExecMode: "daemon",
						Command:  []string{"awesome-proxy"},
						Config: map[string]interface{}{
							"foo": "qux",
						},
					},
				},
			},
			{
				ID:   "Kh81CPF6",
				Name: "Kh81CPF6-proxy",
				Port: 31471,
				Kind: "connect-proxy",
				Proxy: &structs.ConnectProxyConfig{
					DestinationServiceName: "6L6BVfgH",
					DestinationServiceID:   "6L6BVfgH-id",
					LocalServiceAddress:    "127.0.0.2",
					LocalServicePort:       23759,
					Config: map[string]interface{}{
						"cedGGtZf": "pWrUNiWw",
					},
					Upstreams: structs.Upstreams{
						{
							DestinationType: "service", // Default should be explicitly filled
							DestinationName: "KPtAj2cb",
							LocalBindPort:   4051,
							Config: map[string]interface{}{
								"kzRnZOyd": "nUNKoL8H",
							},
						},
						{
							DestinationType:      "prepared_query",
							DestinationNamespace: "9nakw0td",
							DestinationName:      "KSd8HsRl",
							LocalBindPort:        11884,
							LocalBindAddress:     "127.24.88.0",
						},
					},
				},
				Weights: &structs.Weights{
					Passing: 1,
					Warning: 1,
				},
			},
			{
				ID:      "dLOXpSCI",
				Name:    "o1ynPkp0",
				Tags:    []string{"nkwshvM5", "NTDWn3ek"},
				Address: "cOlSOhbp",
				Token:   "msy7iWER",
				Meta:    map[string]string{"mymeta": "data"},
				Port:    24237,
				Weights: &structs.Weights{
					Passing: 100,
					Warning: 1,
				},
				EnableTagOverride: true,
				Connect: &structs.ServiceConnect{
					Native: true,
				},
				Checks: structs.CheckTypes{
					&structs.CheckType{
						CheckID:    "Zv99e9Ka",
						Name:       "sgV4F7Pk",
						Notes:      "yP5nKbW0",
						Status:     "7oLMEyfu",
						ScriptArgs: []string{"5wEZtZpv", "0Ihyk8cS"},
						HTTP:       "KyDjGY9H",
						Header: map[string][]string{
							"gv5qefTz": {"5Olo2pMG", "PvvKWQU5"},
							"SHOVq1Vv": {"jntFhyym", "GYJh32pp"},
						},
						Method:                         "T66MFBfR",
						TCP:                            "bNnNfx2A",
						Interval:                       22224 * time.Second,
						DockerContainerID:              "ipgdFtjd",
						Shell:                          "omVZq7Sz",
						TLSSkipVerify:                  true,
						Timeout:                        18913 * time.Second,
						TTL:                            44743 * time.Second,
						DeregisterCriticalServiceAfter: 8482 * time.Second,
					},
					&structs.CheckType{
						CheckID:    "G79O6Mpr",
						Name:       "IEqrzrsd",
						Notes:      "SVqApqeM",
						Status:     "XXkVoZXt",
						ScriptArgs: []string{"wD05Bvao", "rLYB7kQC"},
						HTTP:       "kyICZsn8",
						Header: map[string][]string{
							"4ebP5vL4": {"G20SrL5Q", "DwPKlMbo"},
							"p2UI34Qz": {"UsG1D0Qh", "NHhRiB6s"},
						},
						Method:                         "ciYHWors",
						TCP:                            "FfvCwlqH",
						Interval:                       12356 * time.Second,
						DockerContainerID:              "HBndBU6R",
						Shell:                          "hVI33JjA",
						TLSSkipVerify:                  true,
						Timeout:                        38282 * time.Second,
						TTL:                            1181 * time.Second,
						DeregisterCriticalServiceAfter: 4992 * time.Second,
					},
					&structs.CheckType{
						CheckID:    "RMi85Dv8",
						Name:       "iehanzuq",
						Status:     "rCvn53TH",
						Notes:      "fti5lfF3",
						ScriptArgs: []string{"16WRUmwS", "QWk7j7ae"},
						HTTP:       "dl3Fgme3",
						Header: map[string][]string{
							"rjm4DEd3": {"2m3m2Fls"},
							"l4HwQ112": {"fk56MNlo", "dhLK56aZ"},
						},
						Method:                         "9afLm3Mj",
						TCP:                            "fjiLFqVd",
						Interval:                       23926 * time.Second,
						DockerContainerID:              "dO5TtRHk",
						Shell:                          "e6q2ttES",
						TLSSkipVerify:                  true,
						Timeout:                        38483 * time.Second,
						TTL:                            10943 * time.Second,
						DeregisterCriticalServiceAfter: 68787 * time.Second,
					},
				},
			},
		},
		SerfAdvertiseAddrLAN: tcpAddr("17.99.29.16:8301"),
		SerfAdvertiseAddrWAN: tcpAddr("78.63.37.19:8302"),
		SerfBindAddrLAN:      tcpAddr("99.43.63.15:8301"),
		SerfBindAddrWAN:      tcpAddr("67.88.33.19:8302"),
		SessionTTLMin:        26627 * time.Second,
		SkipLeaveOnInt:       true,
		StartJoinAddrsLAN:    []string{"LR3hGDoG", "MwVpZ4Up"},
		StartJoinAddrsWAN:    []string{"EbFSc3nA", "kwXTh623"},
		SyslogFacility:       "hHv79Uia",
		Telemetry: lib.TelemetryConfig{
			CirconusAPIApp:                     "p4QOTe9j",
			CirconusAPIToken:                   "E3j35V23",
			CirconusAPIURL:                     "mEMjHpGg",
			CirconusBrokerID:                   "BHlxUhed",
			CirconusBrokerSelectTag:            "13xy1gHm",
			CirconusCheckDisplayName:           "DRSlQR6n",
			CirconusCheckForceMetricActivation: "Ua5FGVYf",
			CirconusCheckID:                    "kGorutad",
			CirconusCheckInstanceID:            "rwoOL6R4",
			CirconusCheckSearchTag:             "ovT4hT4f",
			CirconusCheckTags:                  "prvO4uBl",
			CirconusSubmissionInterval:         "DolzaflP",
			CirconusSubmissionURL:              "gTcbS93G",
			DisableHostname:                    true,
			DogstatsdAddr:                      "0wSndumK",
			DogstatsdTags:                      []string{"3N81zSUB", "Xtj8AnXZ"},
			FilterDefault:                      true,
			AllowedPrefixes:                    []string{"oJotS8XJ"},
			BlockedPrefixes:                    []string{"cazlEhGn"},
			MetricsPrefix:                      "ftO6DySn",
			PrometheusRetentionTime:            15 * time.Second,
			StatsdAddr:                         "drce87cy",
			StatsiteAddr:                       "HpFwKB8R",
		},
		TLSCipherSuites:             []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384},
		TLSMinVersion:               "pAOWafkR",
		TLSPreferServerCipherSuites: true,
		TaggedAddresses: map[string]string{
			"7MYgHrYH": "dALJAhLD",
			"h6DdBy6K": "ebrr9zZ8",
			"lan":      "17.99.29.16",
			"wan":      "78.63.37.19",
		},
		TranslateWANAddrs:    true,
		UIDir:                "11IFzAUn",
		UnixSocketUser:       "E0nB1DwA",
		UnixSocketGroup:      "8pFodrV8",
		UnixSocketMode:       "E8sAwOv4",
		VerifyIncoming:       true,
		VerifyIncomingHTTPS:  true,
		VerifyIncomingRPC:    true,
		VerifyOutgoing:       true,
		VerifyServerHostname: true,
		Watches: []map[string]interface{}{
			map[string]interface{}{
				"type":       "key",
				"datacenter": "GyE6jpeW",
				"key":        "j9lF1Tve",
				"handler":    "90N7S4LN",
			},
			map[string]interface{}{
				"type":       "keyprefix",
				"datacenter": "fYrl3F5d",
				"key":        "sl3Dffu7",
				"args":       []interface{}{"dltjDJ2a", "flEa7C2d"},
			},
		},
	}

	warns := []string{
		`The 'acl_datacenter' field is deprecated. Use the 'primary_datacenter' field instead.`,
		`bootstrap_expect > 0: expecting 53 servers`,
	}

	// ensure that all fields are set to unique non-zero values
	// todo(fs): This currently fails since ServiceDefinition.Check is not used
	// todo(fs): not sure on how to work around this. Possible options are:
	// todo(fs):  * move first check into the Check field
	// todo(fs):  * ignore the Check field
	// todo(fs): both feel like a hack
	if err := nonZero("RuntimeConfig", nil, want); err != nil {
		t.Log(err)
	}

	for format, data := range src {
		t.Run(format, func(t *testing.T) {
			// parse the flags since this is the only way we can set the
			// DevMode flag
			var flags Flags
			fs := flag.NewFlagSet("", flag.ContinueOnError)
			AddFlags(fs, &flags)
			if err := fs.Parse(flagSrc); err != nil {
				t.Fatalf("ParseFlags: %s", err)
			}

			// ensure that all fields are set to unique non-zero values
			// if err := nonZero("Config", nil, c); err != nil {
			// 	t.Fatal(err)
			// }

			b, err := NewBuilder(flags)
			if err != nil {
				t.Fatalf("NewBuilder: %s", err)
			}
			b.Sources = append(b.Sources, Source{Name: "full." + format, Data: data})
			b.Tail = append(b.Tail, tail[format]...)
			b.Tail = append(b.Tail, VersionSource("JNtPSav3", "R909Hblt", "ZT1JOQLn"))

			// construct the runtime config
			rt, err := b.Build()
			if err != nil {
				t.Fatalf("Build: %s", err)
			}

			// verify that all fields are set
			if !verify.Values(t, "runtime_config", rt, want) {
				t.FailNow()
			}

			// at this point we have confirmed that the parsing worked
			// for all fields but the validation will fail since certain
			// combinations are not allowed. Since it is not possible to have
			// all fields with non-zero values and to have a valid configuration
			// we are patching a handful of safe fields to make validation pass.
			rt.Bootstrap = false
			rt.DevMode = false
			rt.EnableUI = false
			rt.SegmentName = ""
			rt.Segments = nil

			// validate the runtime config
			if err := b.Validate(rt); err != nil {
				t.Fatalf("Validate: %s", err)
			}

			// check the warnings
			if got, want := b.Warnings, warns; !verify.Values(t, "warnings", got, want) {
				t.FailNow()
			}
		})
	}
}

// nonZero verifies recursively that all fields are set to unique,
// non-zero and non-nil values.
//
// struct: check all fields recursively
// slice: check len > 0 and all values recursively
// ptr: check not nil
// bool: check not zero (cannot check uniqueness)
// string, int, uint: check not zero and unique
// other: error
func nonZero(name string, uniq map[interface{}]string, v interface{}) error {
	if v == nil {
		return fmt.Errorf("%q is nil", name)
	}

	if uniq == nil {
		uniq = map[interface{}]string{}
	}

	isUnique := func(v interface{}) error {
		if other := uniq[v]; other != "" {
			return fmt.Errorf("%q and %q both use value %q", name, other, v)
		}
		uniq[v] = name
		return nil
	}

	val, typ := reflect.ValueOf(v), reflect.TypeOf(v)
	// fmt.Printf("%s: %T\n", name, v)
	switch typ.Kind() {
	case reflect.Struct:
		for i := 0; i < typ.NumField(); i++ {
			f := typ.Field(i)
			fieldname := fmt.Sprintf("%s.%s", name, f.Name)
			err := nonZero(fieldname, uniq, val.Field(i).Interface())
			if err != nil {
				return err
			}
		}

	case reflect.Slice:
		if val.Len() == 0 {
			return fmt.Errorf("%q is empty slice", name)
		}
		for i := 0; i < val.Len(); i++ {
			elemname := fmt.Sprintf("%s[%d]", name, i)
			err := nonZero(elemname, uniq, val.Index(i).Interface())
			if err != nil {
				return err
			}
		}

	case reflect.Map:
		if val.Len() == 0 {
			return fmt.Errorf("%q is empty map", name)
		}
		for _, key := range val.MapKeys() {
			keyname := fmt.Sprintf("%s[%s]", name, key.String())
			if err := nonZero(keyname, uniq, key.Interface()); err != nil {
				if strings.Contains(err.Error(), "is zero value") {
					return fmt.Errorf("%q has zero value map key", name)
				}
				return err
			}
			if err := nonZero(keyname, uniq, val.MapIndex(key).Interface()); err != nil {
				return err
			}
		}

	case reflect.Bool:
		if val.Bool() != true {
			return fmt.Errorf("%q is zero value", name)
		}
		// do not test bool for uniqueness since there are only two values

	case reflect.String:
		if val.Len() == 0 {
			return fmt.Errorf("%q is zero value", name)
		}
		return isUnique(v)

	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if val.Int() == 0 {
			return fmt.Errorf("%q is zero value", name)
		}
		return isUnique(v)

	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		if val.Uint() == 0 {
			return fmt.Errorf("%q is zero value", name)
		}
		return isUnique(v)

	case reflect.Float32, reflect.Float64:
		if val.Float() == 0 {
			return fmt.Errorf("%q is zero value", name)
		}
		return isUnique(v)

	case reflect.Ptr:
		if val.IsNil() {
			return fmt.Errorf("%q is nil", name)
		}
		return nonZero("*"+name, uniq, val.Elem().Interface())

	default:
		return fmt.Errorf("%T is not supported", v)
	}
	return nil
}

func TestNonZero(t *testing.T) {
	var empty string

	tests := []struct {
		desc string
		v    interface{}
		err  error
	}{
		{"nil", nil, errors.New(`"x" is nil`)},
		{"zero bool", false, errors.New(`"x" is zero value`)},
		{"zero string", "", errors.New(`"x" is zero value`)},
		{"zero int", int(0), errors.New(`"x" is zero value`)},
		{"zero int8", int8(0), errors.New(`"x" is zero value`)},
		{"zero int16", int16(0), errors.New(`"x" is zero value`)},
		{"zero int32", int32(0), errors.New(`"x" is zero value`)},
		{"zero int64", int64(0), errors.New(`"x" is zero value`)},
		{"zero uint", uint(0), errors.New(`"x" is zero value`)},
		{"zero uint8", uint8(0), errors.New(`"x" is zero value`)},
		{"zero uint16", uint16(0), errors.New(`"x" is zero value`)},
		{"zero uint32", uint32(0), errors.New(`"x" is zero value`)},
		{"zero uint64", uint64(0), errors.New(`"x" is zero value`)},
		{"zero float32", float32(0), errors.New(`"x" is zero value`)},
		{"zero float64", float64(0), errors.New(`"x" is zero value`)},
		{"ptr to zero value", &empty, errors.New(`"*x" is zero value`)},
		{"empty slice", []string{}, errors.New(`"x" is empty slice`)},
		{"slice with zero value", []string{""}, errors.New(`"x[0]" is zero value`)},
		{"empty map", map[string]string{}, errors.New(`"x" is empty map`)},
		{"map with zero value key", map[string]string{"": "y"}, errors.New(`"x" has zero value map key`)},
		{"map with zero value elem", map[string]string{"y": ""}, errors.New(`"x[y]" is zero value`)},
		{"struct with nil field", struct{ Y *int }{}, errors.New(`"x.Y" is nil`)},
		{"struct with zero value field", struct{ Y string }{}, errors.New(`"x.Y" is zero value`)},
		{"struct with empty array", struct{ Y []string }{}, errors.New(`"x.Y" is empty slice`)},
	}

	for _, tt := range tests {
		t.Run(tt.desc, func(t *testing.T) {
			if got, want := nonZero("x", nil, tt.v), tt.err; !reflect.DeepEqual(got, want) {
				t.Fatalf("got error %v want %v", got, want)
			}
		})
	}
}

func TestConfigDecodeBytes(t *testing.T) {
	t.Parallel()
	// Test with some input
	src := []byte("abc")
	key := base64.StdEncoding.EncodeToString(src)

	result, err := decodeBytes(key)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if !bytes.Equal(src, result) {
		t.Fatalf("bad: %#v", result)
	}

	// Test with no input
	result, err = decodeBytes("")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if len(result) > 0 {
		t.Fatalf("bad: %#v", result)
	}
}

func TestSanitize(t *testing.T) {
	rt := RuntimeConfig{
		BindAddr:             &net.IPAddr{IP: net.ParseIP("127.0.0.1")},
		SerfAdvertiseAddrLAN: &net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
		DNSAddrs: []net.Addr{
			&net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
			&net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
		},
		DNSSOA: RuntimeSOAConfig{Refresh: 3600, Retry: 600, Expire: 86400, Minttl: 0},
		HTTPAddrs: []net.Addr{
			&net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 5678},
			&net.UnixAddr{Name: "/var/run/foo"},
		},
		ConsulCoordinateUpdatePeriod: 15 * time.Second,
		RetryJoinLAN: []string{
			"foo=bar key=baz secret=boom bang=bar",
		},
		RetryJoinWAN: []string{
			"wan_foo=bar wan_key=baz wan_secret=boom wan_bang=bar",
		},
		Services: []*structs.ServiceDefinition{
			&structs.ServiceDefinition{
				Name:  "foo",
				Token: "bar",
				Check: structs.CheckType{
					Name: "blurb",
				},
				Weights: &structs.Weights{
					Passing: 67,
					Warning: 3,
				},
			},
		},
		Checks: []*structs.CheckDefinition{
			&structs.CheckDefinition{
				Name:  "zoo",
				Token: "zope",
			},
		},
	}

	rtJSON := `{
		"ACLAgentMasterToken": "hidden",
		"ACLAgentToken": "hidden",
		"ACLDatacenter": "",
		"ACLDefaultPolicy": "",
		"ACLDisabledTTL": "0s",
		"ACLDownPolicy": "",
		"ACLEnableKeyListPolicy": false,
		"ACLEnforceVersion8": false,
		"ACLMasterToken": "hidden",
		"ACLPolicyTTL": "0s",
		"ACLReplicationToken": "hidden",
		"ACLTokenReplication": false,
		"ACLTokenTTL": "0s",
		"ACLToken": "hidden",
		"ACLsEnabled": false,
		"AEInterval": "0s",
		"AdvertiseAddrLAN": "",
		"AdvertiseAddrWAN": "",
		"AutopilotCleanupDeadServers": false,
		"AutopilotDisableUpgradeMigration": false,
		"AutopilotLastContactThreshold": "0s",
		"AutopilotMaxTrailingLogs": 0,
		"AutopilotRedundancyZoneTag": "",
		"AutopilotServerStabilizationTime": "0s",
		"AutopilotUpgradeVersionTag": "",
		"BindAddr": "127.0.0.1",
		"Bootstrap": false,
		"BootstrapExpect": 0,
		"CAFile": "",
		"CAPath": "",
		"CertFile": "",
		"CheckDeregisterIntervalMin": "0s",
		"CheckReapInterval": "0s",
		"CheckUpdateInterval": "0s",
		"Checks": [{
			"AliasNode": "",
			"AliasService": "",
			"DeregisterCriticalServiceAfter": "0s",
			"DockerContainerID": "",
			"GRPC": "",
			"GRPCUseTLS": false,
			"HTTP": "",
			"Header": {},
			"ID": "",
			"Interval": "0s",
			"Method": "",
			"Name": "zoo",
			"Notes": "",
			"ScriptArgs": [],
			"ServiceID": "",
			"Shell": "",
			"Status": "",
			"TCP": "",
			"TLSSkipVerify": false,
			"TTL": "0s",
			"Timeout": "0s",
			"Token": "hidden"
		}],
		"ClientAddrs": [],
		"ConnectCAConfig": {},
		"ConnectCAProvider": "",
		"ConnectEnabled": false,
		"ConnectProxyAllowManagedAPIRegistration": false,
		"ConnectProxyAllowManagedRoot": false,
		"ConnectProxyBindMaxPort": 0,
		"ConnectProxyBindMinPort": 0,
		"ConnectProxyDefaultConfig": {},
		"ConnectProxyDefaultDaemonCommand": [],
		"ConnectProxyDefaultExecMode": "",
		"ConnectProxyDefaultScriptCommand": [],
		"ConnectSidecarMaxPort": 0,
		"ConnectSidecarMinPort": 0,
		"ConnectReplicationToken": "hidden",
		"ConnectTestDisableManagedProxies": false,
		"ConsulCoordinateUpdateBatchSize": 0,
		"ConsulCoordinateUpdateMaxBatches": 0,
		"ConsulCoordinateUpdatePeriod": "15s",
		"ConsulRaftElectionTimeout": "0s",
		"ConsulRaftHeartbeatTimeout": "0s",
		"ConsulRaftLeaderLeaseTimeout": "0s",
		"GossipLANGossipInterval": "0s",
		"GossipLANGossipNodes": 0,
		"GossipLANProbeInterval": "0s",
		"GossipLANProbeTimeout": "0s",
		"GossipLANRetransmitMult": 0,
		"GossipLANSuspicionMult": 0,
		"GossipWANGossipInterval": "0s",
		"GossipWANGossipNodes": 0,
		"GossipWANProbeInterval": "0s",
		"GossipWANProbeTimeout": "0s",
		"GossipWANRetransmitMult": 0,
		"GossipWANSuspicionMult": 0,
		"ConsulServerHealthInterval": "0s",
		"DNSARecordLimit": 0,
		"DNSAddrs": [
			"tcp://1.2.3.4:5678",
			"udp://1.2.3.4:5678"
		],
		"DNSAllowStale": false,
		"DNSDisableCompression": false,
		"DNSDomain": "",
		"DNSEnableTruncate": false,
		"DNSMaxStale": "0s",
		"DNSNodeMetaTXT": false,
		"DNSNodeTTL": "0s",
		"DNSOnlyPassing": false,
		"DNSPort": 0,
		"DNSRecursorTimeout": "0s",
		"DNSRecursors": [],
		"DNSServiceTTL": {},
		"DNSSOA": {
			"Refresh": 3600,
			"Retry": 600,
			"Expire": 86400,
			"Minttl": 0
		},
		"DNSUDPAnswerLimit": 0,
		"DataDir": "",
		"Datacenter": "",
		"DevMode": false,
		"DisableAnonymousSignature": false,
		"DisableCoordinates": false,
		"DisableHTTPUnprintableCharFilter": false,
		"DisableHostNodeID": false,
		"DisableKeyringFile": false,
		"DisableRemoteExec": false,
		"DisableUpdateCheck": false,
		"DiscardCheckOutput": false,
		"DiscoveryMaxStale": "0s",
		"EnableAgentTLSForChecks": false,
		"EnableDebug": false,
		"EnableLocalScriptChecks": false,
		"EnableRemoteScriptChecks": false,
		"EnableSyslog": false,
		"EnableUI": false,
		"EncryptKey": "hidden",
		"EncryptVerifyIncoming": false,
		"EncryptVerifyOutgoing": false,
		"GRPCAddrs": [],
		"GRPCPort": 0,
		"HTTPAddrs": [
			"tcp://1.2.3.4:5678",
			"unix:///var/run/foo"
		],
		"HTTPBlockEndpoints": [],
		"HTTPPort": 0,
		"HTTPResponseHeaders": {},
		"HTTPSAddrs": [],
		"HTTPSPort": 0,
		"KeyFile": "hidden",
		"LeaveDrainTime": "0s",
		"LeaveOnTerm": false,
		"LogLevel": "",
		"LogFile": "",
		"LogRotateBytes": 0,
		"LogRotateDuration": "0s",
		"NodeID": "",
		"NodeMeta": {},
		"NodeName": "",
		"NonVotingServer": false,
		"PidFile": "",
		"PrimaryDatacenter": "",
		"RPCAdvertiseAddr": "",
		"RPCBindAddr": "",
		"RPCHoldTimeout": "0s",
		"RPCMaxBurst": 0,
		"RPCProtocol": 0,
		"RPCRateLimit": 0,
		"RaftProtocol": 0,
		"RaftSnapshotInterval": "0s",
		"RaftSnapshotThreshold": 0,
		"ReconnectTimeoutLAN": "0s",
		"ReconnectTimeoutWAN": "0s",
		"RejoinAfterLeave": false,
		"RetryJoinIntervalLAN": "0s",
		"RetryJoinIntervalWAN": "0s",
		"RetryJoinLAN": [
			"foo=bar key=hidden secret=hidden bang=bar"
		],
		"RetryJoinMaxAttemptsLAN": 0,
		"RetryJoinMaxAttemptsWAN": 0,
		"RetryJoinWAN": [
			"wan_foo=bar wan_key=hidden wan_secret=hidden wan_bang=bar"
		],
		"Revision": "",
		"SegmentLimit": 0,
		"SegmentName": "",
		"SegmentNameLimit": 0,
		"Segments": [],
		"SerfAdvertiseAddrLAN": "tcp://1.2.3.4:5678",
		"SerfAdvertiseAddrWAN": "",
		"SerfBindAddrLAN": "",
		"SerfBindAddrWAN": "",
		"SerfPortLAN": 0,
		"SerfPortWAN": 0,
		"ServerMode": false,
		"ServerName": "",
		"ServerPort": 0,
		"Services": [{
			"Address": "",
			"Check": {
				"AliasNode": "",
				"AliasService": "",
				"CheckID": "",
				"DeregisterCriticalServiceAfter": "0s",
				"DockerContainerID": "",
				"GRPC": "",
				"GRPCUseTLS": false,
				"HTTP": "",
				"Header": {},
				"Interval": "0s",
				"Method": "",
				"Name": "blurb",
				"Notes": "",
				"ScriptArgs": [],
				"Shell": "",
				"Status": "",
				"TCP": "",
				"TLSSkipVerify": false,
				"TTL": "0s",
				"Timeout": "0s"
			},
			"Checks": [],
			"Connect": null,
			"EnableTagOverride": false,
			"ID": "",
			"Kind": "",
			"Meta": {},
			"Name": "foo",
			"Port": 0,
			"Proxy": null,
			"ProxyDestination": "",
			"Tags": [],
			"Token": "hidden",
			"Weights": {
				"Passing": 67,
				"Warning": 3
			}
		}],
		"SessionTTLMin": "0s",
		"SkipLeaveOnInt": false,
		"StartJoinAddrsLAN": [],
		"StartJoinAddrsWAN": [],
		"SyncCoordinateIntervalMin": "0s",
		"SyncCoordinateRateTarget": 0,
		"SyslogFacility": "",
		"TLSCipherSuites": [],
		"TLSMinVersion": "",
		"TLSPreferServerCipherSuites": false,
		"TaggedAddresses": {},
		"Telemetry": {
			"AllowedPrefixes": [],
			"BlockedPrefixes": [],
			"CirconusAPIApp": "",
			"CirconusAPIToken": "hidden",
			"CirconusAPIURL": "",
			"CirconusBrokerID": "",
			"CirconusBrokerSelectTag": "",
			"CirconusCheckDisplayName": "",
			"CirconusCheckForceMetricActivation": "",
			"CirconusCheckID": "",
			"CirconusCheckInstanceID": "",
			"CirconusCheckSearchTag": "",
			"CirconusCheckTags": "",
			"CirconusSubmissionInterval": "",
			"CirconusSubmissionURL": "",
			"DisableHostname": false,
			"DogstatsdAddr": "",
			"DogstatsdTags": [],
			"FilterDefault": false,
			"MetricsPrefix": "",
			"PrometheusRetentionTime": "0s",
			"StatsdAddr": "",
			"StatsiteAddr": ""
		},
		"TranslateWANAddrs": false,
		"UIDir": "",
		"UnixSocketGroup": "",
		"UnixSocketMode": "",
		"UnixSocketUser": "",
		"VerifyIncoming": false,
		"VerifyIncomingHTTPS": false,
		"VerifyIncomingRPC": false,
		"VerifyOutgoing": false,
		"VerifyServerHostname": false,
		"Version": "",
		"VersionPrerelease": "",
		"Watches": []
	}`
	b, err := json.MarshalIndent(rt.Sanitized(), "", "    ")
	if err != nil {
		t.Fatal(err)
	}
	require.JSONEq(t, rtJSON, string(b))
}

func TestRuntime_apiAddresses(t *testing.T) {
	rt := RuntimeConfig{
		HTTPAddrs: []net.Addr{
			&net.TCPAddr{IP: net.ParseIP("198.18.0.1"), Port: 5678},
			&net.UnixAddr{Name: "/var/run/foo"},
		},
		HTTPSAddrs: []net.Addr{
			&net.TCPAddr{IP: net.ParseIP("198.18.0.2"), Port: 5678},
		}}

	unixAddrs, httpAddrs, httpsAddrs := rt.apiAddresses(1)

	require.Len(t, unixAddrs, 1)
	require.Len(t, httpAddrs, 1)
	require.Len(t, httpsAddrs, 1)

	require.Equal(t, "/var/run/foo", unixAddrs[0])
	require.Equal(t, "198.18.0.1:5678", httpAddrs[0])
	require.Equal(t, "198.18.0.2:5678", httpsAddrs[0])
}

func TestRuntime_APIConfigHTTPS(t *testing.T) {
	rt := RuntimeConfig{
		HTTPAddrs: []net.Addr{
			&net.TCPAddr{IP: net.ParseIP("198.18.0.1"), Port: 5678},
			&net.UnixAddr{Name: "/var/run/foo"},
		},
		HTTPSAddrs: []net.Addr{
			&net.TCPAddr{IP: net.ParseIP("198.18.0.2"), Port: 5678},
		},
		Datacenter:     "dc-test",
		CAFile:         "/etc/consul/ca.crt",
		CAPath:         "/etc/consul/ca.dir",
		CertFile:       "/etc/consul/server.crt",
		KeyFile:        "/etc/consul/ssl/server.key",
		VerifyOutgoing: false,
	}

	cfg, err := rt.APIConfig(false)
	require.NoError(t, err)
	require.Equal(t, "198.18.0.2:5678", cfg.Address)
	require.Equal(t, "https", cfg.Scheme)
	require.Equal(t, rt.CAFile, cfg.TLSConfig.CAFile)
	require.Equal(t, rt.CAPath, cfg.TLSConfig.CAPath)
	require.Equal(t, "", cfg.TLSConfig.CertFile)
	require.Equal(t, "", cfg.TLSConfig.KeyFile)
	require.Equal(t, rt.Datacenter, cfg.Datacenter)
	require.Equal(t, true, cfg.TLSConfig.InsecureSkipVerify)

	rt.VerifyOutgoing = true
	cfg, err = rt.APIConfig(true)
	require.NoError(t, err)
	require.Equal(t, "198.18.0.2:5678", cfg.Address)
	require.Equal(t, "https", cfg.Scheme)
	require.Equal(t, rt.CAFile, cfg.TLSConfig.CAFile)
	require.Equal(t, rt.CAPath, cfg.TLSConfig.CAPath)
	require.Equal(t, rt.CertFile, cfg.TLSConfig.CertFile)
	require.Equal(t, rt.KeyFile, cfg.TLSConfig.KeyFile)
	require.Equal(t, rt.Datacenter, cfg.Datacenter)
	require.Equal(t, false, cfg.TLSConfig.InsecureSkipVerify)
}

func TestRuntime_APIConfigHTTP(t *testing.T) {
	rt := RuntimeConfig{
		HTTPAddrs: []net.Addr{
			&net.UnixAddr{Name: "/var/run/foo"},
			&net.TCPAddr{IP: net.ParseIP("198.18.0.1"), Port: 5678},
		},
		Datacenter: "dc-test",
	}

	cfg, err := rt.APIConfig(false)
	require.NoError(t, err)
	require.Equal(t, rt.Datacenter, cfg.Datacenter)
	require.Equal(t, "198.18.0.1:5678", cfg.Address)
	require.Equal(t, "http", cfg.Scheme)
	require.Equal(t, "", cfg.TLSConfig.CAFile)
	require.Equal(t, "", cfg.TLSConfig.CAPath)
	require.Equal(t, "", cfg.TLSConfig.CertFile)
	require.Equal(t, "", cfg.TLSConfig.KeyFile)
}

func TestRuntime_APIConfigUNIX(t *testing.T) {
	rt := RuntimeConfig{
		HTTPAddrs: []net.Addr{
			&net.UnixAddr{Name: "/var/run/foo"},
		},
		Datacenter: "dc-test",
	}

	cfg, err := rt.APIConfig(false)
	require.NoError(t, err)
	require.Equal(t, rt.Datacenter, cfg.Datacenter)
	require.Equal(t, "unix:///var/run/foo", cfg.Address)
	require.Equal(t, "http", cfg.Scheme)
	require.Equal(t, "", cfg.TLSConfig.CAFile)
	require.Equal(t, "", cfg.TLSConfig.CAPath)
	require.Equal(t, "", cfg.TLSConfig.CertFile)
	require.Equal(t, "", cfg.TLSConfig.KeyFile)
}

func TestRuntime_APIConfigANYAddrV4(t *testing.T) {
	rt := RuntimeConfig{
		HTTPAddrs: []net.Addr{
			&net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 5678},
		},
		Datacenter: "dc-test",
	}

	cfg, err := rt.APIConfig(false)
	require.NoError(t, err)
	require.Equal(t, rt.Datacenter, cfg.Datacenter)
	require.Equal(t, "127.0.0.1:5678", cfg.Address)
	require.Equal(t, "http", cfg.Scheme)
	require.Equal(t, "", cfg.TLSConfig.CAFile)
	require.Equal(t, "", cfg.TLSConfig.CAPath)
	require.Equal(t, "", cfg.TLSConfig.CertFile)
	require.Equal(t, "", cfg.TLSConfig.KeyFile)
}

func TestRuntime_APIConfigANYAddrV6(t *testing.T) {
	rt := RuntimeConfig{
		HTTPAddrs: []net.Addr{
			&net.TCPAddr{IP: net.ParseIP("::"), Port: 5678},
		},
		Datacenter: "dc-test",
	}

	cfg, err := rt.APIConfig(false)
	require.NoError(t, err)
	require.Equal(t, rt.Datacenter, cfg.Datacenter)
	require.Equal(t, "[::1]:5678", cfg.Address)
	require.Equal(t, "http", cfg.Scheme)
	require.Equal(t, "", cfg.TLSConfig.CAFile)
	require.Equal(t, "", cfg.TLSConfig.CAPath)
	require.Equal(t, "", cfg.TLSConfig.CertFile)
	require.Equal(t, "", cfg.TLSConfig.KeyFile)
}

func TestRuntime_ClientAddress(t *testing.T) {
	rt := RuntimeConfig{
		HTTPAddrs: []net.Addr{
			&net.TCPAddr{IP: net.ParseIP("::"), Port: 5678},
			&net.TCPAddr{IP: net.ParseIP("198.18.0.1"), Port: 5679},
			&net.UnixAddr{Name: "/var/run/foo", Net: "unix"},
		},
		HTTPSAddrs: []net.Addr{
			&net.TCPAddr{IP: net.ParseIP("::"), Port: 5688},
			&net.TCPAddr{IP: net.ParseIP("198.18.0.1"), Port: 5689},
		},
	}

	unix, http, https := rt.ClientAddress()

	require.Equal(t, "unix:///var/run/foo", unix)
	require.Equal(t, "198.18.0.1:5679", http)
	require.Equal(t, "198.18.0.1:5689", https)
}

func TestRuntime_ClientAddressAnyV4(t *testing.T) {
	rt := RuntimeConfig{
		HTTPAddrs: []net.Addr{
			&net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 5678},
			&net.UnixAddr{Name: "/var/run/foo", Net: "unix"},
		},
		HTTPSAddrs: []net.Addr{
			&net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 5688},
		},
	}

	unix, http, https := rt.ClientAddress()

	require.Equal(t, "unix:///var/run/foo", unix)
	require.Equal(t, "127.0.0.1:5678", http)
	require.Equal(t, "127.0.0.1:5688", https)
}

func TestRuntime_ClientAddressAnyV6(t *testing.T) {
	rt := RuntimeConfig{
		HTTPAddrs: []net.Addr{
			&net.TCPAddr{IP: net.ParseIP("::"), Port: 5678},
			&net.UnixAddr{Name: "/var/run/foo", Net: "unix"},
		},
		HTTPSAddrs: []net.Addr{
			&net.TCPAddr{IP: net.ParseIP("::"), Port: 5688},
		},
	}

	unix, http, https := rt.ClientAddress()

	require.Equal(t, "unix:///var/run/foo", unix)
	require.Equal(t, "[::1]:5678", http)
	require.Equal(t, "[::1]:5688", https)
}

func splitIPPort(hostport string) (net.IP, int) {
	h, p, err := net.SplitHostPort(hostport)
	if err != nil {
		panic(err)
	}
	port, err := strconv.Atoi(p)
	if err != nil {
		panic(err)
	}
	return net.ParseIP(h), port
}

func ipAddr(addr string) *net.IPAddr {
	return &net.IPAddr{IP: net.ParseIP(addr)}
}

func tcpAddr(addr string) *net.TCPAddr {
	ip, port := splitIPPort(addr)
	return &net.TCPAddr{IP: ip, Port: port}
}

func udpAddr(addr string) *net.UDPAddr {
	ip, port := splitIPPort(addr)
	return &net.UDPAddr{IP: ip, Port: port}
}

func unixAddr(addr string) *net.UnixAddr {
	if !strings.HasPrefix(addr, "unix://") {
		panic("not a unix socket addr: " + addr)
	}
	return &net.UnixAddr{Net: "unix", Name: addr[len("unix://"):]}
}

func writeFile(path string, data []byte) {
	if err := os.MkdirAll(filepath.Dir(path), 0750); err != nil {
		panic(err)
	}
	if err := ioutil.WriteFile(path, data, 0640); err != nil {
		panic(err)
	}
}

func cleanDir(path string) {
	root := path
	err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		if path == root {
			return nil
		}
		return os.RemoveAll(path)
	})
	if err != nil {
		panic(err)
	}
}

func randomString(n int) string {
	s := ""
	for ; n > 0; n-- {
		s += "x"
	}
	return s
}

func metaPairs(n int, format string) string {
	var s []string
	for i := 0; i < n; i++ {
		switch format {
		case "json":
			s = append(s, fmt.Sprintf(`"%d":"%d"`, i, i))
		case "hcl":
			s = append(s, fmt.Sprintf(`"%d"="%d"`, i, i))
		default:
			panic("invalid format: " + format)
		}
	}
	switch format {
	case "json":
		return strings.Join(s, ",")
	case "hcl":
		return strings.Join(s, " ")
	default:
		panic("invalid format: " + format)
	}
}
