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