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