diff --git a/cmd/sops/main.go b/cmd/sops/main.go index 0563a2daa..a297d9609 100644 --- a/cmd/sops/main.go +++ b/cmd/sops/main.go @@ -44,7 +44,13 @@ import ( "github.com/getsops/sops/v3/version" ) -var log *logrus.Logger +var ( + log *logrus.Logger + + // Whether the config file warning was already shown to the user. + // Used and set by findConfigFile(). + showedConfigFileWarning bool +) func init() { log = logging.NewLogger("CMD") @@ -364,7 +370,7 @@ func main() { if c.GlobalString("config") != "" { configPath = c.GlobalString("config") } else { - configPath, err = config.FindConfigFile(".") + configPath, err = findConfigFile() if err != nil { return common.NewExitError(err, codes.ErrorGeneric) } @@ -685,7 +691,7 @@ func main() { if c.GlobalString("config") != "" { configPath = c.GlobalString("config") } else { - configPath, err = config.FindConfigFile(".") + configPath, err = findConfigFile() if err != nil { return common.NewExitError(err, codes.ErrorGeneric) } @@ -2183,11 +2189,21 @@ func keyservices(c *cli.Context) (svcs []keyservice.KeyServiceClient) { return } +// Wrapper of config.FindConfigFileEx that takes care of handling the returned wraning. +func findConfigFile() (string, error) { + configPath, err, warn := config.FindConfigFileEx(".") + if len(warn) > 0 && !showedConfigFileWarning { + showedConfigFileWarning = true + log.Warn(warn) + } + return configPath, err +} + func loadStoresConfig(context *cli.Context, path string) (*config.StoresConfig, error) { configPath := context.GlobalString("config") if configPath == "" { - // Ignore config not found errors returned from FindConfigFile since the config file is not mandatory - foundPath, err := config.FindConfigFile(".") + // Ignore config not found errors returned from findConfigFile since the config file is not mandatory + foundPath, err := findConfigFile() if err != nil { return config.NewStoresConfig(), nil } @@ -2322,14 +2338,14 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) { return []sops.KeyGroup{group}, nil } -// loadConfig will look for an existing config file, either provided through the command line, or using config.FindConfigFile. +// loadConfig will look for an existing config file, either provided through the command line, or using findConfigFile // Since a config file is not required, this function does not error when one is not found, and instead returns a nil config pointer func loadConfig(c *cli.Context, file string, kmsEncryptionContext map[string]*string) (*config.Config, error) { var err error configPath := c.GlobalString("config") if configPath == "" { - // Ignore config not found errors returned from FindConfigFile since the config file is not mandatory - configPath, err = config.FindConfigFile(".") + // Ignore config not found errors returned from findConfigFile since the config file is not mandatory + configPath, err = findConfigFile() if err != nil { // If we can't find a config file, but we were not explicitly requested to, assume it does not exist return nil, nil diff --git a/config/config.go b/config/config.go index 8d3dc4dd1..e6d36bb6e 100644 --- a/config/config.go +++ b/config/config.go @@ -37,22 +37,48 @@ func (fs osFS) Stat(name string) (os.FileInfo, error) { var fs fileSystem = osFS{stat: os.Stat} const ( - maxDepth = 100 - configFileName = ".sops.yaml" + maxDepth = 100 + configFileName = ".sops.yaml" + misspelledConfigFilename = ".sops.yml" ) +// FindConfigFileEx looks for a sops config file in the current working directory and on parent directories, up to the limit defined by the maxDepth constant. +// The third return value is a warning in case misspelled config files were found. +func FindConfigFileEx(start string) (string, error, string) { + filepath := path.Dir(start) + var foundMisspelledPath string + var warning string + for i := 0; i < maxDepth; i++ { + configPath := path.Join(filepath, configFileName) + _, err := fs.Stat(configPath) + if err != nil { + misspelledPath := path.Join(filepath, misspelledConfigFilename) + _, err = fs.Stat(misspelledPath) + if err == nil && len(foundMisspelledPath) == 0 { + foundMisspelledPath = misspelledPath + } + filepath = path.Join(filepath, "..") + } else { + if len(foundMisspelledPath) > 0 { + warning = fmt.Sprintf( + "Ignoring %q when searching for config file. The config file must be called %q."+ + " Found and using %q further up the directory tree instead.", + foundMisspelledPath, configFileName, configPath) + } + return configPath, nil, warning + } + } + err := fmt.Errorf("Config file not found") + if len(foundMisspelledPath) > 0 { + warning = fmt.Sprintf("Ignoring %q when searching for config file. The config file must be called %q.", foundMisspelledPath, configFileName) + } + return "", err, warning +} + // FindConfigFile looks for a sops config file in the current working directory and on parent directories, up to the limit defined by the maxDepth constant. func FindConfigFile(start string) (string, error) { - filepath := path.Dir(start) - for i := 0; i < maxDepth; i++ { - _, err := fs.Stat(path.Join(filepath, configFileName)) - if err != nil { - filepath = path.Join(filepath, "..") - } else { - return path.Join(filepath, configFileName), nil - } - } - return "", fmt.Errorf("Config file not found") + config, err, _ := FindConfigFileEx(start) + return config, err } type DotenvStoreConfig struct{}