diff --git a/cmd/sops/main.go b/cmd/sops/main.go index 0563a2daa..9853505af 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.LookupConfigFile that takes care of handling the returned warning. +func findConfigFile() (string, error) { + result, err := config.LookupConfigFile(".") + if len(result.Warning) > 0 && !showedConfigFileWarning { + showedConfigFileWarning = true + log.Warn(result.Warning) + } + return result.Path, 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..06ad57370 100644 --- a/config/config.go +++ b/config/config.go @@ -37,22 +37,65 @@ 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" + alternateConfigName = ".sops.yml" ) -// 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) { +// ConfigFileResult contains the path to a config file and any warnings +type ConfigFileResult struct { + Path string + Warning string +} + +// LookupConfigFile looks for a sops config file in the current working directory +// and on parent directories, up to the maxDepth limit. +// It returns a result containing the file path and any warnings. +func LookupConfigFile(start string) (ConfigFileResult, error) { filepath := path.Dir(start) + var foundAlternatePath string + 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 + configPath := path.Join(filepath, configFileName) + _, err := fs.Stat(configPath) + if err == nil { + result := ConfigFileResult{Path: configPath} + + if foundAlternatePath != "" { + result.Warning = fmt.Sprintf( + "ignoring %q when searching for config file; the config file must be called %q; using %q instead", + foundAlternatePath, configFileName, configPath) + } + return result, nil } + + // Check for alternate filename if we haven't found one yet + if foundAlternatePath == "" { + alternatePath := path.Join(filepath, alternateConfigName) + _, altErr := fs.Stat(alternatePath) + if altErr == nil { + foundAlternatePath = alternatePath + } + } + + filepath = path.Join(filepath, "..") } - return "", fmt.Errorf("Config file not found") + + // No config file found + result := ConfigFileResult{} + if foundAlternatePath != "" { + result.Warning = fmt.Sprintf( + "ignoring %q when searching for config file; the config file must be called %q", + foundAlternatePath, configFileName) + } + + return result, fmt.Errorf("config file not found") +} + +// 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) { + result, err := LookupConfigFile(start) + return result.Path, err } type DotenvStoreConfig struct{}