diff --git a/proxy/launch.go b/proxy/launch.go index a4a359b..d769b11 100644 --- a/proxy/launch.go +++ b/proxy/launch.go @@ -23,8 +23,8 @@ func runSignalListener(cancelFunc context.CancelFunc) { }() } -func launchProxy(profilingSupported bool) { - conf, err := config.New().LoadConfig() +func launchProxy(profilingSupported bool, configFile string) { + conf, err := config.New().LoadConfig(configFile) if err != nil { log.Errorf("Error loading configuration: %v. Aborting startup.", err) diff --git a/proxy/main.go b/proxy/main.go index a564618..bdf51ff 100644 --- a/proxy/main.go +++ b/proxy/main.go @@ -16,7 +16,8 @@ import ( // TODO: to be managed externally const ZdmVersionString = "2.2.0" -var displayVersion = flag.Bool("version", false, "Display the ZDM proxy version and exit") +var displayVersion = flag.Bool("version", false, "display the ZDM proxy version and exit") +var configFile = flag.String("config", "", "specify path to ZDM configuration file") func main() { @@ -29,5 +30,5 @@ func main() { // Always record version information (very) early in the log log.Infof("Starting ZDM proxy version %v", ZdmVersionString) - launchProxy(false) + launchProxy(false, *configFile) } diff --git a/proxy/main_profiling.go b/proxy/main_profiling.go index 0acd9ff..1e883bb 100644 --- a/proxy/main_profiling.go +++ b/proxy/main_profiling.go @@ -15,6 +15,7 @@ import ( var cpuProfile = flag.String("cpu_profile", "", "write cpu profile to the specified file") var memProfile = flag.String("mem_profile", "", "write memory profile to the specified file") +var configFile = flag.String("config", "", "specify path to ZDM configuration file") func main() { @@ -61,5 +62,5 @@ func main() { }() } - launchProxy(true) + launchProxy(true, configFile) } diff --git a/proxy/pkg/config/config.go b/proxy/pkg/config/config.go index b0dc358..b8019de 100644 --- a/proxy/pkg/config/config.go +++ b/proxy/pkg/config/config.go @@ -10,7 +10,6 @@ import ( "gopkg.in/yaml.v3" "net" "os" - "reflect" "strconv" "strings" ) @@ -38,8 +37,8 @@ type Config struct { OriginPort int `default:"9042" split_words:"true" yaml:"origin_port"` OriginSecureConnectBundlePath string `split_words:"true" yaml:"origin_secure_connect_bundle_path"` OriginLocalDatacenter string `split_words:"true" yaml:"origin_local_datacenter"` - OriginUsername string `split_words:"true" yaml:"origin_username"` - OriginPassword string `split_words:"true" json:"-" yaml:"origin_password"` + OriginUsername string `required:"true" split_words:"true" yaml:"origin_username"` + OriginPassword string `required:"true" split_words:"true" json:"-" yaml:"origin_password"` OriginConnectionTimeoutMs int `default:"30000" split_words:"true" yaml:"origin_connection_timeout_ms"` OriginTlsServerCaPath string `split_words:"true" yaml:"origin_tls_server_ca_path"` @@ -52,8 +51,8 @@ type Config struct { TargetPort int `default:"9042" split_words:"true" yaml:"target_port"` TargetSecureConnectBundlePath string `split_words:"true" yaml:"target_secure_connect_bundle_path"` TargetLocalDatacenter string `split_words:"true" yaml:"target_local_datacenter"` - TargetUsername string `split_words:"true" yaml:"target_username"` - TargetPassword string `split_words:"true" json:"-" yaml:"target_password"` + TargetUsername string `required:"true" split_words:"true" yaml:"target_username"` + TargetPassword string `required:"true" split_words:"true" json:"-" yaml:"target_password"` TargetConnectionTimeoutMs int `default:"30000" split_words:"true" yaml:"target_connection_timeout_ms"` TargetTlsServerCaPath string `split_words:"true" yaml:"target_tls_server_ca_path"` @@ -127,14 +126,6 @@ type Config struct { AsyncConnectorWriteBufferSizeBytes int `default:"4096" split_words:"true" yaml:"async_connector_write_buffer_size_bytes"` } -var DefaultConfig *Config = nil - -func init() { - c := &Config{} - def.SetDefaults(c) - DefaultConfig = c -} - func (c *Config) String() string { serializedConfig, _ := json.Marshal(c) return string(serializedConfig) @@ -145,52 +136,18 @@ func New() *Config { return &Config{} } -func (c *Config) loadFromFiles() error { - paths := os.Getenv("ZDM_CONFIG_FILES") - for _, path := range strings.Split(paths, ",") { - if len(path) == 0 { - continue - } - file, err := os.Open(path) - if err != nil { - return fmt.Errorf("could not read configuration file %v: %w", path, err) - } - defer file.Close() - - dec := yaml.NewDecoder(file) - pc := &Config{} - def.SetDefaults(pc) // apply default tag, YAML decoder does not support it - if err = dec.Decode(pc); err != nil { - return fmt.Errorf("could not parse yaml file %v: %w", path, err) - } - err = c.mergeConfig(pc) - if err != nil { - return err - } +func (c *Config) loadFromFile(configFile string) error { + file, err := os.Open(configFile) + if err != nil { + return fmt.Errorf("could not read configuration file %v: %w", configFile, err) } - return nil -} + defer file.Close() -func (c *Config) mergeConfig(other *Config) error { - cRef := reflect.ValueOf(c).Elem() - otherRef := reflect.ValueOf(*other) - defaultRef := reflect.ValueOf(*DefaultConfig) - for i := 0; i < cRef.NumField(); i++ { - field := cRef.Type().Field(i).Tag.Get("yaml") - cVal := cRef.Field(i).Interface() - otherVal := otherRef.Field(i).Interface() - defaultVal := defaultRef.Field(i).Interface() - - if otherVal != defaultVal { - // other value is different from the default - if cVal != defaultVal && cVal != otherVal { - return fmt.Errorf("inconsistent values [%v] and [%v] for parameter %v", cVal, otherVal, field) - } else { - cRef.Field(i).Set(otherRef.Field(i)) - } - } + def.SetDefaults(c) // apply default tag, it is not supported by YAML decoder + dec := yaml.NewDecoder(file) + if err = dec.Decode(c); err != nil { + return fmt.Errorf("could not parse yaml file %v: %w", configFile, err) } - return nil } @@ -205,13 +162,14 @@ func (c *Config) parseEnvVars() error { return nil } -func (c *Config) LoadConfig() (*Config, error) { - err := c.parseEnvVars() - if err != nil { - return nil, err - } +func (c *Config) LoadConfig(configFile string) (*Config, error) { + var err error - err = c.loadFromFiles() + if configFile != "" { + err = c.loadFromFile(configFile) + } else { + err = c.parseEnvVars() + } if err != nil { return nil, err } diff --git a/proxy/pkg/config/config_dual_reads_test.go b/proxy/pkg/config/config_dual_reads_test.go index f220273..2b4c572 100644 --- a/proxy/pkg/config/config_dual_reads_test.go +++ b/proxy/pkg/config/config_dual_reads_test.go @@ -62,7 +62,7 @@ func TestConfig_ParseReadMode(t *testing.T) { setOriginContactPointsAndPortEnvVars() setTargetContactPointsAndPortEnvVars() - conf, err := New().LoadConfig() + conf, err := New().LoadConfig("") if err != nil { if tt.errExpected { require.Equal(t, tt.errMsg, err.Error()) diff --git a/proxy/pkg/config/config_test.go b/proxy/pkg/config/config_test.go index 88c1b05..e211fc5 100644 --- a/proxy/pkg/config/config_test.go +++ b/proxy/pkg/config/config_test.go @@ -17,7 +17,7 @@ func TestTargetConfig_WithBundleOnly(t *testing.T) { // test-specific setup setEnvVar("ZDM_TARGET_SECURE_CONNECT_BUNDLE_PATH", "/path/to/target/bundle") - conf, err := New().LoadConfig() + conf, err := New().LoadConfig("") require.Nil(t, err) require.Equal(t, conf.TargetSecureConnectBundlePath, "/path/to/target/bundle") require.Empty(t, conf.TargetContactPoints) @@ -36,7 +36,7 @@ func TestTargetConfig_WithHostnameAndPortOnly(t *testing.T) { // test-specific setup setTargetContactPointsAndPortEnvVars() - conf, err := New().LoadConfig() + conf, err := New().LoadConfig("") require.Nil(t, err) require.Equal(t, conf.TargetContactPoints, "target.hostname.com") require.Equal(t, conf.TargetPort, 5647) @@ -56,7 +56,7 @@ func TestTargetConfig_WithBundleAndHostname(t *testing.T) { setTargetContactPointsAndPortEnvVars() setTargetSecureConnectBundleEnvVar() - _, err := New().LoadConfig() + _, err := New().LoadConfig("") require.Error(t, err, "TargetSecureConnectBundlePath and TargetContactPoints are "+ "mutually exclusive. Please specify only one of them.") } @@ -72,7 +72,7 @@ func TestTargetConfig_WithoutBundleAndHostname(t *testing.T) { // no test-specific setup in this case - _, err := New().LoadConfig() + _, err := New().LoadConfig("") require.Error(t, err, "Both TargetSecureConnectBundlePath and TargetContactPoints are "+ "empty. Please specify either one of them.") } @@ -89,7 +89,7 @@ func TestTargetConfig_WithHostnameButWithoutPort(t *testing.T) { //test-specific setup setEnvVar("ZDM_TARGET_CONTACT_POINTS", "target.hostname.com") - c, err := New().LoadConfig() + c, err := New().LoadConfig("") require.Nil(t, err) require.Equal(t, 9042, c.TargetPort) } @@ -98,9 +98,7 @@ func TestConfig_LoadNotExistingFile(t *testing.T) { defer clearAllEnvVars() clearAllEnvVars() - setConfigFilesEnvVar("/not/existing/file") - - _, err := New().LoadConfig() + _, err := New().LoadConfig("/not/existing/file") require.NotNil(t, err) require.Contains(t, err.Error(), "could not read configuration file /not/existing/file") } @@ -125,9 +123,8 @@ proxy_listen_port: 39042 `) defer removeConfigFile(f) require.Nil(t, err) - setConfigFilesEnvVar(f.Name()) - c, err := New().LoadConfig() + c, err := New().LoadConfig(f.Name()) require.Nil(t, err) require.Equal(t, "ORIGIN", c.PrimaryCluster) require.Equal(t, "foo1", c.OriginUsername) @@ -139,63 +136,5 @@ proxy_listen_port: 39042 require.Equal(t, "192.168.100.102", c.TargetContactPoints) require.Equal(t, 29042, c.TargetPort) require.Equal(t, 39042, c.ProxyListenPort) -} - -func TestConfig_LoadConfigFromFileAndEnvVars(t *testing.T) { - defer clearAllEnvVars() - clearAllEnvVars() - - // publicly available information stored as environment variables - setOriginContactPointsAndPortEnvVars() - setTargetContactPointsAndPortEnvVars() - - // sensitive username and passwords stored inside two files - f1, err := createConfigFile(` -origin_username: foo1 -origin_password: bar1 -`) - defer removeConfigFile(f1) - require.Nil(t, err) - f2, err := createConfigFile(` -target_username: foo2 -target_password: bar2 -`) - defer removeConfigFile(f2) - require.Nil(t, err) - - setConfigFilesEnvVar(f1.Name(), f2.Name()) - - c, err := New().LoadConfig() - require.Nil(t, err) - require.Equal(t, "foo1", c.OriginUsername) - require.Equal(t, "bar1", c.OriginPassword) - require.Equal(t, "foo2", c.TargetUsername) - require.Equal(t, "bar2", c.TargetPassword) - require.Equal(t, "origin.hostname.com", c.OriginContactPoints) - require.Equal(t, "target.hostname.com", c.TargetContactPoints) -} - -func TestConfig_FailOnDuplicateValuePresent(t *testing.T) { - defer clearAllEnvVars() - clearAllEnvVars() - - // publicly available information stored as environment variables - setOriginCredentialsEnvVars() - setTargetCredentialsEnvVars() - setOriginContactPointsAndPortEnvVars() - setTargetContactPointsAndPortEnvVars() - - // try to overriding values should raise error - f, err := createConfigFile(` -origin_username: different -origin_password: different -`) - defer removeConfigFile(f) - require.Nil(t, err) - - setConfigFilesEnvVar(f.Name()) - - _, err = New().LoadConfig() - require.NotNil(t, err) - require.Contains(t, err.Error(), "inconsistent values [originUser] and [different] for parameter origin_username") + require.Equal(t, 4000, c.AsyncHandshakeTimeoutMs) // verify that defaults were applied } diff --git a/proxy/pkg/config/config_tls_test.go b/proxy/pkg/config/config_tls_test.go index ab4d8b9..ba89525 100644 --- a/proxy/pkg/config/config_tls_test.go +++ b/proxy/pkg/config/config_tls_test.go @@ -191,7 +191,7 @@ func TestOriginConfig_ClusterTlsConfig(t *testing.T) { setTargetContactPointsAndPortEnvVars() var tlsConf *common.ClusterTlsConfig - conf, err := New().LoadConfig() + conf, err := New().LoadConfig("") if err != nil { if tt.errExpected { require.Equal(t, tt.errMsg, err.Error()) @@ -389,7 +389,7 @@ func TestTargetConfig_ClusterTlsConfig(t *testing.T) { setOriginContactPointsAndPortEnvVars() var tlsConf *common.ClusterTlsConfig - conf, err := New().LoadConfig() + conf, err := New().LoadConfig("") if err != nil { if tt.errExpected { // Expected configuration validation error diff --git a/proxy/pkg/config/configtestutils.go b/proxy/pkg/config/configtestutils.go index d1504e6..43bea35 100644 --- a/proxy/pkg/config/configtestutils.go +++ b/proxy/pkg/config/configtestutils.go @@ -2,7 +2,6 @@ package config import ( "os" - "strings" ) type envVar struct { @@ -38,10 +37,6 @@ func setTargetSecureConnectBundleEnvVar() { setEnvVar("ZDM_TARGET_SECURE_CONNECT_BUNDLE_PATH", "/path/to/origin/bundle") } -func setConfigFilesEnvVar(paths ...string) { - setEnvVar("ZDM_CONFIG_FILES", strings.Join(paths[:], ",")) -} - func createConfigFile(content string) (*os.File, error) { f, err := os.CreateTemp("", "config.*.yml") if err == nil {