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