config: make config file relative to the config dir

a configuration parsing error is now fatal
This commit is contained in:
Nicola Murino 2020-12-03 17:16:35 +01:00
parent 2a9ed0abca
commit 95c6d41c35
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
7 changed files with 87 additions and 54 deletions

View file

@ -64,7 +64,7 @@ func getCustomServeFlags() []string {
result = append(result, "--"+configDirFlag)
result = append(result, configDir)
}
if configFile != "" {
if configFile != defaultConfigFile {
result = append(result, "--"+configFileFlag)
result = append(result, configFile)
}

View file

@ -124,6 +124,7 @@ Please take a look at the usage below to customize the serving parameters`,
}
service := service.Service{
ConfigDir: filepath.Clean(defaultConfigDir),
ConfigFile: defaultConfigFile,
LogFilePath: portableLogFile,
LogMaxSize: defaultLogMaxSize,
LogMaxBackups: defaultLogMaxBackup,

View file

@ -15,6 +15,7 @@ const (
configDirFlag = "config-dir"
configDirKey = "config_dir"
configFileFlag = "config-file"
configFileKey = "config_file"
logFilePathFlag = "log-file-path"
logFilePathKey = "log_file_path"
logMaxSizeFlag = "log-max-size"
@ -38,6 +39,7 @@ const (
loadDataCleanFlag = "loaddata-clean"
loadDataCleanKey = "loaddata_clean"
defaultConfigDir = "."
defaultConfigFile = ""
defaultLogFile = "sftpgo.log"
defaultLogMaxSize = 10
defaultLogMaxBackup = 5
@ -92,22 +94,35 @@ func addConfigFlags(cmd *cobra.Command) {
viper.SetDefault(configDirKey, defaultConfigDir)
viper.BindEnv(configDirKey, "SFTPGO_CONFIG_DIR") //nolint:errcheck // err is not nil only if the key to bind is missing
cmd.Flags().StringVarP(&configDir, configDirFlag, "c", viper.GetString(configDirKey),
`Location for SFTPGo config dir. This directory
should contain the "sftpgo" configuration file.
It is used as the base for files with a relative path
(eg. the private keys for the SFTP server, the SQLite
database if you use SQLite as data provider).
`Location for the config dir. This directory
is used as the base for files with a relative
path, eg. the private keys for the SFTP
server or the SQLite database if you use
SQLite as data provider.
The configuration file, if not explicitly set,
is looked for in this dir. We support reading
from JSON, TOML, YAML, HCL, envfile and Java
properties config files. The default config
file name is "sftpgo" and therefore
"sftpgo.json", "sftpgo.yaml" and so on are
searched.
This flag can be set using SFTPGO_CONFIG_DIR
env var too.`)
viper.BindPFlag(configDirKey, cmd.Flags().Lookup(configDirFlag)) //nolint:errcheck
cmd.Flags().StringVar(&configFile, configFileFlag, os.Getenv("SFTPGO_CONFIG_FILE"),
`Path to SFTPGo configuration file. It must be
an absolute path to a file or a path relative to the working directory.
The specified file name must have a supported extension
(JSON, YAML, TOML or Java properties).
viper.SetDefault(configFileKey, defaultConfigFile)
viper.BindEnv(configFileKey, "SFTPGO_CONFIG_FILE") //nolint:errcheck
cmd.Flags().StringVar(&configFile, configFileFlag, viper.GetString(configFileKey),
`Path to SFTPGo configuration file.
This flag explicitly defines the path, name
and extension of the config file. If must be
an absolute path or a path relative to the
configuration directory. The specified file
name must have a supported extension (JSON,
YAML, TOML, HCL or Java properties).
This flag can be set using SFTPGO_CONFIG_FILE
env var too.`)
viper.BindPFlag(configFileKey, cmd.Flags().Lookup(configFileFlag)) //nolint:errcheck
}
func addServeFlags(cmd *cobra.Command) {

View file

@ -3,6 +3,7 @@ package config
import (
"fmt"
"path/filepath"
"strings"
"github.com/spf13/viper"
@ -22,11 +23,11 @@ import (
const (
logSender = "config"
// configName defines the name for the default config file.
// This is the file name without extension, we use viper and so we
// support all the config files format supported by viper
// configName defines the name for config file.
// This name does not include the extension, viper will search for files
// with supported extensions such as "sftpgo.json", "sftpgo.yaml" and so on
configName = "sftpgo"
// ConfigEnvPrefix defines a prefix that ENVIRONMENT variables will use
// ConfigEnvPrefix defines a prefix that environment variables will use
configEnvPrefix = "sftpgo"
)
@ -286,26 +287,35 @@ func getRedactedGlobalConf() globalConfig {
return conf
}
func setConfigFile(configDir, configFile string) {
if configFile == "" {
return
}
if !filepath.IsAbs(configFile) && utils.IsFileInputValid(configFile) {
configFile = filepath.Join(configDir, configFile)
}
viper.SetConfigFile(configFile)
}
// LoadConfig loads the configuration
// configDir will be added to the configuration search paths.
// The search path contains by default the current directory and on linux it contains
// $HOME/.config/sftpgo and /etc/sftpgo too.
// configFile is an absolute or relative path (to the working directory) to the configuration file.
// configFile is an absolute or relative path (to the config dir) to the configuration file.
func LoadConfig(configDir, configFile string) error {
var err error
viper.AddConfigPath(configDir)
setViperAdditionalConfigPaths()
viper.AddConfigPath(".")
viper.SetConfigFile(configFile)
setConfigFile(configDir, configFile)
if err = viper.ReadInConfig(); err != nil {
logger.Warn(logSender, "", "error loading configuration file: %v", err)
logger.WarnToConsole("error loading configuration file: %v", err)
}
err = viper.Unmarshal(&globalConf)
if err != nil {
logger.Warn(logSender, "", "error parsing configuration file: %v. Default configuration will be used: %+v",
err, getRedactedGlobalConf())
logger.WarnToConsole("error parsing configuration file: %v. Default configuration will be used.", err)
logger.Warn(logSender, "", "error parsing configuration file: %v", err)
logger.WarnToConsole("error parsing configuration file: %v", err)
return err
}
checkCommonParamsCompatibility()
@ -322,34 +332,34 @@ func LoadConfig(configDir, configFile string) error {
logger.WarnToConsole("Configuration error: %v", err)
}
if globalConf.Common.UploadMode < 0 || globalConf.Common.UploadMode > 2 {
err = fmt.Errorf("invalid upload_mode 0, 1 and 2 are supported, configured: %v reset upload_mode to 0",
warn := fmt.Sprintf("invalid upload_mode 0, 1 and 2 are supported, configured: %v reset upload_mode to 0",
globalConf.Common.UploadMode)
globalConf.Common.UploadMode = 0
logger.Warn(logSender, "", "Configuration error: %v", err)
logger.WarnToConsole("Configuration error: %v", err)
logger.Warn(logSender, "", "Configuration error: %v", warn)
logger.WarnToConsole("Configuration error: %v", warn)
}
if globalConf.Common.ProxyProtocol < 0 || globalConf.Common.ProxyProtocol > 2 {
err = fmt.Errorf("invalid proxy_protocol 0, 1 and 2 are supported, configured: %v reset proxy_protocol to 0",
warn := fmt.Sprintf("invalid proxy_protocol 0, 1 and 2 are supported, configured: %v reset proxy_protocol to 0",
globalConf.Common.ProxyProtocol)
globalConf.Common.ProxyProtocol = 0
logger.Warn(logSender, "", "Configuration error: %v", err)
logger.WarnToConsole("Configuration error: %v", err)
logger.Warn(logSender, "", "Configuration error: %v", warn)
logger.WarnToConsole("Configuration error: %v", warn)
}
if globalConf.ProviderConf.ExternalAuthScope < 0 || globalConf.ProviderConf.ExternalAuthScope > 7 {
err = fmt.Errorf("invalid external_auth_scope: %v reset to 0", globalConf.ProviderConf.ExternalAuthScope)
warn := fmt.Sprintf("invalid external_auth_scope: %v reset to 0", globalConf.ProviderConf.ExternalAuthScope)
globalConf.ProviderConf.ExternalAuthScope = 0
logger.Warn(logSender, "", "Configuration error: %v", err)
logger.WarnToConsole("Configuration error: %v", err)
logger.Warn(logSender, "", "Configuration error: %v", warn)
logger.WarnToConsole("Configuration error: %v", warn)
}
if len(globalConf.ProviderConf.CredentialsPath) == 0 {
err = fmt.Errorf("invalid credentials path, reset to \"credentials\"")
if globalConf.ProviderConf.CredentialsPath == "" {
warn := "invalid credentials path, reset to \"credentials\""
globalConf.ProviderConf.CredentialsPath = "credentials"
logger.Warn(logSender, "", "Configuration error: %v", err)
logger.WarnToConsole("Configuration error: %v", err)
logger.Warn(logSender, "", "Configuration error: %v", warn)
logger.WarnToConsole("Configuration error: %v", warn)
}
checkHostKeyCompatibility()
logger.Debug(logSender, "", "config file used: '%#v', config loaded: %+v", viper.ConfigFileUsed(), getRedactedGlobalConf())
return err
return nil
}
func checkHostKeyCompatibility() {

View file

@ -42,16 +42,16 @@ func TestLoadConfigTest(t *testing.T) {
assert.NotEqual(t, httpclient.Config{}, config.GetHTTPConfig())
confName := tempConfigName + ".json"
configFilePath := filepath.Join(configDir, confName)
err = config.LoadConfig(configDir, configFilePath)
err = config.LoadConfig(configDir, confName)
assert.NoError(t, err)
err = ioutil.WriteFile(configFilePath, []byte("{invalid json}"), os.ModePerm)
assert.NoError(t, err)
err = config.LoadConfig(configDir, configFilePath)
err = config.LoadConfig(configDir, confName)
assert.NoError(t, err)
err = ioutil.WriteFile(configFilePath, []byte("{\"sftpd\": {\"bind_port\": \"a\"}}"), os.ModePerm)
assert.NoError(t, err)
err = config.LoadConfig(configDir, configFilePath)
assert.NotNil(t, err)
err = config.LoadConfig(configDir, confName)
assert.Error(t, err)
err = os.Remove(configFilePath)
assert.NoError(t, err)
}
@ -71,7 +71,7 @@ func TestEmptyBanner(t *testing.T) {
jsonConf, _ := json.Marshal(c)
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
assert.NoError(t, err)
err = config.LoadConfig(configDir, configFilePath)
err = config.LoadConfig(configDir, confName)
assert.NoError(t, err)
sftpdConf = config.GetSFTPDConfig()
assert.NotEmpty(t, strings.TrimSpace(sftpdConf.Banner))
@ -85,7 +85,7 @@ func TestEmptyBanner(t *testing.T) {
jsonConf, _ = json.Marshal(c1)
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
assert.NoError(t, err)
err = config.LoadConfig(configDir, configFilePath)
err = config.LoadConfig(configDir, confName)
assert.NoError(t, err)
ftpdConf = config.GetFTPDConfig()
assert.NotEmpty(t, strings.TrimSpace(ftpdConf.Banner))
@ -109,8 +109,9 @@ func TestInvalidUploadMode(t *testing.T) {
assert.NoError(t, err)
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
assert.NoError(t, err)
err = config.LoadConfig(configDir, configFilePath)
assert.NotNil(t, err)
err = config.LoadConfig(configDir, confName)
assert.NoError(t, err)
assert.Equal(t, 0, config.GetCommonConfig().UploadMode)
err = os.Remove(configFilePath)
assert.NoError(t, err)
}
@ -131,8 +132,9 @@ func TestInvalidExternalAuthScope(t *testing.T) {
assert.NoError(t, err)
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
assert.NoError(t, err)
err = config.LoadConfig(configDir, configFilePath)
assert.NotNil(t, err)
err = config.LoadConfig(configDir, confName)
assert.NoError(t, err)
assert.Equal(t, 0, config.GetProviderConf().ExternalAuthScope)
err = os.Remove(configFilePath)
assert.NoError(t, err)
}
@ -153,8 +155,9 @@ func TestInvalidCredentialsPath(t *testing.T) {
assert.NoError(t, err)
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
assert.NoError(t, err)
err = config.LoadConfig(configDir, configFilePath)
assert.NotNil(t, err)
err = config.LoadConfig(configDir, confName)
assert.NoError(t, err)
assert.Equal(t, "credentials", config.GetProviderConf().CredentialsPath)
err = os.Remove(configFilePath)
assert.NoError(t, err)
}
@ -175,8 +178,9 @@ func TestInvalidProxyProtocol(t *testing.T) {
assert.NoError(t, err)
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
assert.NoError(t, err)
err = config.LoadConfig(configDir, configFilePath)
assert.NotNil(t, err)
err = config.LoadConfig(configDir, confName)
assert.NoError(t, err)
assert.Equal(t, 0, config.GetCommonConfig().ProxyProtocol)
err = os.Remove(configFilePath)
assert.NoError(t, err)
}
@ -197,8 +201,9 @@ func TestInvalidUsersBaseDir(t *testing.T) {
assert.NoError(t, err)
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
assert.NoError(t, err)
err = config.LoadConfig(configDir, configFilePath)
assert.NotNil(t, err)
err = config.LoadConfig(configDir, confName)
assert.NoError(t, err)
assert.Empty(t, config.GetProviderConf().UsersBaseDir)
err = os.Remove(configFilePath)
assert.NoError(t, err)
}
@ -225,7 +230,7 @@ func TestCommonParamsCompatibility(t *testing.T) {
assert.NoError(t, err)
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
assert.NoError(t, err)
err = config.LoadConfig(configDir, configFilePath)
err = config.LoadConfig(configDir, confName)
assert.NoError(t, err)
commonConf := config.GetCommonConfig()
assert.Equal(t, 21, commonConf.IdleTimeout)
@ -263,7 +268,7 @@ func TestHostKeyCompatibility(t *testing.T) {
assert.NoError(t, err)
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
assert.NoError(t, err)
err = config.LoadConfig(configDir, configFilePath)
err = config.LoadConfig(configDir, confName)
assert.NoError(t, err)
sftpdConf = config.GetSFTPDConfig()
assert.Equal(t, 2, len(sftpdConf.HostKeys))

View file

@ -24,8 +24,9 @@ Flags:
The `serve` command supports the following flags:
- `--config-dir` string. Location of the config dir. This directory should contain the configuration file and is used as the base directory for any files that use a relative path (eg. the private keys for the SFTP server, the SQLite or bblot database if you use SQLite or bbolt as data provider). The default value is "." or the value of `SFTPGO_CONFIG_DIR` environment variable.
- `--config-file` string. Name of the configuration file. It must be the name of a file stored in `config-dir`, not the absolute path to the configuration file. The specified file name must have no extension because we automatically append JSON, YAML, TOML, HCL and Java extensions when we search for the file. The default value is "sftpgo" (and therefore `sftpgo.json`, `sftpgo.yaml` and so on are searched) or the value of `SFTPGO_CONFIG_FILE` environment variable.
- `--config-dir` string. Location of the config dir. This directory is used as the base for files with a relative path, eg. the private keys for the SFTP server or the SQLite database if you use SQLite as data provider. The configuration file, if not explicitly set, is looked for in this dir. We support reading from JSON, TOML, YAML, HCL, envfile and Java properties config files. The default config file name is `sftpgo` and therefore `sftpgo.json`, `sftpgo.yaml` and so on are searched. The default value is the working directory (".") or the value of `SFTPGO_CONFIG_DIR` environment variable.
- `--config-file` string. This flag explicitly defines the path, name and extension of the config file. If must be an absolute path or a path relative to the configuration directory. The specified file name must have a supported extension (JSON, YAML, TOML, HCL or Java properties). The default value is empty or the value of `SFTPGO_CONFIG_FILE` environment variable.
- `--loaddata-from` string. Load users and folders from this file. The file must be specified as absolute path and it must contain a backup obtained using the `dumpdata` REST API or compatible content. The default value is empty or the value of `SFTPGO_LOADDATA_FROM` environment variable.
- `--loaddata-from` string. Load users and folders from this file. The file must be specified as absolute path and it must contain a backup obtained using the `dumpdata` REST API or compatible content. The default value is empty or the value of `SFTPGO_LOADDATA_FROM` environment variable.
- `--loaddata-clean` boolean. Determine if the loaddata-from file should be removed after a successful load. Default `false` or the value of `SFTPGO_LOADDATA_CLEAN` environment variable (1 or `true`, 0 or `false`).
- `--loaddata-mode`, integer. Restore mode for data to load. 0 means new users are added, existing users are updated. 1 means new users are added, existing users are not modified. Default 1 or the value of `SFTPGO_LOADDATA_MODE` environment variable.

View file

@ -72,6 +72,7 @@ func (s *Service) Start() error {
err := config.LoadConfig(s.ConfigDir, s.ConfigFile)
if err != nil {
logger.Error(logSender, "", "error loading configuration: %v", err)
return err
}
}
if !config.HasServicesToStart() {