auto-config: reduce awareness of config

This is a small step to allowing Agent to accept its dependencies
instead of creating them in New.

There were two fields in autoconfig.Config that were used exclusively
to load config. These were replaced with a single function, allowing us
to move LoadConfig back to the config package.

Also removed the WithX functions for building a Config. Since these were
simple assignment, it appeared we were not getting much value from them.
This commit is contained in:
Daniel Nephin 2020-08-12 12:35:30 -04:00
parent 1593ce2948
commit 45dae87ee7
7 changed files with 273 additions and 287 deletions

View File

@ -433,7 +433,7 @@ func New(options ...AgentOption) (*Agent, error) {
}
// parse the configuration and handle the error/warnings
config, warnings, err := autoconf.LoadConfig(flat.builderOpts, nil, flat.overrides...)
cfg, warnings, err := config.Load(flat.builderOpts, nil, flat.overrides...)
if err != nil {
return nil, err
}
@ -449,7 +449,7 @@ func New(options ...AgentOption) (*Agent, error) {
// set the config in the agent, this is just the preliminary configuration as we haven't
// loaded any auto-config sources yet.
a.config = config
a.config = cfg
// create the cache using the rate limiting settings from the config. Note that this means
// that these limits are not reloadable.
@ -457,15 +457,15 @@ func New(options ...AgentOption) (*Agent, error) {
if flat.logger == nil {
logConf := &logging.Config{
LogLevel: config.LogLevel,
LogJSON: config.LogJSON,
LogLevel: cfg.LogLevel,
LogJSON: cfg.LogJSON,
Name: logging.Agent,
EnableSyslog: config.EnableSyslog,
SyslogFacility: config.SyslogFacility,
LogFilePath: config.LogFile,
LogRotateDuration: config.LogRotateDuration,
LogRotateBytes: config.LogRotateBytes,
LogRotateMaxFiles: config.LogRotateMaxFiles,
EnableSyslog: cfg.EnableSyslog,
SyslogFacility: cfg.SyslogFacility,
LogFilePath: cfg.LogFile,
LogRotateDuration: cfg.LogRotateDuration,
LogRotateBytes: cfg.LogRotateBytes,
LogRotateMaxFiles: cfg.LogRotateMaxFiles,
}
a.logger, err = logging.Setup(logConf, flat.writers)
@ -477,7 +477,7 @@ func New(options ...AgentOption) (*Agent, error) {
}
if flat.initTelemetry {
memSink, err := lib.InitTelemetry(config.Telemetry)
memSink, err := lib.InitTelemetry(cfg.Telemetry)
if err != nil {
return nil, fmt.Errorf("Failed to initialize telemetry: %w", err)
}
@ -539,12 +539,14 @@ func New(options ...AgentOption) (*Agent, error) {
return nil, err
}
acConf := new(autoconf.Config).
WithDirectRPC(a.connPool).
WithBuilderOpts(flat.builderOpts).
WithLogger(a.logger).
WithOverrides(flat.overrides...).
WithCertMonitor(acCertMon)
acConf := autoconf.Config{
DirectRPC: a.connPool,
Logger: a.logger,
CertMonitor: acCertMon,
Loader: func(source config.Source) (*config.RuntimeConfig, []string, error) {
return config.Load(flat.builderOpts, source, flat.overrides...)
},
}
ac, err := autoconf.New(acConf)
if err != nil {
return nil, err

View File

@ -54,26 +54,20 @@ var (
// then we will need to add some locking here. I am deferring that for now
// to help ease the review of this already large PR.
type AutoConfig struct {
builderOpts config.BuilderOpts
acConfig Config
logger hclog.Logger
directRPC DirectRPC
waiter *lib.RetryWaiter
overrides []config.Source
certMonitor CertMonitor
config *config.RuntimeConfig
autoConfigResponse *pbautoconf.AutoConfigResponse
autoConfigSource config.Source
cancel context.CancelFunc
}
// New creates a new AutoConfig object for providing automatic
// Consul configuration.
func New(config *Config) (*AutoConfig, error) {
if config == nil {
return nil, fmt.Errorf("must provide a config struct")
}
if config.DirectRPC == nil {
// New creates a new AutoConfig object for providing automatic Consul configuration.
func New(config Config) (*AutoConfig, error) {
switch {
case config.Loader == nil:
return nil, fmt.Errorf("must provide a config loader")
case config.DirectRPC == nil:
return nil, fmt.Errorf("must provide a direct RPC delegate")
}
@ -84,27 +78,21 @@ func New(config *Config) (*AutoConfig, error) {
logger = logger.Named(logging.AutoConfig)
}
waiter := config.Waiter
if waiter == nil {
waiter = lib.NewRetryWaiter(1, 0, 10*time.Minute, lib.NewJitterRandomStagger(25))
if config.Waiter == nil {
config.Waiter = lib.NewRetryWaiter(1, 0, 10*time.Minute, lib.NewJitterRandomStagger(25))
}
ac := &AutoConfig{
builderOpts: config.BuilderOpts,
return &AutoConfig{
acConfig: config,
logger: logger,
directRPC: config.DirectRPC,
waiter: waiter,
overrides: config.Overrides,
certMonitor: config.CertMonitor,
}
return ac, nil
}, nil
}
// ReadConfig will parse the current configuration and inject any
// auto-config sources if present into the correct place in the parsing chain.
func (ac *AutoConfig) ReadConfig() (*config.RuntimeConfig, error) {
cfg, warnings, err := LoadConfig(ac.builderOpts, ac.autoConfigSource, ac.overrides...)
cfg, warnings, err := ac.acConfig.Loader(ac.autoConfigSource)
if err != nil {
return cfg, err
}
@ -377,7 +365,7 @@ func (ac *AutoConfig) getInitialConfigurationOnce(ctx context.Context, csr strin
}
ac.logger.Debug("making AutoConfig.InitialConfiguration RPC", "addr", addr.String())
if err = ac.directRPC.RPC(ac.config.Datacenter, ac.config.NodeName, &addr, "AutoConfig.InitialConfiguration", &request, &resp); err != nil {
if err = ac.acConfig.DirectRPC.RPC(ac.config.Datacenter, ac.config.NodeName, &addr, "AutoConfig.InitialConfiguration", &request, &resp); err != nil {
ac.logger.Error("AutoConfig.InitialConfiguration RPC failed", "addr", addr.String(), "error", err)
continue
}
@ -405,7 +393,7 @@ func (ac *AutoConfig) getInitialConfiguration(ctx context.Context) error {
}
// this resets the failures so that we will perform immediate request
wait := ac.waiter.Success()
wait := ac.acConfig.Waiter.Success()
for {
select {
case <-wait:
@ -417,7 +405,7 @@ func (ac *AutoConfig) getInitialConfiguration(ctx context.Context) error {
} else {
ac.logger.Error("No error returned when fetching the initial auto-configuration but no response was either")
}
wait = ac.waiter.Failed()
wait = ac.acConfig.Waiter.Failed()
case <-ctx.Done():
ac.logger.Info("interrupted during initial auto configuration", "err", ctx.Err())
return ctx.Err()

View File

@ -87,11 +87,23 @@ func TestNew(t *testing.T) {
cases := map[string]testCase{
"no-direct-rpc": {
config: Config{
Loader: func(source config.Source) (cfg *config.RuntimeConfig, warnings []string, err error) {
return nil, nil, nil
},
},
err: "must provide a direct RPC delegate",
},
"no-config-loader": {
err: "must provide a config loader",
},
"ok": {
config: Config{
DirectRPC: &mockDirectRPC{},
Loader: func(source config.Source) (cfg *config.RuntimeConfig, warnings []string, err error) {
return nil, nil, nil
},
},
validate: func(t *testing.T, ac *AutoConfig) {
t.Helper()
@ -102,7 +114,7 @@ func TestNew(t *testing.T) {
for name, tcase := range cases {
t.Run(name, func(t *testing.T) {
ac, err := New(&tcase.config)
ac, err := New(tcase.config)
if tcase.err != "" {
testutil.RequireErrorContains(t, err, tcase.err)
} else {
@ -116,99 +128,64 @@ func TestNew(t *testing.T) {
}
}
func TestLoadConfig(t *testing.T) {
// Basically just testing that injection of the extra
// source works.
devMode := true
builderOpts := config.BuilderOpts{
// putting this in dev mode so that the config validates
// without having to specify a data directory
DevMode: &devMode,
}
cfg, warnings, err := LoadConfig(builderOpts, config.FileSource{
Name: "test",
Format: "hcl",
Data: `node_name = "hobbiton"`,
},
config.FileSource{
Name: "overrides",
Format: "json",
Data: `{"check_reap_interval": "1ms"}`,
})
require.NoError(t, err)
require.Empty(t, warnings)
require.NotNil(t, cfg)
require.Equal(t, "hobbiton", cfg.NodeName)
require.Equal(t, 1*time.Millisecond, cfg.CheckReapInterval)
}
func TestReadConfig(t *testing.T) {
// just testing that some auto config source gets injected
devMode := true
ac := AutoConfig{
autoConfigSource: config.LiteralSource{
Name: autoConfigFileName,
Config: config.Config{NodeName: stringPointer("hobbiton")},
},
builderOpts: config.BuilderOpts{
// putting this in dev mode so that the config validates
// without having to specify a data directory
DevMode: &devMode,
},
logger: testutil.Logger(t),
acConfig: Config{
Loader: func(source config.Source) (*config.RuntimeConfig, []string, error) {
cfg, _, err := source.Parse()
if err != nil {
return nil, nil, err
}
return &config.RuntimeConfig{
DevMode: true,
NodeName: *cfg.NodeName,
}, nil, nil
},
},
}
cfg, err := ac.ReadConfig()
require.NoError(t, err)
require.NotNil(t, cfg)
require.Equal(t, "hobbiton", cfg.NodeName)
require.True(t, cfg.DevMode)
require.Same(t, ac.config, cfg)
}
func testSetupAutoConf(t *testing.T) (string, string, config.BuilderOpts) {
func setupRuntimeConfig(t *testing.T) *config.RuntimeConfig {
t.Helper()
// create top level directory to hold both config and data
tld := testutil.TempDir(t, "auto-config")
t.Cleanup(func() { os.RemoveAll(tld) })
dataDir := testutil.TempDir(t, "auto-config")
t.Cleanup(func() { os.RemoveAll(dataDir) })
// create the data directory
dataDir := filepath.Join(tld, "data")
require.NoError(t, os.Mkdir(dataDir, 0700))
// create the config directory
configDir := filepath.Join(tld, "config")
require.NoError(t, os.Mkdir(configDir, 0700))
builderOpts := config.BuilderOpts{
HCL: []string{
`data_dir = "` + dataDir + `"`,
`datacenter = "dc1"`,
`node_name = "autoconf"`,
`bind_addr = "127.0.0.1"`,
},
rtConfig := &config.RuntimeConfig{
DataDir: dataDir,
Datacenter: "dc1",
NodeName: "autoconf",
BindAddr: &net.IPAddr{IP: net.ParseIP("127.0.0.1")},
}
return dataDir, configDir, builderOpts
return rtConfig
}
func TestInitialConfiguration_disabled(t *testing.T) {
dataDir, configDir, builderOpts := testSetupAutoConf(t)
rtConfig := setupRuntimeConfig(t)
cfgFile := filepath.Join(configDir, "test.json")
require.NoError(t, ioutil.WriteFile(cfgFile, []byte(`{
"primary_datacenter": "primary",
"auto_config": {"enabled": false}
}`), 0600))
builderOpts.ConfigFiles = append(builderOpts.ConfigFiles, cfgFile)
directRPC := mockDirectRPC{}
conf := new(Config).
WithBuilderOpts(builderOpts).
WithDirectRPC(&directRPC)
directRPC := new(mockDirectRPC)
directRPC.Test(t)
conf := Config{
DirectRPC: directRPC,
Loader: func(source config.Source) (*config.RuntimeConfig, []string, error) {
rtConfig.PrimaryDatacenter = "primary"
rtConfig.AutoConfig.Enabled = false
return rtConfig, nil, nil
},
}
ac, err := New(conf)
require.NoError(t, err)
require.NotNil(t, ac)
@ -217,26 +194,17 @@ func TestInitialConfiguration_disabled(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, cfg)
require.Equal(t, "primary", cfg.PrimaryDatacenter)
require.NoFileExists(t, filepath.Join(dataDir, autoConfigFileName))
require.NoFileExists(t, filepath.Join(rtConfig.DataDir, autoConfigFileName))
// ensure no RPC was made
directRPC.AssertExpectations(t)
}
func TestInitialConfiguration_cancelled(t *testing.T) {
_, configDir, builderOpts := testSetupAutoConf(t)
cfgFile := filepath.Join(configDir, "test.json")
require.NoError(t, ioutil.WriteFile(cfgFile, []byte(`{
"primary_datacenter": "primary",
"auto_config": {"enabled": true, "intro_token": "blarg", "server_addresses": ["127.0.0.1:8300"]},
"verify_outgoing": true
}`), 0600))
builderOpts.ConfigFiles = append(builderOpts.ConfigFiles, cfgFile)
directRPC := mockDirectRPC{}
rtConfig := setupRuntimeConfig(t)
directRPC := new(mockDirectRPC)
directRPC.Test(t)
expectedRequest := pbautoconf.AutoConfigRequest{
Datacenter: "dc1",
Node: "autoconf",
@ -244,9 +212,19 @@ func TestInitialConfiguration_cancelled(t *testing.T) {
}
directRPC.On("RPC", "dc1", "autoconf", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 8300}, "AutoConfig.InitialConfiguration", &expectedRequest, mock.Anything).Return(fmt.Errorf("injected error")).Times(0)
conf := new(Config).
WithBuilderOpts(builderOpts).
WithDirectRPC(&directRPC)
conf := Config{
DirectRPC: directRPC,
Loader: func(source config.Source) (*config.RuntimeConfig, []string, error) {
rtConfig.PrimaryDatacenter = "primary"
rtConfig.AutoConfig = config.AutoConfig{
Enabled: true,
IntroToken: "blarg",
ServerAddresses: []string{"127.0.0.1:8300"},
}
rtConfig.VerifyOutgoing = true
return rtConfig, nil, nil
},
}
ac, err := New(conf)
require.NoError(t, err)
require.NotNil(t, ac)
@ -263,17 +241,10 @@ func TestInitialConfiguration_cancelled(t *testing.T) {
}
func TestInitialConfiguration_restored(t *testing.T) {
dataDir, configDir, builderOpts := testSetupAutoConf(t)
cfgFile := filepath.Join(configDir, "test.json")
require.NoError(t, ioutil.WriteFile(cfgFile, []byte(`{
"auto_config": {"enabled": true, "intro_token": "blarg", "server_addresses": ["127.0.0.1:8300"]}, "verify_outgoing": true
}`), 0600))
builderOpts.ConfigFiles = append(builderOpts.ConfigFiles, cfgFile)
rtConfig := setupRuntimeConfig(t)
// persist an auto config response to the data dir where it is expected
persistedFile := filepath.Join(dataDir, autoConfigFileName)
persistedFile := filepath.Join(rtConfig.DataDir, autoConfigFileName)
response := &pbautoconf.AutoConfigResponse{
Config: &pbconfig.Config{
PrimaryDatacenter: "primary",
@ -312,11 +283,13 @@ func TestInitialConfiguration_restored(t *testing.T) {
require.NoError(t, err)
require.NoError(t, ioutil.WriteFile(persistedFile, []byte(data), 0600))
directRPC := mockDirectRPC{}
directRPC := new(mockDirectRPC)
directRPC.Test(t)
// setup the mock certificate monitor to ensure that the initial state gets
// updated appropriately during config restoration.
certMon := mockCertMonitor{}
certMon := new(mockCertMonitor)
certMon.Test(t)
certMon.On("Update", &structs.SignedResponse{
IssuedCert: structs.IssuedCert{
SerialNumber: "1234",
@ -349,10 +322,22 @@ func TestInitialConfiguration_restored(t *testing.T) {
VerifyServerHostname: true,
}).Return(nil).Once()
conf := new(Config).
WithBuilderOpts(builderOpts).
WithDirectRPC(&directRPC).
WithCertMonitor(&certMon)
conf := Config{
DirectRPC: directRPC,
Loader: func(source config.Source) (*config.RuntimeConfig, []string, error) {
if err := setPrimaryDatacenterFromSource(rtConfig, source); err != nil {
return nil, nil, err
}
rtConfig.AutoConfig = config.AutoConfig{
Enabled: true,
IntroToken: "blarg",
ServerAddresses: []string{"127.0.0.1:8300"},
}
rtConfig.VerifyOutgoing = true
return rtConfig, nil, nil
},
CertMonitor: certMon,
}
ac, err := New(conf)
require.NoError(t, err)
require.NotNil(t, ac)
@ -367,18 +352,22 @@ func TestInitialConfiguration_restored(t *testing.T) {
certMon.AssertExpectations(t)
}
func setPrimaryDatacenterFromSource(rtConfig *config.RuntimeConfig, source config.Source) error {
if source != nil {
cfg, _, err := source.Parse()
if err != nil {
return err
}
rtConfig.PrimaryDatacenter = *cfg.PrimaryDatacenter
}
return nil
}
func TestInitialConfiguration_success(t *testing.T) {
dataDir, configDir, builderOpts := testSetupAutoConf(t)
rtConfig := setupRuntimeConfig(t)
cfgFile := filepath.Join(configDir, "test.json")
require.NoError(t, ioutil.WriteFile(cfgFile, []byte(`{
"auto_config": {"enabled": true, "intro_token": "blarg", "server_addresses": ["127.0.0.1:8300"]}, "verify_outgoing": true
}`), 0600))
builderOpts.ConfigFiles = append(builderOpts.ConfigFiles, cfgFile)
persistedFile := filepath.Join(dataDir, autoConfigFileName)
directRPC := mockDirectRPC{}
directRPC := new(mockDirectRPC)
directRPC.Test(t)
populateResponse := func(val interface{}) {
resp, ok := val.(*pbautoconf.AutoConfigResponse)
@ -434,7 +423,8 @@ func TestInitialConfiguration_success(t *testing.T) {
// setup the mock certificate monitor to ensure that the initial state gets
// updated appropriately during config restoration.
certMon := mockCertMonitor{}
certMon := new(mockCertMonitor)
certMon.Test(t)
certMon.On("Update", &structs.SignedResponse{
IssuedCert: structs.IssuedCert{
SerialNumber: "1234",
@ -465,10 +455,22 @@ func TestInitialConfiguration_success(t *testing.T) {
VerifyServerHostname: true,
}).Return(nil).Once()
conf := new(Config).
WithBuilderOpts(builderOpts).
WithDirectRPC(&directRPC).
WithCertMonitor(&certMon)
conf := Config{
DirectRPC: directRPC,
Loader: func(source config.Source) (*config.RuntimeConfig, []string, error) {
if err := setPrimaryDatacenterFromSource(rtConfig, source); err != nil {
return nil, nil, err
}
rtConfig.AutoConfig = config.AutoConfig{
Enabled: true,
IntroToken: "blarg",
ServerAddresses: []string{"127.0.0.1:8300"},
}
rtConfig.VerifyOutgoing = true
return rtConfig, nil, nil
},
CertMonitor: certMon,
}
ac, err := New(conf)
require.NoError(t, err)
require.NotNil(t, ac)
@ -479,6 +481,7 @@ func TestInitialConfiguration_success(t *testing.T) {
require.Equal(t, "primary", cfg.PrimaryDatacenter)
// the file was written to.
persistedFile := filepath.Join(rtConfig.DataDir, autoConfigFileName)
require.FileExists(t, persistedFile)
// ensure no RPC was made
@ -487,17 +490,10 @@ func TestInitialConfiguration_success(t *testing.T) {
}
func TestInitialConfiguration_retries(t *testing.T) {
dataDir, configDir, builderOpts := testSetupAutoConf(t)
rtConfig := setupRuntimeConfig(t)
cfgFile := filepath.Join(configDir, "test.json")
require.NoError(t, ioutil.WriteFile(cfgFile, []byte(`{
"auto_config": {"enabled": true, "intro_token": "blarg", "server_addresses": ["198.18.0.1", "198.18.0.2:8398", "198.18.0.3:8399", "127.0.0.1:1234"]}, "verify_outgoing": true
}`), 0600))
builderOpts.ConfigFiles = append(builderOpts.ConfigFiles, cfgFile)
persistedFile := filepath.Join(dataDir, autoConfigFileName)
directRPC := mockDirectRPC{}
directRPC := new(mockDirectRPC)
directRPC.Test(t)
populateResponse := func(val interface{}) {
resp, ok := val.(*pbautoconf.AutoConfigResponse)
@ -557,11 +553,27 @@ func TestInitialConfiguration_retries(t *testing.T) {
&expectedRequest,
&pbautoconf.AutoConfigResponse{}).Return(populateResponse)
waiter := lib.NewRetryWaiter(2, 0, 1*time.Millisecond, nil)
conf := new(Config).
WithBuilderOpts(builderOpts).
WithDirectRPC(&directRPC).
WithRetryWaiter(waiter)
conf := Config{
DirectRPC: directRPC,
Loader: func(source config.Source) (*config.RuntimeConfig, []string, error) {
if err := setPrimaryDatacenterFromSource(rtConfig, source); err != nil {
return nil, nil, err
}
rtConfig.AutoConfig = config.AutoConfig{
Enabled: true,
IntroToken: "blarg",
ServerAddresses: []string{
"198.18.0.1:8300",
"198.18.0.2:8398",
"198.18.0.3:8399",
"127.0.0.1:1234",
},
}
rtConfig.VerifyOutgoing = true
return rtConfig, nil, nil
},
Waiter: lib.NewRetryWaiter(2, 0, 1*time.Millisecond, nil),
}
ac, err := New(conf)
require.NoError(t, err)
require.NotNil(t, ac)
@ -572,6 +584,7 @@ func TestInitialConfiguration_retries(t *testing.T) {
require.Equal(t, "primary", cfg.PrimaryDatacenter)
// the file was written to.
persistedFile := filepath.Join(rtConfig.DataDir, autoConfigFileName)
require.FileExists(t, persistedFile)
// ensure no RPC was made
@ -584,25 +597,34 @@ func TestAutoConfig_StartStop(t *testing.T) {
// stopped and not that anything with regards to running the cert monitor
// actually work. Those are tested in the cert-monitor package.
_, configDir, builderOpts := testSetupAutoConf(t)
rtConfig := setupRuntimeConfig(t)
cfgFile := filepath.Join(configDir, "test.json")
require.NoError(t, ioutil.WriteFile(cfgFile, []byte(`{
"auto_config": {"enabled": true, "intro_token": "blarg", "server_addresses": ["198.18.0.1", "198.18.0.2:8398", "198.18.0.3:8399", "127.0.0.1:1234"]}, "verify_outgoing": true
}`), 0600))
builderOpts.ConfigFiles = append(builderOpts.ConfigFiles, cfgFile)
directRPC := &mockDirectRPC{}
directRPC.Test(t)
certMon := &mockCertMonitor{}
certMon.Test(t)
certMon.On("Start").Return((<-chan struct{})(nil), nil).Once()
certMon.On("Stop").Return(true).Once()
conf := new(Config).
WithBuilderOpts(builderOpts).
WithDirectRPC(directRPC).
WithCertMonitor(certMon)
conf := Config{
DirectRPC: directRPC,
Loader: func(source config.Source) (*config.RuntimeConfig, []string, error) {
rtConfig.AutoConfig = config.AutoConfig{
Enabled: true,
IntroToken: "blarg",
ServerAddresses: []string{
"198.18.0.1",
"198.18.0.2:8398",
"198.18.0.3:8399",
"127.0.0.1:1234",
},
}
rtConfig.VerifyOutgoing = true
return rtConfig, nil, nil
},
CertMonitor: certMon,
}
ac, err := New(conf)
require.NoError(t, err)
require.NotNil(t, ac)
@ -618,16 +640,10 @@ func TestAutoConfig_StartStop(t *testing.T) {
}
func TestFallBackTLS(t *testing.T) {
_, configDir, builderOpts := testSetupAutoConf(t)
rtConfig := setupRuntimeConfig(t)
cfgFile := filepath.Join(configDir, "test.json")
require.NoError(t, ioutil.WriteFile(cfgFile, []byte(`{
"auto_config": {"enabled": true, "intro_token": "blarg", "server_addresses": ["127.0.0.1:8300"]}, "verify_outgoing": true
}`), 0600))
builderOpts.ConfigFiles = append(builderOpts.ConfigFiles, cfgFile)
directRPC := mockDirectRPC{}
directRPC := new(mockDirectRPC)
directRPC.Test(t)
populateResponse := func(val interface{}) {
resp, ok := val.(*pbautoconf.AutoConfigResponse)
@ -684,12 +700,21 @@ func TestFallBackTLS(t *testing.T) {
// setup the mock certificate monitor we don't expect it to be used
// as the FallbackTLS method is mainly used by the certificate monitor
// if for some reason it fails to renew the TLS certificate in time.
certMon := mockCertMonitor{}
certMon := new(mockCertMonitor)
conf := new(Config).
WithBuilderOpts(builderOpts).
WithDirectRPC(&directRPC).
WithCertMonitor(&certMon)
conf := Config{
DirectRPC: directRPC,
Loader: func(source config.Source) (*config.RuntimeConfig, []string, error) {
rtConfig.AutoConfig = config.AutoConfig{
Enabled: true,
IntroToken: "blarg",
ServerAddresses: []string{"127.0.0.1:8300"},
}
rtConfig.VerifyOutgoing = true
return rtConfig, nil, nil
},
CertMonitor: certMon,
}
ac, err := New(conf)
require.NoError(t, err)
require.NotNil(t, ac)

View File

@ -1,30 +0,0 @@
package autoconf
import (
"github.com/hashicorp/consul/agent/config"
)
// LoadConfig will build the configuration including the extraHead source injected
// after all other defaults but before any user supplied configuration and the overrides
// source injected as the final source in the configuration parsing chain.
func LoadConfig(builderOpts config.BuilderOpts, extraHead config.Source, overrides ...config.Source) (*config.RuntimeConfig, []string, error) {
b, err := config.NewBuilder(builderOpts)
if err != nil {
return nil, nil, err
}
if extraHead != nil {
b.Head = append(b.Head, extraHead)
}
if len(overrides) != 0 {
b.Tail = append(b.Tail, overrides...)
}
cfg, err := b.BuildAndValidate()
if err != nil {
return nil, nil, err
}
return &cfg, b.Warnings, nil
}

View File

@ -37,17 +37,6 @@ type Config struct {
// configuration. Setting this field is required.
DirectRPC DirectRPC
// BuilderOpts are any configuration building options that should be
// used when loading the Consul configuration. This is mostly a pass
// through from what the CLI will generate. While this option is
// not strictly required, not setting it will prevent AutoConfig
// from doing anything useful. Enabling AutoConfig requires a
// CLI flag or a config file (also specified by the CLI) flag.
// So without providing the opts its equivalent to using the
// configuration of not specifying anything to the consul agent
// cli.
BuilderOpts config.BuilderOpts
// Waiter is a RetryWaiter to be used during retrieval of the
// initial configuration. When a round of requests fails we will
// wait and eventually make another round of requests (1 round
@ -60,56 +49,14 @@ type Config struct {
// having the test take minutes/hours to complete.
Waiter *lib.RetryWaiter
// Overrides are a list of configuration sources to append to the tail of
// the config builder. This field is optional and mainly useful for testing
// to override values that would be otherwise not user-settable.
Overrides []config.Source
// CertMonitor is the Connect TLS Certificate Monitor to be used for ongoing
// certificate renewals and connect CA roots updates. This field is not
// strictly required but if not provided the TLS certificates retrieved
// through by the AutoConfig.InitialConfiguration RPC will not be used
// or renewed.
CertMonitor CertMonitor
}
// WithLogger will cause the created AutoConfig type to use the provided logger
func (c *Config) WithLogger(logger hclog.Logger) *Config {
c.Logger = logger
return c
}
// WithConnectionPool will cause the created AutoConfig type to use the provided connection pool
func (c *Config) WithDirectRPC(directRPC DirectRPC) *Config {
c.DirectRPC = directRPC
return c
}
// WithBuilderOpts will cause the created AutoConfig type to use the provided CLI builderOpts
func (c *Config) WithBuilderOpts(builderOpts config.BuilderOpts) *Config {
c.BuilderOpts = builderOpts
return c
}
// WithRetryWaiter will cause the created AutoConfig type to use the provided retry waiter
func (c *Config) WithRetryWaiter(waiter *lib.RetryWaiter) *Config {
c.Waiter = waiter
return c
}
// WithOverrides is used to provide a config source to append to the tail sources
// during config building. It is really only useful for testing to tune non-user
// configurable tunables to make various tests converge more quickly than they
// could otherwise.
func (c *Config) WithOverrides(overrides ...config.Source) *Config {
c.Overrides = overrides
return c
}
// WithCertMonitor is used to provide a certificate monitor to the auto-config.
// This monitor is responsible for renewing the agents TLS certificate and keeping
// the connect CA roots up to date.
func (c *Config) WithCertMonitor(certMonitor CertMonitor) *Config {
c.CertMonitor = certMonitor
return c
// Loader merges source with the existing FileSources and returns the complete
// RuntimeConfig.
Loader func(source config.Source) (cfg *config.RuntimeConfig, warnings []string, err error)
}

View File

@ -33,6 +33,31 @@ import (
"golang.org/x/time/rate"
)
// LoadConfig will build the configuration including the extraHead source injected
// after all other defaults but before any user supplied configuration and the overrides
// source injected as the final source in the configuration parsing chain.
func Load(builderOpts BuilderOpts, extraHead Source, overrides ...Source) (*RuntimeConfig, []string, error) {
b, err := NewBuilder(builderOpts)
if err != nil {
return nil, nil, err
}
if extraHead != nil {
b.Head = append(b.Head, extraHead)
}
if len(overrides) != 0 {
b.Tail = append(b.Tail, overrides...)
}
cfg, err := b.BuildAndValidate()
if err != nil {
return nil, nil, err
}
return &cfg, b.Warnings, nil
}
// Builder constructs a valid runtime configuration from multiple
// configuration sources.
//

View File

@ -6,10 +6,39 @@ import (
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestLoad(t *testing.T) {
// Basically just testing that injection of the extra
// source works.
devMode := true
builderOpts := BuilderOpts{
// putting this in dev mode so that the config validates
// without having to specify a data directory
DevMode: &devMode,
}
cfg, warnings, err := Load(builderOpts, FileSource{
Name: "test",
Format: "hcl",
Data: `node_name = "hobbiton"`,
},
FileSource{
Name: "overrides",
Format: "json",
Data: `{"check_reap_interval": "1ms"}`,
})
require.NoError(t, err)
require.Empty(t, warnings)
require.NotNil(t, cfg)
require.Equal(t, "hobbiton", cfg.NodeName)
require.Equal(t, 1*time.Millisecond, cfg.CheckReapInterval)
}
func TestShouldParseFile(t *testing.T) {
var testcases = []struct {
filename string