github.com/prestonp/nomad@v0.10.4/command/agent/config_parse_test.go (about)

     1  package agent
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  	"sort"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/hashicorp/nomad/helper"
    11  	"github.com/hashicorp/nomad/nomad/structs"
    12  	"github.com/hashicorp/nomad/nomad/structs/config"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  var basicConfig = &Config{
    17  	Region:      "foobar",
    18  	Datacenter:  "dc2",
    19  	NodeName:    "my-web",
    20  	DataDir:     "/tmp/nomad",
    21  	PluginDir:   "/tmp/nomad-plugins",
    22  	LogFile:     "/var/log/nomad.log",
    23  	LogLevel:    "ERR",
    24  	LogJson:     true,
    25  	BindAddr:    "192.168.0.1",
    26  	EnableDebug: true,
    27  	Ports: &Ports{
    28  		HTTP: 1234,
    29  		RPC:  2345,
    30  		Serf: 3456,
    31  	},
    32  	Addresses: &Addresses{
    33  		HTTP: "127.0.0.1",
    34  		RPC:  "127.0.0.2",
    35  		Serf: "127.0.0.3",
    36  	},
    37  	AdvertiseAddrs: &AdvertiseAddrs{
    38  		RPC:  "127.0.0.3",
    39  		Serf: "127.0.0.4",
    40  	},
    41  	Client: &ClientConfig{
    42  		Enabled:   true,
    43  		StateDir:  "/tmp/client-state",
    44  		AllocDir:  "/tmp/alloc",
    45  		Servers:   []string{"a.b.c:80", "127.0.0.1:1234"},
    46  		NodeClass: "linux-medium-64bit",
    47  		ServerJoin: &ServerJoin{
    48  			RetryJoin:        []string{"1.1.1.1", "2.2.2.2"},
    49  			RetryInterval:    time.Duration(15) * time.Second,
    50  			RetryIntervalHCL: "15s",
    51  			RetryMaxAttempts: 3,
    52  		},
    53  		Meta: map[string]string{
    54  			"foo": "bar",
    55  			"baz": "zip",
    56  		},
    57  		Options: map[string]string{
    58  			"foo": "bar",
    59  			"baz": "zip",
    60  		},
    61  		ChrootEnv: map[string]string{
    62  			"/opt/myapp/etc": "/etc",
    63  			"/opt/myapp/bin": "/bin",
    64  		},
    65  		NetworkInterface: "eth0",
    66  		NetworkSpeed:     100,
    67  		CpuCompute:       4444,
    68  		MemoryMB:         0,
    69  		MaxKillTimeout:   "10s",
    70  		ClientMinPort:    1000,
    71  		ClientMaxPort:    2000,
    72  		Reserved: &Resources{
    73  			CPU:           10,
    74  			MemoryMB:      10,
    75  			DiskMB:        10,
    76  			ReservedPorts: "1,100,10-12",
    77  		},
    78  		GCInterval:            6 * time.Second,
    79  		GCIntervalHCL:         "6s",
    80  		GCParallelDestroys:    6,
    81  		GCDiskUsageThreshold:  82,
    82  		GCInodeUsageThreshold: 91,
    83  		GCMaxAllocs:           50,
    84  		NoHostUUID:            helper.BoolToPtr(false),
    85  		DisableRemoteExec:     true,
    86  		HostVolumes: []*structs.ClientHostVolumeConfig{
    87  			{Name: "tmp", Path: "/tmp"},
    88  		},
    89  	},
    90  	Server: &ServerConfig{
    91  		Enabled:                true,
    92  		AuthoritativeRegion:    "foobar",
    93  		BootstrapExpect:        5,
    94  		DataDir:                "/tmp/data",
    95  		ProtocolVersion:        3,
    96  		RaftProtocol:           3,
    97  		NumSchedulers:          helper.IntToPtr(2),
    98  		EnabledSchedulers:      []string{"test"},
    99  		NodeGCThreshold:        "12h",
   100  		EvalGCThreshold:        "12h",
   101  		JobGCInterval:          "3m",
   102  		JobGCThreshold:         "12h",
   103  		DeploymentGCThreshold:  "12h",
   104  		HeartbeatGrace:         30 * time.Second,
   105  		HeartbeatGraceHCL:      "30s",
   106  		MinHeartbeatTTL:        33 * time.Second,
   107  		MinHeartbeatTTLHCL:     "33s",
   108  		MaxHeartbeatsPerSecond: 11.0,
   109  		RetryJoin:              []string{"1.1.1.1", "2.2.2.2"},
   110  		StartJoin:              []string{"1.1.1.1", "2.2.2.2"},
   111  		RetryInterval:          15 * time.Second,
   112  		RetryIntervalHCL:       "15s",
   113  		RejoinAfterLeave:       true,
   114  		RetryMaxAttempts:       3,
   115  		NonVotingServer:        true,
   116  		RedundancyZone:         "foo",
   117  		UpgradeVersion:         "0.8.0",
   118  		EncryptKey:             "abc",
   119  		ServerJoin: &ServerJoin{
   120  			RetryJoin:        []string{"1.1.1.1", "2.2.2.2"},
   121  			RetryInterval:    time.Duration(15) * time.Second,
   122  			RetryIntervalHCL: "15s",
   123  			RetryMaxAttempts: 3,
   124  		},
   125  	},
   126  	ACL: &ACLConfig{
   127  		Enabled:          true,
   128  		TokenTTL:         60 * time.Second,
   129  		TokenTTLHCL:      "60s",
   130  		PolicyTTL:        60 * time.Second,
   131  		PolicyTTLHCL:     "60s",
   132  		ReplicationToken: "foobar",
   133  	},
   134  	Telemetry: &Telemetry{
   135  		StatsiteAddr:               "127.0.0.1:1234",
   136  		StatsdAddr:                 "127.0.0.1:2345",
   137  		PrometheusMetrics:          true,
   138  		DisableHostname:            true,
   139  		UseNodeName:                false,
   140  		CollectionInterval:         "3s",
   141  		collectionInterval:         3 * time.Second,
   142  		PublishAllocationMetrics:   true,
   143  		PublishNodeMetrics:         true,
   144  		DisableTaggedMetrics:       true,
   145  		BackwardsCompatibleMetrics: true,
   146  	},
   147  	LeaveOnInt:                true,
   148  	LeaveOnTerm:               true,
   149  	EnableSyslog:              true,
   150  	SyslogFacility:            "LOCAL1",
   151  	DisableUpdateCheck:        helper.BoolToPtr(true),
   152  	DisableAnonymousSignature: true,
   153  	Consul: &config.ConsulConfig{
   154  		ServerServiceName:    "nomad",
   155  		ServerHTTPCheckName:  "nomad-server-http-health-check",
   156  		ServerSerfCheckName:  "nomad-server-serf-health-check",
   157  		ServerRPCCheckName:   "nomad-server-rpc-health-check",
   158  		ClientServiceName:    "nomad-client",
   159  		ClientHTTPCheckName:  "nomad-client-http-health-check",
   160  		Addr:                 "127.0.0.1:9500",
   161  		AllowUnauthenticated: &trueValue,
   162  		Token:                "token1",
   163  		Auth:                 "username:pass",
   164  		EnableSSL:            &trueValue,
   165  		VerifySSL:            &trueValue,
   166  		CAFile:               "/path/to/ca/file",
   167  		CertFile:             "/path/to/cert/file",
   168  		KeyFile:              "/path/to/key/file",
   169  		ServerAutoJoin:       &trueValue,
   170  		ClientAutoJoin:       &trueValue,
   171  		AutoAdvertise:        &trueValue,
   172  		ChecksUseAdvertise:   &trueValue,
   173  		Timeout:              5 * time.Second,
   174  	},
   175  	Vault: &config.VaultConfig{
   176  		Addr:                 "127.0.0.1:9500",
   177  		AllowUnauthenticated: &trueValue,
   178  		ConnectionRetryIntv:  config.DefaultVaultConnectRetryIntv,
   179  		Enabled:              &falseValue,
   180  		Role:                 "test_role",
   181  		TLSCaFile:            "/path/to/ca/file",
   182  		TLSCaPath:            "/path/to/ca",
   183  		TLSCertFile:          "/path/to/cert/file",
   184  		TLSKeyFile:           "/path/to/key/file",
   185  		TLSServerName:        "foobar",
   186  		TLSSkipVerify:        &trueValue,
   187  		TaskTokenTTL:         "1s",
   188  		Token:                "12345",
   189  	},
   190  	TLSConfig: &config.TLSConfig{
   191  		EnableHTTP:                  true,
   192  		EnableRPC:                   true,
   193  		VerifyServerHostname:        true,
   194  		CAFile:                      "foo",
   195  		CertFile:                    "bar",
   196  		KeyFile:                     "pipe",
   197  		RPCUpgradeMode:              true,
   198  		VerifyHTTPSClient:           true,
   199  		TLSPreferServerCipherSuites: true,
   200  		TLSCipherSuites:             "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
   201  		TLSMinVersion:               "tls12",
   202  	},
   203  	HTTPAPIResponseHeaders: map[string]string{
   204  		"Access-Control-Allow-Origin": "*",
   205  	},
   206  	Sentinel: &config.SentinelConfig{
   207  		Imports: []*config.SentinelImport{
   208  			{
   209  				Name: "foo",
   210  				Path: "foo",
   211  				Args: []string{"a", "b", "c"},
   212  			},
   213  			{
   214  				Name: "bar",
   215  				Path: "bar",
   216  				Args: []string{"x", "y", "z"},
   217  			},
   218  		},
   219  	},
   220  	Autopilot: &config.AutopilotConfig{
   221  		CleanupDeadServers:         &trueValue,
   222  		ServerStabilizationTime:    23057 * time.Second,
   223  		ServerStabilizationTimeHCL: "23057s",
   224  		LastContactThreshold:       12705 * time.Second,
   225  		LastContactThresholdHCL:    "12705s",
   226  		MaxTrailingLogs:            17849,
   227  		EnableRedundancyZones:      &trueValue,
   228  		DisableUpgradeMigration:    &trueValue,
   229  		EnableCustomUpgrades:       &trueValue,
   230  	},
   231  	Plugins: []*config.PluginConfig{
   232  		{
   233  			Name: "docker",
   234  			Args: []string{"foo", "bar"},
   235  			Config: map[string]interface{}{
   236  				"foo": "bar",
   237  				"nested": []map[string]interface{}{
   238  					{
   239  						"bam": 2,
   240  					},
   241  				},
   242  			},
   243  		},
   244  		{
   245  			Name: "exec",
   246  			Config: map[string]interface{}{
   247  				"foo": true,
   248  			},
   249  		},
   250  	},
   251  }
   252  
   253  var pluginConfig = &Config{
   254  	Region:         "",
   255  	Datacenter:     "",
   256  	NodeName:       "",
   257  	DataDir:        "",
   258  	PluginDir:      "",
   259  	LogLevel:       "",
   260  	BindAddr:       "",
   261  	EnableDebug:    false,
   262  	Ports:          nil,
   263  	Addresses:      nil,
   264  	AdvertiseAddrs: nil,
   265  	Client: &ClientConfig{
   266  		Enabled:               false,
   267  		StateDir:              "",
   268  		AllocDir:              "",
   269  		Servers:               nil,
   270  		NodeClass:             "",
   271  		Meta:                  nil,
   272  		Options:               nil,
   273  		ChrootEnv:             nil,
   274  		NetworkInterface:      "",
   275  		NetworkSpeed:          0,
   276  		CpuCompute:            0,
   277  		MemoryMB:              5555,
   278  		MaxKillTimeout:        "",
   279  		ClientMinPort:         0,
   280  		ClientMaxPort:         0,
   281  		Reserved:              nil,
   282  		GCInterval:            0,
   283  		GCParallelDestroys:    0,
   284  		GCDiskUsageThreshold:  0,
   285  		GCInodeUsageThreshold: 0,
   286  		GCMaxAllocs:           0,
   287  		NoHostUUID:            nil,
   288  	},
   289  	Server:                    nil,
   290  	ACL:                       nil,
   291  	Telemetry:                 nil,
   292  	LeaveOnInt:                false,
   293  	LeaveOnTerm:               false,
   294  	EnableSyslog:              false,
   295  	SyslogFacility:            "",
   296  	DisableUpdateCheck:        nil,
   297  	DisableAnonymousSignature: false,
   298  	Consul:                    nil,
   299  	Vault:                     nil,
   300  	TLSConfig:                 nil,
   301  	HTTPAPIResponseHeaders:    map[string]string{},
   302  	Sentinel:                  nil,
   303  	Plugins: []*config.PluginConfig{
   304  		{
   305  			Name: "docker",
   306  			Config: map[string]interface{}{
   307  				"allow_privileged": true,
   308  			},
   309  		},
   310  		{
   311  			Name: "raw_exec",
   312  			Config: map[string]interface{}{
   313  				"enabled": true,
   314  			},
   315  		},
   316  	},
   317  }
   318  
   319  var nonoptConfig = &Config{
   320  	Region:         "",
   321  	Datacenter:     "",
   322  	NodeName:       "",
   323  	DataDir:        "",
   324  	PluginDir:      "",
   325  	LogLevel:       "",
   326  	BindAddr:       "",
   327  	EnableDebug:    false,
   328  	Ports:          nil,
   329  	Addresses:      nil,
   330  	AdvertiseAddrs: nil,
   331  	Client: &ClientConfig{
   332  		Enabled:               false,
   333  		StateDir:              "",
   334  		AllocDir:              "",
   335  		Servers:               nil,
   336  		NodeClass:             "",
   337  		Meta:                  nil,
   338  		Options:               nil,
   339  		ChrootEnv:             nil,
   340  		NetworkInterface:      "",
   341  		NetworkSpeed:          0,
   342  		CpuCompute:            0,
   343  		MemoryMB:              5555,
   344  		MaxKillTimeout:        "",
   345  		ClientMinPort:         0,
   346  		ClientMaxPort:         0,
   347  		Reserved:              nil,
   348  		GCInterval:            0,
   349  		GCParallelDestroys:    0,
   350  		GCDiskUsageThreshold:  0,
   351  		GCInodeUsageThreshold: 0,
   352  		GCMaxAllocs:           0,
   353  		NoHostUUID:            nil,
   354  	},
   355  	Server:                    nil,
   356  	ACL:                       nil,
   357  	Telemetry:                 nil,
   358  	LeaveOnInt:                false,
   359  	LeaveOnTerm:               false,
   360  	EnableSyslog:              false,
   361  	SyslogFacility:            "",
   362  	DisableUpdateCheck:        nil,
   363  	DisableAnonymousSignature: false,
   364  	Consul:                    nil,
   365  	Vault:                     nil,
   366  	TLSConfig:                 nil,
   367  	HTTPAPIResponseHeaders:    map[string]string{},
   368  	Sentinel:                  nil,
   369  }
   370  
   371  func TestConfig_Parse(t *testing.T) {
   372  	t.Parallel()
   373  
   374  	basicConfig.addDefaults()
   375  	pluginConfig.addDefaults()
   376  	nonoptConfig.addDefaults()
   377  
   378  	cases := []struct {
   379  		File   string
   380  		Result *Config
   381  		Err    bool
   382  	}{
   383  		{
   384  			"basic.hcl",
   385  			basicConfig,
   386  			false,
   387  		},
   388  		{
   389  			"basic.json",
   390  			basicConfig,
   391  			false,
   392  		},
   393  		{
   394  			"plugin.hcl",
   395  			pluginConfig,
   396  			false,
   397  		},
   398  		{
   399  			"plugin.json",
   400  			pluginConfig,
   401  			false,
   402  		},
   403  		{
   404  			"non-optional.hcl",
   405  			nonoptConfig,
   406  			false,
   407  		},
   408  	}
   409  
   410  	for _, tc := range cases {
   411  		t.Run(tc.File, func(t *testing.T) {
   412  			require := require.New(t)
   413  			path, err := filepath.Abs(filepath.Join("./testdata", tc.File))
   414  			require.NoError(err)
   415  
   416  			actual, err := ParseConfigFile(path)
   417  			require.NoError(err)
   418  
   419  			// ParseConfig used to re-merge defaults for these three objects,
   420  			// despite them already being merged in LoadConfig. The test structs
   421  			// expect these defaults to be set, but not the DefaultConfig
   422  			// defaults, which include additional settings
   423  			oldDefault := &Config{
   424  				Consul:    config.DefaultConsulConfig(),
   425  				Vault:     config.DefaultVaultConfig(),
   426  				Autopilot: config.DefaultAutopilotConfig(),
   427  			}
   428  			actual = oldDefault.Merge(actual)
   429  
   430  			//panic(fmt.Sprintf("first: %+v \n second: %+v", actual.TLSConfig, tc.Result.TLSConfig))
   431  			require.EqualValues(tc.Result, removeHelperAttributes(actual))
   432  		})
   433  	}
   434  }
   435  
   436  // In order to compare the Config struct after parsing, and from generating what
   437  // is expected in the test, we need to remove helper attributes that are
   438  // instantiated in the process of parsing the configuration
   439  func removeHelperAttributes(c *Config) *Config {
   440  	if c.TLSConfig != nil {
   441  		c.TLSConfig.KeyLoader = nil
   442  	}
   443  	return c
   444  }
   445  
   446  func (c *Config) addDefaults() {
   447  	if c.Client == nil {
   448  		c.Client = &ClientConfig{}
   449  	}
   450  	if c.Client.ServerJoin == nil {
   451  		c.Client.ServerJoin = &ServerJoin{}
   452  	}
   453  	if c.ACL == nil {
   454  		c.ACL = &ACLConfig{}
   455  	}
   456  	if c.Consul == nil {
   457  		c.Consul = config.DefaultConsulConfig()
   458  	}
   459  	if c.Autopilot == nil {
   460  		c.Autopilot = config.DefaultAutopilotConfig()
   461  	}
   462  	if c.Vault == nil {
   463  		c.Vault = config.DefaultVaultConfig()
   464  	}
   465  	if c.Telemetry == nil {
   466  		c.Telemetry = &Telemetry{}
   467  	}
   468  	if c.Server == nil {
   469  		c.Server = &ServerConfig{}
   470  	}
   471  	if c.Server.ServerJoin == nil {
   472  		c.Server.ServerJoin = &ServerJoin{}
   473  	}
   474  }
   475  
   476  // Tests for a panic parsing json with an object of exactly
   477  // length 1 described in
   478  // https://github.com/hashicorp/nomad/issues/1290
   479  func TestConfig_ParsePanic(t *testing.T) {
   480  	c, err := ParseConfigFile("./testdata/obj-len-one.hcl")
   481  	if err != nil {
   482  		t.Fatalf("parse error: %s\n", err)
   483  	}
   484  
   485  	d, err := ParseConfigFile("./testdata/obj-len-one.json")
   486  	if err != nil {
   487  		t.Fatalf("parse error: %s\n", err)
   488  	}
   489  
   490  	require.EqualValues(t, c, d)
   491  }
   492  
   493  // Top level keys left by hcl when parsing slices in the config
   494  // structure should not be unexpected
   495  func TestConfig_ParseSliceExtra(t *testing.T) {
   496  	c, err := ParseConfigFile("./testdata/config-slices.json")
   497  	require.NoError(t, err)
   498  
   499  	opt := map[string]string{"o0": "foo", "o1": "bar"}
   500  	meta := map[string]string{"m0": "foo", "m1": "bar", "m2": "true", "m3": "1.2"}
   501  	env := map[string]string{"e0": "baz"}
   502  	srv := []string{"foo", "bar"}
   503  
   504  	require.EqualValues(t, opt, c.Client.Options)
   505  	require.EqualValues(t, meta, c.Client.Meta)
   506  	require.EqualValues(t, env, c.Client.ChrootEnv)
   507  	require.EqualValues(t, srv, c.Client.Servers)
   508  	require.EqualValues(t, srv, c.Server.EnabledSchedulers)
   509  	require.EqualValues(t, srv, c.Server.StartJoin)
   510  	require.EqualValues(t, srv, c.Server.RetryJoin)
   511  
   512  	// the alt format is also accepted by hcl as valid config data
   513  	c, err = ParseConfigFile("./testdata/config-slices-alt.json")
   514  	require.NoError(t, err)
   515  
   516  	require.EqualValues(t, opt, c.Client.Options)
   517  	require.EqualValues(t, meta, c.Client.Meta)
   518  	require.EqualValues(t, env, c.Client.ChrootEnv)
   519  	require.EqualValues(t, srv, c.Client.Servers)
   520  	require.EqualValues(t, srv, c.Server.EnabledSchedulers)
   521  	require.EqualValues(t, srv, c.Server.StartJoin)
   522  	require.EqualValues(t, srv, c.Server.RetryJoin)
   523  
   524  	// small files keep more extra keys than large ones
   525  	_, err = ParseConfigFile("./testdata/obj-len-one-server.json")
   526  	require.NoError(t, err)
   527  }
   528  
   529  var sample0 = &Config{
   530  	Region:     "global",
   531  	Datacenter: "dc1",
   532  	DataDir:    "/opt/data/nomad/data",
   533  	LogLevel:   "INFO",
   534  	BindAddr:   "0.0.0.0",
   535  	AdvertiseAddrs: &AdvertiseAddrs{
   536  		HTTP: "host.example.com",
   537  		RPC:  "host.example.com",
   538  		Serf: "host.example.com",
   539  	},
   540  	Client: &ClientConfig{ServerJoin: &ServerJoin{}},
   541  	Server: &ServerConfig{
   542  		Enabled:         true,
   543  		BootstrapExpect: 3,
   544  		RetryJoin:       []string{"10.0.0.101", "10.0.0.102", "10.0.0.103"},
   545  		EncryptKey:      "sHck3WL6cxuhuY7Mso9BHA==",
   546  		ServerJoin:      &ServerJoin{},
   547  	},
   548  	ACL: &ACLConfig{
   549  		Enabled: true,
   550  	},
   551  	Telemetry: &Telemetry{
   552  		PrometheusMetrics:        true,
   553  		DisableHostname:          true,
   554  		CollectionInterval:       "60s",
   555  		collectionInterval:       60 * time.Second,
   556  		PublishAllocationMetrics: true,
   557  		PublishNodeMetrics:       true,
   558  	},
   559  	LeaveOnInt:     true,
   560  	LeaveOnTerm:    true,
   561  	EnableSyslog:   true,
   562  	SyslogFacility: "LOCAL0",
   563  	Consul: &config.ConsulConfig{
   564  		Token:          "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
   565  		ServerAutoJoin: helper.BoolToPtr(false),
   566  		ClientAutoJoin: helper.BoolToPtr(false),
   567  	},
   568  	Vault: &config.VaultConfig{
   569  		Enabled: helper.BoolToPtr(true),
   570  		Role:    "nomad-cluster",
   571  		Addr:    "http://host.example.com:8200",
   572  	},
   573  	TLSConfig: &config.TLSConfig{
   574  		EnableHTTP:           true,
   575  		EnableRPC:            true,
   576  		VerifyServerHostname: true,
   577  		CAFile:               "/opt/data/nomad/certs/nomad-ca.pem",
   578  		CertFile:             "/opt/data/nomad/certs/server.pem",
   579  		KeyFile:              "/opt/data/nomad/certs/server-key.pem",
   580  	},
   581  	Autopilot: &config.AutopilotConfig{
   582  		CleanupDeadServers: helper.BoolToPtr(true),
   583  	},
   584  }
   585  
   586  func TestConfig_ParseSample0(t *testing.T) {
   587  	c, err := ParseConfigFile("./testdata/sample0.json")
   588  	require.NoError(t, err)
   589  	require.EqualValues(t, sample0, c)
   590  }
   591  
   592  var sample1 = &Config{
   593  	Region:     "global",
   594  	Datacenter: "dc1",
   595  	DataDir:    "/opt/data/nomad/data",
   596  	LogLevel:   "INFO",
   597  	BindAddr:   "0.0.0.0",
   598  	AdvertiseAddrs: &AdvertiseAddrs{
   599  		HTTP: "host.example.com",
   600  		RPC:  "host.example.com",
   601  		Serf: "host.example.com",
   602  	},
   603  	Client: &ClientConfig{ServerJoin: &ServerJoin{}},
   604  	Server: &ServerConfig{
   605  		Enabled:         true,
   606  		BootstrapExpect: 3,
   607  		RetryJoin:       []string{"10.0.0.101", "10.0.0.102", "10.0.0.103"},
   608  		EncryptKey:      "sHck3WL6cxuhuY7Mso9BHA==",
   609  		ServerJoin:      &ServerJoin{},
   610  	},
   611  	ACL: &ACLConfig{
   612  		Enabled: true,
   613  	},
   614  	Telemetry: &Telemetry{
   615  		PrometheusMetrics:        true,
   616  		DisableHostname:          true,
   617  		CollectionInterval:       "60s",
   618  		collectionInterval:       60 * time.Second,
   619  		PublishAllocationMetrics: true,
   620  		PublishNodeMetrics:       true,
   621  	},
   622  	LeaveOnInt:     true,
   623  	LeaveOnTerm:    true,
   624  	EnableSyslog:   true,
   625  	SyslogFacility: "LOCAL0",
   626  	Consul: &config.ConsulConfig{
   627  		EnableSSL:      helper.BoolToPtr(true),
   628  		Token:          "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
   629  		ServerAutoJoin: helper.BoolToPtr(false),
   630  		ClientAutoJoin: helper.BoolToPtr(false),
   631  	},
   632  	Vault: &config.VaultConfig{
   633  		Enabled: helper.BoolToPtr(true),
   634  		Role:    "nomad-cluster",
   635  		Addr:    "http://host.example.com:8200",
   636  	},
   637  	TLSConfig: &config.TLSConfig{
   638  		EnableHTTP:           true,
   639  		EnableRPC:            true,
   640  		VerifyServerHostname: true,
   641  		CAFile:               "/opt/data/nomad/certs/nomad-ca.pem",
   642  		CertFile:             "/opt/data/nomad/certs/server.pem",
   643  		KeyFile:              "/opt/data/nomad/certs/server-key.pem",
   644  	},
   645  	Autopilot: &config.AutopilotConfig{
   646  		CleanupDeadServers: helper.BoolToPtr(true),
   647  	},
   648  }
   649  
   650  func TestConfig_ParseDir(t *testing.T) {
   651  	c, err := LoadConfig("./testdata/sample1")
   652  	require.NoError(t, err)
   653  
   654  	// LoadConfig Merges all the config files in testdata/sample1, which makes empty
   655  	// maps & slices rather than nil, so set those
   656  	require.Empty(t, c.Client.Options)
   657  	c.Client.Options = nil
   658  	require.Empty(t, c.Client.Meta)
   659  	c.Client.Meta = nil
   660  	require.Empty(t, c.Client.ChrootEnv)
   661  	c.Client.ChrootEnv = nil
   662  	require.Empty(t, c.Server.StartJoin)
   663  	c.Server.StartJoin = nil
   664  	require.Empty(t, c.HTTPAPIResponseHeaders)
   665  	c.HTTPAPIResponseHeaders = nil
   666  
   667  	// LoadDir lists the config files
   668  	expectedFiles := []string{
   669  		"testdata/sample1/sample0.json",
   670  		"testdata/sample1/sample1.json",
   671  		"testdata/sample1/sample2.hcl",
   672  	}
   673  	require.Equal(t, expectedFiles, c.Files)
   674  	c.Files = nil
   675  
   676  	require.EqualValues(t, sample1, c)
   677  }
   678  
   679  // TestConfig_ParseDir_Matches_IndividualParsing asserts
   680  // that parsing a directory config is the equivalent of
   681  // parsing individual files in any order
   682  func TestConfig_ParseDir_Matches_IndividualParsing(t *testing.T) {
   683  	dirConfig, err := LoadConfig("./testdata/sample1")
   684  	require.NoError(t, err)
   685  
   686  	dirConfig = DefaultConfig().Merge(dirConfig)
   687  
   688  	files := []string{
   689  		"testdata/sample1/sample0.json",
   690  		"testdata/sample1/sample1.json",
   691  		"testdata/sample1/sample2.hcl",
   692  	}
   693  
   694  	for _, perm := range permutations(files) {
   695  		t.Run(fmt.Sprintf("permutation %v", perm), func(t *testing.T) {
   696  			config := DefaultConfig()
   697  
   698  			for _, f := range perm {
   699  				fc, err := LoadConfig(f)
   700  				require.NoError(t, err)
   701  
   702  				config = config.Merge(fc)
   703  			}
   704  
   705  			// sort files to get stable view
   706  			sort.Strings(config.Files)
   707  			sort.Strings(dirConfig.Files)
   708  
   709  			require.EqualValues(t, dirConfig, config)
   710  		})
   711  	}
   712  
   713  }
   714  
   715  // https://stackoverflow.com/a/30226442
   716  func permutations(arr []string) [][]string {
   717  	var helper func([]string, int)
   718  	res := [][]string{}
   719  
   720  	helper = func(arr []string, n int) {
   721  		if n == 1 {
   722  			tmp := make([]string, len(arr))
   723  			copy(tmp, arr)
   724  			res = append(res, tmp)
   725  		} else {
   726  			for i := 0; i < n; i++ {
   727  				helper(arr, n-1)
   728  				if n%2 == 1 {
   729  					tmp := arr[i]
   730  					arr[i] = arr[n-1]
   731  					arr[n-1] = tmp
   732  				} else {
   733  					tmp := arr[0]
   734  					arr[0] = arr[n-1]
   735  					arr[n-1] = tmp
   736  				}
   737  			}
   738  		}
   739  	}
   740  	helper(arr, len(arr))
   741  	return res
   742  }