github.com/karlem/nomad@v0.10.2-rc1/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  		Token:               "token1",
   162  		Auth:                "username:pass",
   163  		EnableSSL:           &trueValue,
   164  		VerifySSL:           &trueValue,
   165  		CAFile:              "/path/to/ca/file",
   166  		CertFile:            "/path/to/cert/file",
   167  		KeyFile:             "/path/to/key/file",
   168  		ServerAutoJoin:      &trueValue,
   169  		ClientAutoJoin:      &trueValue,
   170  		AutoAdvertise:       &trueValue,
   171  		ChecksUseAdvertise:  &trueValue,
   172  		Timeout:             5 * time.Second,
   173  	},
   174  	Vault: &config.VaultConfig{
   175  		Addr:                 "127.0.0.1:9500",
   176  		AllowUnauthenticated: &trueValue,
   177  		ConnectionRetryIntv:  config.DefaultVaultConnectRetryIntv,
   178  		Enabled:              &falseValue,
   179  		Role:                 "test_role",
   180  		TLSCaFile:            "/path/to/ca/file",
   181  		TLSCaPath:            "/path/to/ca",
   182  		TLSCertFile:          "/path/to/cert/file",
   183  		TLSKeyFile:           "/path/to/key/file",
   184  		TLSServerName:        "foobar",
   185  		TLSSkipVerify:        &trueValue,
   186  		TaskTokenTTL:         "1s",
   187  		Token:                "12345",
   188  	},
   189  	TLSConfig: &config.TLSConfig{
   190  		EnableHTTP:                  true,
   191  		EnableRPC:                   true,
   192  		VerifyServerHostname:        true,
   193  		CAFile:                      "foo",
   194  		CertFile:                    "bar",
   195  		KeyFile:                     "pipe",
   196  		RPCUpgradeMode:              true,
   197  		VerifyHTTPSClient:           true,
   198  		TLSPreferServerCipherSuites: true,
   199  		TLSCipherSuites:             "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
   200  		TLSMinVersion:               "tls12",
   201  	},
   202  	HTTPAPIResponseHeaders: map[string]string{
   203  		"Access-Control-Allow-Origin": "*",
   204  	},
   205  	Sentinel: &config.SentinelConfig{
   206  		Imports: []*config.SentinelImport{
   207  			{
   208  				Name: "foo",
   209  				Path: "foo",
   210  				Args: []string{"a", "b", "c"},
   211  			},
   212  			{
   213  				Name: "bar",
   214  				Path: "bar",
   215  				Args: []string{"x", "y", "z"},
   216  			},
   217  		},
   218  	},
   219  	Autopilot: &config.AutopilotConfig{
   220  		CleanupDeadServers:         &trueValue,
   221  		ServerStabilizationTime:    23057 * time.Second,
   222  		ServerStabilizationTimeHCL: "23057s",
   223  		LastContactThreshold:       12705 * time.Second,
   224  		LastContactThresholdHCL:    "12705s",
   225  		MaxTrailingLogs:            17849,
   226  		EnableRedundancyZones:      &trueValue,
   227  		DisableUpgradeMigration:    &trueValue,
   228  		EnableCustomUpgrades:       &trueValue,
   229  	},
   230  	Plugins: []*config.PluginConfig{
   231  		{
   232  			Name: "docker",
   233  			Args: []string{"foo", "bar"},
   234  			Config: map[string]interface{}{
   235  				"foo": "bar",
   236  				"nested": []map[string]interface{}{
   237  					{
   238  						"bam": 2,
   239  					},
   240  				},
   241  			},
   242  		},
   243  		{
   244  			Name: "exec",
   245  			Config: map[string]interface{}{
   246  				"foo": true,
   247  			},
   248  		},
   249  	},
   250  }
   251  
   252  var pluginConfig = &Config{
   253  	Region:         "",
   254  	Datacenter:     "",
   255  	NodeName:       "",
   256  	DataDir:        "",
   257  	PluginDir:      "",
   258  	LogLevel:       "",
   259  	BindAddr:       "",
   260  	EnableDebug:    false,
   261  	Ports:          nil,
   262  	Addresses:      nil,
   263  	AdvertiseAddrs: nil,
   264  	Client: &ClientConfig{
   265  		Enabled:               false,
   266  		StateDir:              "",
   267  		AllocDir:              "",
   268  		Servers:               nil,
   269  		NodeClass:             "",
   270  		Meta:                  nil,
   271  		Options:               nil,
   272  		ChrootEnv:             nil,
   273  		NetworkInterface:      "",
   274  		NetworkSpeed:          0,
   275  		CpuCompute:            0,
   276  		MemoryMB:              5555,
   277  		MaxKillTimeout:        "",
   278  		ClientMinPort:         0,
   279  		ClientMaxPort:         0,
   280  		Reserved:              nil,
   281  		GCInterval:            0,
   282  		GCParallelDestroys:    0,
   283  		GCDiskUsageThreshold:  0,
   284  		GCInodeUsageThreshold: 0,
   285  		GCMaxAllocs:           0,
   286  		NoHostUUID:            nil,
   287  	},
   288  	Server:                    nil,
   289  	ACL:                       nil,
   290  	Telemetry:                 nil,
   291  	LeaveOnInt:                false,
   292  	LeaveOnTerm:               false,
   293  	EnableSyslog:              false,
   294  	SyslogFacility:            "",
   295  	DisableUpdateCheck:        nil,
   296  	DisableAnonymousSignature: false,
   297  	Consul:                    nil,
   298  	Vault:                     nil,
   299  	TLSConfig:                 nil,
   300  	HTTPAPIResponseHeaders:    map[string]string{},
   301  	Sentinel:                  nil,
   302  	Plugins: []*config.PluginConfig{
   303  		{
   304  			Name: "docker",
   305  			Config: map[string]interface{}{
   306  				"allow_privileged": true,
   307  			},
   308  		},
   309  		{
   310  			Name: "raw_exec",
   311  			Config: map[string]interface{}{
   312  				"enabled": true,
   313  			},
   314  		},
   315  	},
   316  }
   317  
   318  var nonoptConfig = &Config{
   319  	Region:         "",
   320  	Datacenter:     "",
   321  	NodeName:       "",
   322  	DataDir:        "",
   323  	PluginDir:      "",
   324  	LogLevel:       "",
   325  	BindAddr:       "",
   326  	EnableDebug:    false,
   327  	Ports:          nil,
   328  	Addresses:      nil,
   329  	AdvertiseAddrs: nil,
   330  	Client: &ClientConfig{
   331  		Enabled:               false,
   332  		StateDir:              "",
   333  		AllocDir:              "",
   334  		Servers:               nil,
   335  		NodeClass:             "",
   336  		Meta:                  nil,
   337  		Options:               nil,
   338  		ChrootEnv:             nil,
   339  		NetworkInterface:      "",
   340  		NetworkSpeed:          0,
   341  		CpuCompute:            0,
   342  		MemoryMB:              5555,
   343  		MaxKillTimeout:        "",
   344  		ClientMinPort:         0,
   345  		ClientMaxPort:         0,
   346  		Reserved:              nil,
   347  		GCInterval:            0,
   348  		GCParallelDestroys:    0,
   349  		GCDiskUsageThreshold:  0,
   350  		GCInodeUsageThreshold: 0,
   351  		GCMaxAllocs:           0,
   352  		NoHostUUID:            nil,
   353  	},
   354  	Server:                    nil,
   355  	ACL:                       nil,
   356  	Telemetry:                 nil,
   357  	LeaveOnInt:                false,
   358  	LeaveOnTerm:               false,
   359  	EnableSyslog:              false,
   360  	SyslogFacility:            "",
   361  	DisableUpdateCheck:        nil,
   362  	DisableAnonymousSignature: false,
   363  	Consul:                    nil,
   364  	Vault:                     nil,
   365  	TLSConfig:                 nil,
   366  	HTTPAPIResponseHeaders:    map[string]string{},
   367  	Sentinel:                  nil,
   368  }
   369  
   370  func TestConfig_Parse(t *testing.T) {
   371  	t.Parallel()
   372  
   373  	basicConfig.addDefaults()
   374  	pluginConfig.addDefaults()
   375  	nonoptConfig.addDefaults()
   376  
   377  	cases := []struct {
   378  		File   string
   379  		Result *Config
   380  		Err    bool
   381  	}{
   382  		{
   383  			"basic.hcl",
   384  			basicConfig,
   385  			false,
   386  		},
   387  		{
   388  			"basic.json",
   389  			basicConfig,
   390  			false,
   391  		},
   392  		{
   393  			"plugin.hcl",
   394  			pluginConfig,
   395  			false,
   396  		},
   397  		{
   398  			"plugin.json",
   399  			pluginConfig,
   400  			false,
   401  		},
   402  		{
   403  			"non-optional.hcl",
   404  			nonoptConfig,
   405  			false,
   406  		},
   407  	}
   408  
   409  	for _, tc := range cases {
   410  		t.Run(tc.File, func(t *testing.T) {
   411  			require := require.New(t)
   412  			path, err := filepath.Abs(filepath.Join("./testdata", tc.File))
   413  			require.NoError(err)
   414  
   415  			actual, err := ParseConfigFile(path)
   416  			require.NoError(err)
   417  
   418  			// ParseConfig used to re-merge defaults for these three objects,
   419  			// despite them already being merged in LoadConfig. The test structs
   420  			// expect these defaults to be set, but not the DefaultConfig
   421  			// defaults, which include additional settings
   422  			oldDefault := &Config{
   423  				Consul:    config.DefaultConsulConfig(),
   424  				Vault:     config.DefaultVaultConfig(),
   425  				Autopilot: config.DefaultAutopilotConfig(),
   426  			}
   427  			actual = oldDefault.Merge(actual)
   428  
   429  			//panic(fmt.Sprintf("first: %+v \n second: %+v", actual.TLSConfig, tc.Result.TLSConfig))
   430  			require.EqualValues(tc.Result, removeHelperAttributes(actual))
   431  		})
   432  	}
   433  }
   434  
   435  // In order to compare the Config struct after parsing, and from generating what
   436  // is expected in the test, we need to remove helper attributes that are
   437  // instantiated in the process of parsing the configuration
   438  func removeHelperAttributes(c *Config) *Config {
   439  	if c.TLSConfig != nil {
   440  		c.TLSConfig.KeyLoader = nil
   441  	}
   442  	return c
   443  }
   444  
   445  func (c *Config) addDefaults() {
   446  	if c.Client == nil {
   447  		c.Client = &ClientConfig{}
   448  	}
   449  	if c.Client.ServerJoin == nil {
   450  		c.Client.ServerJoin = &ServerJoin{}
   451  	}
   452  	if c.ACL == nil {
   453  		c.ACL = &ACLConfig{}
   454  	}
   455  	if c.Consul == nil {
   456  		c.Consul = config.DefaultConsulConfig()
   457  	}
   458  	if c.Autopilot == nil {
   459  		c.Autopilot = config.DefaultAutopilotConfig()
   460  	}
   461  	if c.Vault == nil {
   462  		c.Vault = config.DefaultVaultConfig()
   463  	}
   464  	if c.Telemetry == nil {
   465  		c.Telemetry = &Telemetry{}
   466  	}
   467  	if c.Server == nil {
   468  		c.Server = &ServerConfig{}
   469  	}
   470  	if c.Server.ServerJoin == nil {
   471  		c.Server.ServerJoin = &ServerJoin{}
   472  	}
   473  }
   474  
   475  // Tests for a panic parsing json with an object of exactly
   476  // length 1 described in
   477  // https://github.com/hashicorp/nomad/issues/1290
   478  func TestConfig_ParsePanic(t *testing.T) {
   479  	c, err := ParseConfigFile("./testdata/obj-len-one.hcl")
   480  	if err != nil {
   481  		t.Fatalf("parse error: %s\n", err)
   482  	}
   483  
   484  	d, err := ParseConfigFile("./testdata/obj-len-one.json")
   485  	if err != nil {
   486  		t.Fatalf("parse error: %s\n", err)
   487  	}
   488  
   489  	require.EqualValues(t, c, d)
   490  }
   491  
   492  // Top level keys left by hcl when parsing slices in the config
   493  // structure should not be unexpected
   494  func TestConfig_ParseSliceExtra(t *testing.T) {
   495  	c, err := ParseConfigFile("./testdata/config-slices.json")
   496  	require.NoError(t, err)
   497  
   498  	opt := map[string]string{"o0": "foo", "o1": "bar"}
   499  	meta := map[string]string{"m0": "foo", "m1": "bar", "m2": "true", "m3": "1.2"}
   500  	env := map[string]string{"e0": "baz"}
   501  	srv := []string{"foo", "bar"}
   502  
   503  	require.EqualValues(t, opt, c.Client.Options)
   504  	require.EqualValues(t, meta, c.Client.Meta)
   505  	require.EqualValues(t, env, c.Client.ChrootEnv)
   506  	require.EqualValues(t, srv, c.Client.Servers)
   507  	require.EqualValues(t, srv, c.Server.EnabledSchedulers)
   508  	require.EqualValues(t, srv, c.Server.StartJoin)
   509  	require.EqualValues(t, srv, c.Server.RetryJoin)
   510  
   511  	// the alt format is also accepted by hcl as valid config data
   512  	c, err = ParseConfigFile("./testdata/config-slices-alt.json")
   513  	require.NoError(t, err)
   514  
   515  	require.EqualValues(t, opt, c.Client.Options)
   516  	require.EqualValues(t, meta, c.Client.Meta)
   517  	require.EqualValues(t, env, c.Client.ChrootEnv)
   518  	require.EqualValues(t, srv, c.Client.Servers)
   519  	require.EqualValues(t, srv, c.Server.EnabledSchedulers)
   520  	require.EqualValues(t, srv, c.Server.StartJoin)
   521  	require.EqualValues(t, srv, c.Server.RetryJoin)
   522  
   523  	// small files keep more extra keys than large ones
   524  	_, err = ParseConfigFile("./testdata/obj-len-one-server.json")
   525  	require.NoError(t, err)
   526  }
   527  
   528  var sample0 = &Config{
   529  	Region:     "global",
   530  	Datacenter: "dc1",
   531  	DataDir:    "/opt/data/nomad/data",
   532  	LogLevel:   "INFO",
   533  	BindAddr:   "0.0.0.0",
   534  	AdvertiseAddrs: &AdvertiseAddrs{
   535  		HTTP: "host.example.com",
   536  		RPC:  "host.example.com",
   537  		Serf: "host.example.com",
   538  	},
   539  	Client: &ClientConfig{ServerJoin: &ServerJoin{}},
   540  	Server: &ServerConfig{
   541  		Enabled:         true,
   542  		BootstrapExpect: 3,
   543  		RetryJoin:       []string{"10.0.0.101", "10.0.0.102", "10.0.0.103"},
   544  		EncryptKey:      "sHck3WL6cxuhuY7Mso9BHA==",
   545  		ServerJoin:      &ServerJoin{},
   546  	},
   547  	ACL: &ACLConfig{
   548  		Enabled: true,
   549  	},
   550  	Telemetry: &Telemetry{
   551  		PrometheusMetrics:        true,
   552  		DisableHostname:          true,
   553  		CollectionInterval:       "60s",
   554  		collectionInterval:       60 * time.Second,
   555  		PublishAllocationMetrics: true,
   556  		PublishNodeMetrics:       true,
   557  	},
   558  	LeaveOnInt:     true,
   559  	LeaveOnTerm:    true,
   560  	EnableSyslog:   true,
   561  	SyslogFacility: "LOCAL0",
   562  	Consul: &config.ConsulConfig{
   563  		Token:          "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
   564  		ServerAutoJoin: helper.BoolToPtr(false),
   565  		ClientAutoJoin: helper.BoolToPtr(false),
   566  	},
   567  	Vault: &config.VaultConfig{
   568  		Enabled: helper.BoolToPtr(true),
   569  		Role:    "nomad-cluster",
   570  		Addr:    "http://host.example.com:8200",
   571  	},
   572  	TLSConfig: &config.TLSConfig{
   573  		EnableHTTP:           true,
   574  		EnableRPC:            true,
   575  		VerifyServerHostname: true,
   576  		CAFile:               "/opt/data/nomad/certs/nomad-ca.pem",
   577  		CertFile:             "/opt/data/nomad/certs/server.pem",
   578  		KeyFile:              "/opt/data/nomad/certs/server-key.pem",
   579  	},
   580  	Autopilot: &config.AutopilotConfig{
   581  		CleanupDeadServers: helper.BoolToPtr(true),
   582  	},
   583  }
   584  
   585  func TestConfig_ParseSample0(t *testing.T) {
   586  	c, err := ParseConfigFile("./testdata/sample0.json")
   587  	require.NoError(t, err)
   588  	require.EqualValues(t, sample0, c)
   589  }
   590  
   591  var sample1 = &Config{
   592  	Region:     "global",
   593  	Datacenter: "dc1",
   594  	DataDir:    "/opt/data/nomad/data",
   595  	LogLevel:   "INFO",
   596  	BindAddr:   "0.0.0.0",
   597  	AdvertiseAddrs: &AdvertiseAddrs{
   598  		HTTP: "host.example.com",
   599  		RPC:  "host.example.com",
   600  		Serf: "host.example.com",
   601  	},
   602  	Client: &ClientConfig{ServerJoin: &ServerJoin{}},
   603  	Server: &ServerConfig{
   604  		Enabled:         true,
   605  		BootstrapExpect: 3,
   606  		RetryJoin:       []string{"10.0.0.101", "10.0.0.102", "10.0.0.103"},
   607  		EncryptKey:      "sHck3WL6cxuhuY7Mso9BHA==",
   608  		ServerJoin:      &ServerJoin{},
   609  	},
   610  	ACL: &ACLConfig{
   611  		Enabled: true,
   612  	},
   613  	Telemetry: &Telemetry{
   614  		PrometheusMetrics:        true,
   615  		DisableHostname:          true,
   616  		CollectionInterval:       "60s",
   617  		collectionInterval:       60 * time.Second,
   618  		PublishAllocationMetrics: true,
   619  		PublishNodeMetrics:       true,
   620  	},
   621  	LeaveOnInt:     true,
   622  	LeaveOnTerm:    true,
   623  	EnableSyslog:   true,
   624  	SyslogFacility: "LOCAL0",
   625  	Consul: &config.ConsulConfig{
   626  		EnableSSL:      helper.BoolToPtr(true),
   627  		Token:          "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
   628  		ServerAutoJoin: helper.BoolToPtr(false),
   629  		ClientAutoJoin: helper.BoolToPtr(false),
   630  	},
   631  	Vault: &config.VaultConfig{
   632  		Enabled: helper.BoolToPtr(true),
   633  		Role:    "nomad-cluster",
   634  		Addr:    "http://host.example.com:8200",
   635  	},
   636  	TLSConfig: &config.TLSConfig{
   637  		EnableHTTP:           true,
   638  		EnableRPC:            true,
   639  		VerifyServerHostname: true,
   640  		CAFile:               "/opt/data/nomad/certs/nomad-ca.pem",
   641  		CertFile:             "/opt/data/nomad/certs/server.pem",
   642  		KeyFile:              "/opt/data/nomad/certs/server-key.pem",
   643  	},
   644  	Autopilot: &config.AutopilotConfig{
   645  		CleanupDeadServers: helper.BoolToPtr(true),
   646  	},
   647  }
   648  
   649  func TestConfig_ParseDir(t *testing.T) {
   650  	c, err := LoadConfig("./testdata/sample1")
   651  	require.NoError(t, err)
   652  
   653  	// LoadConfig Merges all the config files in testdata/sample1, which makes empty
   654  	// maps & slices rather than nil, so set those
   655  	require.Empty(t, c.Client.Options)
   656  	c.Client.Options = nil
   657  	require.Empty(t, c.Client.Meta)
   658  	c.Client.Meta = nil
   659  	require.Empty(t, c.Client.ChrootEnv)
   660  	c.Client.ChrootEnv = nil
   661  	require.Empty(t, c.Server.StartJoin)
   662  	c.Server.StartJoin = nil
   663  	require.Empty(t, c.HTTPAPIResponseHeaders)
   664  	c.HTTPAPIResponseHeaders = nil
   665  
   666  	// LoadDir lists the config files
   667  	expectedFiles := []string{
   668  		"testdata/sample1/sample0.json",
   669  		"testdata/sample1/sample1.json",
   670  		"testdata/sample1/sample2.hcl",
   671  	}
   672  	require.Equal(t, expectedFiles, c.Files)
   673  	c.Files = nil
   674  
   675  	require.EqualValues(t, sample1, c)
   676  }
   677  
   678  // TestConfig_ParseDir_Matches_IndividualParsing asserts
   679  // that parsing a directory config is the equivalent of
   680  // parsing individual files in any order
   681  func TestConfig_ParseDir_Matches_IndividualParsing(t *testing.T) {
   682  	dirConfig, err := LoadConfig("./testdata/sample1")
   683  	require.NoError(t, err)
   684  
   685  	dirConfig = DefaultConfig().Merge(dirConfig)
   686  
   687  	files := []string{
   688  		"testdata/sample1/sample0.json",
   689  		"testdata/sample1/sample1.json",
   690  		"testdata/sample1/sample2.hcl",
   691  	}
   692  
   693  	for _, perm := range permutations(files) {
   694  		t.Run(fmt.Sprintf("permutation %v", perm), func(t *testing.T) {
   695  			config := DefaultConfig()
   696  
   697  			for _, f := range perm {
   698  				fc, err := LoadConfig(f)
   699  				require.NoError(t, err)
   700  
   701  				config = config.Merge(fc)
   702  			}
   703  
   704  			// sort files to get stable view
   705  			sort.Strings(config.Files)
   706  			sort.Strings(dirConfig.Files)
   707  
   708  			require.EqualValues(t, dirConfig, config)
   709  		})
   710  	}
   711  
   712  }
   713  
   714  // https://stackoverflow.com/a/30226442
   715  func permutations(arr []string) [][]string {
   716  	var helper func([]string, int)
   717  	res := [][]string{}
   718  
   719  	helper = func(arr []string, n int) {
   720  		if n == 1 {
   721  			tmp := make([]string, len(arr))
   722  			copy(tmp, arr)
   723  			res = append(res, tmp)
   724  		} else {
   725  			for i := 0; i < n; i++ {
   726  				helper(arr, n-1)
   727  				if n%2 == 1 {
   728  					tmp := arr[i]
   729  					arr[i] = arr[n-1]
   730  					arr[n-1] = tmp
   731  				} else {
   732  					tmp := arr[0]
   733  					arr[0] = arr[n-1]
   734  					arr[n-1] = tmp
   735  				}
   736  			}
   737  		}
   738  	}
   739  	helper(arr, len(arr))
   740  	return res
   741  }