From 6817fd474467face64b08440e5c938e6566c39d5 Mon Sep 17 00:00:00 2001 From: Lucas Molas Date: Mon, 3 Jan 2022 12:00:01 -0300 Subject: [PATCH] feat(cmds): allow to set the configuration file path --- cmd/ipfs/daemon.go | 5 +- cmd/ipfs/main.go | 2 +- config/config.go | 20 +++++- core/commands/config.go | 6 +- core/commands/root.go | 6 +- repo/fsrepo/fsrepo.go | 67 +++++++++---------- .../migrations/ipfsfetcher/ipfsfetcher.go | 22 +++--- .../ipfsfetcher/ipfsfetcher_test.go | 14 ++-- repo/fsrepo/migrations/migrations.go | 4 +- repo/fsrepo/migrations/migrations_test.go | 10 +-- 10 files changed, 88 insertions(+), 68 deletions(-) diff --git a/cmd/ipfs/daemon.go b/cmd/ipfs/daemon.go index 32cff04ef..14054c36f 100644 --- a/cmd/ipfs/daemon.go +++ b/cmd/ipfs/daemon.go @@ -298,7 +298,8 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment } // Read Migration section of IPFS config - migrationCfg, err := migrations.ReadMigrationConfig(cctx.ConfigRoot) + configFileOpt, _ := req.Options[commands.ConfigFileOption].(string) + migrationCfg, err := migrations.ReadMigrationConfig(cctx.ConfigRoot, configFileOpt) if err != nil { return err } @@ -309,7 +310,7 @@ func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment // to construct the particular IPFS fetcher implementation used here, // which is called only if an IPFS fetcher is needed. newIpfsFetcher := func(distPath string) migrations.Fetcher { - return ipfsfetcher.NewIpfsFetcher(distPath, 0, &cctx.ConfigRoot) + return ipfsfetcher.NewIpfsFetcher(distPath, 0, &cctx.ConfigRoot, configFileOpt) } // Fetch migrations from current distribution, or location from environ diff --git a/cmd/ipfs/main.go b/cmd/ipfs/main.go index b95129db4..6147a0cbf 100644 --- a/cmd/ipfs/main.go +++ b/cmd/ipfs/main.go @@ -303,7 +303,7 @@ func makeExecutor(req *cmds.Request, env interface{}) (cmds.Executor, error) { } func getRepoPath(req *cmds.Request) (string, error) { - repoOpt, found := req.Options["config"].(string) + repoOpt, found := req.Options[corecmds.RepoDirOption].(string) if found && repoOpt != "" { return repoOpt, nil } diff --git a/config/config.go b/config/config.go index 419a6a71f..1f4d9e83c 100644 --- a/config/config.go +++ b/config/config.go @@ -76,9 +76,23 @@ func Path(configroot, extension string) (string, error) { } // Filename returns the configuration file path given a configuration root -// directory. If the configuration root directory is empty, use the default one -func Filename(configroot string) (string, error) { - return Path(configroot, DefaultConfigFile) +// directory and a user-provided configuration file path argument with the +// following rules: +// * If the user-provided configuration file path is empty, use the default one. +// * If the configuration root directory is empty, use the default one. +// * If the user-provided configuration file path is only a file name, use the +// configuration root directory, otherwise use only the user-provided path +// and ignore the configuration root. +func Filename(configroot string, userConfigFile string) (string, error) { + if userConfigFile == "" { + return Path(configroot, DefaultConfigFile) + } + + if filepath.Dir(userConfigFile) == "." { + return Path(configroot, userConfigFile) + } + + return userConfigFile, nil } // HumanOutput gets a config value ready for printing diff --git a/core/commands/config.go b/core/commands/config.go index 3f009de9a..38e14c31d 100644 --- a/core/commands/config.go +++ b/core/commands/config.go @@ -186,7 +186,8 @@ NOTE: For security reasons, this command will omit your private key and remote s return err } - fname, err := config.Filename(cfgRoot) + configFileOpt, _ := req.Options[ConfigFileOption].(string) + fname, err := config.Filename(cfgRoot, configFileOpt) if err != nil { return err } @@ -291,7 +292,8 @@ variable set to your preferred text editor. return err } - filename, err := config.Filename(cfgRoot) + configFileOpt, _ := req.Options[ConfigFileOption].(string) + filename, err := config.Filename(cfgRoot, configFileOpt) if err != nil { return err } diff --git a/core/commands/root.go b/core/commands/root.go index b75c634f1..ef17d2f70 100644 --- a/core/commands/root.go +++ b/core/commands/root.go @@ -19,6 +19,8 @@ var log = logging.Logger("core/commands") var ErrNotOnline = errors.New("this command must be run in online mode. Try running 'ipfs daemon' first") const ( + RepoDirOption = "repo-dir" + ConfigFileOption = "config-file" ConfigOption = "config" DebugOption = "debug" LocalOption = "local" // DEPRECATED: use OfflineOption @@ -94,7 +96,9 @@ The CLI will exit with one of the following values: `, }, Options: []cmds.Option{ - cmds.StringOption(ConfigOption, "c", "Path to the configuration file to use."), + cmds.StringOption(RepoDirOption, "Path to the repository directory to use."), + cmds.StringOption(ConfigFileOption, "Path to the configuration file to use."), + cmds.StringOption(ConfigOption, "c", "[DEPRECATED] Path to the configuration file to use."), cmds.BoolOption(DebugOption, "D", "Operate in debug mode."), cmds.BoolOption(cmds.OptLongHelp, "Show the full command help text."), cmds.BoolOption(cmds.OptShortHelp, "Show a short version of the command help text."), diff --git a/repo/fsrepo/fsrepo.go b/repo/fsrepo/fsrepo.go index 7ec0e0ab8..7ad634c84 100644 --- a/repo/fsrepo/fsrepo.go +++ b/repo/fsrepo/fsrepo.go @@ -96,6 +96,9 @@ type FSRepo struct { closed bool // path is the file-system path path string + // Path to the configuration file that may or may not be inside the FSRepo + // path (see config.Filename for more details). + configFilePath string // lockfile is the file system lock to prevent others from opening // the same fsrepo path concurrently lockfile io.Closer @@ -111,16 +114,25 @@ var _ repo.Repo = (*FSRepo)(nil) // initialized. func Open(repoPath string) (repo.Repo, error) { fn := func() (repo.Repo, error) { - return open(repoPath) + return open(repoPath, "") } return onlyOne.Open(repoPath, fn) } -func open(repoPath string) (repo.Repo, error) { +// OpenWithUserConfig is the equivalent to the Open function above but with the +// option to set the configuration file path instead of using the default. +func OpenWithUserConfig(repoPath string, userConfigFilePath string) (repo.Repo, error) { + fn := func() (repo.Repo, error) { + return open(repoPath, userConfigFilePath) + } + return onlyOne.Open(repoPath, fn) +} + +func open(repoPath string, userConfigFilePath string) (repo.Repo, error) { packageLock.Lock() defer packageLock.Unlock() - r, err := newFSRepo(repoPath) + r, err := newFSRepo(repoPath, userConfigFilePath) if err != nil { return nil, err } @@ -185,13 +197,19 @@ func open(repoPath string) (repo.Repo, error) { return r, nil } -func newFSRepo(rpath string) (*FSRepo, error) { +func newFSRepo(rpath string, userConfigFilePath string) (*FSRepo, error) { expPath, err := homedir.Expand(filepath.Clean(rpath)) if err != nil { return nil, err } - return &FSRepo{path: expPath}, nil + configFilePath, err := config.Filename(rpath, userConfigFilePath) + if err != nil { + // FIXME: Personalize this when the user config path is "". + return nil, fmt.Errorf("finding config filepath from repo %s and user config %s: %w", + rpath, userConfigFilePath, err) + } + return &FSRepo{path: expPath, configFilePath: configFilePath}, nil } func checkInitialized(path string) error { @@ -208,7 +226,7 @@ func checkInitialized(path string) error { // configIsInitialized returns true if the repo is initialized at // provided |path|. func configIsInitialized(path string) bool { - configFilename, err := config.Filename(path) + configFilename, err := config.Filename(path, "") if err != nil { return false } @@ -222,7 +240,7 @@ func initConfig(path string, conf *config.Config) error { if configIsInitialized(path) { return nil } - configFilename, err := config.Filename(path) + configFilename, err := config.Filename(path, "") if err != nil { return err } @@ -372,11 +390,7 @@ func (r *FSRepo) SetAPIAddr(addr ma.Multiaddr) error { // openConfig returns an error if the config file is not present. func (r *FSRepo) openConfig() error { - configFilename, err := config.Filename(r.path) - if err != nil { - return err - } - conf, err := serialize.Load(configFilename) + conf, err := serialize.Load(r.configFilePath) if err != nil { return err } @@ -507,12 +521,7 @@ func (r *FSRepo) BackupConfig(prefix string) (string, error) { } defer temp.Close() - configFilename, err := config.Filename(r.path) - if err != nil { - return "", err - } - - orig, err := os.OpenFile(configFilename, os.O_RDONLY, 0600) + orig, err := os.OpenFile(r.configFilePath, os.O_RDONLY, 0600) if err != nil { return "", err } @@ -546,15 +555,11 @@ func (r *FSRepo) SetConfig(updated *config.Config) error { packageLock.Lock() defer packageLock.Unlock() - configFilename, err := config.Filename(r.path) - if err != nil { - return err - } // to avoid clobbering user-provided keys, must read the config from disk // as a map, write the updated struct values to the map and write the map // to disk. var mapconf map[string]interface{} - if err := serialize.ReadConfigFile(configFilename, &mapconf); err != nil { + if err := serialize.ReadConfigFile(r.configFilePath, &mapconf); err != nil { return err } m, err := config.ToMap(updated) @@ -562,7 +567,7 @@ func (r *FSRepo) SetConfig(updated *config.Config) error { return err } mergedMap := common.MapMergeDeep(mapconf, m) - if err := serialize.WriteConfigFile(configFilename, mergedMap); err != nil { + if err := serialize.WriteConfigFile(r.configFilePath, mergedMap); err != nil { return err } // Do not use `*r.config = ...`. This will modify the *shared* config @@ -580,12 +585,8 @@ func (r *FSRepo) GetConfigKey(key string) (interface{}, error) { return nil, errors.New("repo is closed") } - filename, err := config.Filename(r.path) - if err != nil { - return nil, err - } var cfg map[string]interface{} - if err := serialize.ReadConfigFile(filename, &cfg); err != nil { + if err := serialize.ReadConfigFile(r.configFilePath, &cfg); err != nil { return nil, err } return common.MapGetKV(cfg, key) @@ -600,13 +601,9 @@ func (r *FSRepo) SetConfigKey(key string, value interface{}) error { return errors.New("repo is closed") } - filename, err := config.Filename(r.path) - if err != nil { - return err - } // Load into a map so we don't end up writing any additional defaults to the config file. var mapconf map[string]interface{} - if err := serialize.ReadConfigFile(filename, &mapconf); err != nil { + if err := serialize.ReadConfigFile(r.configFilePath, &mapconf); err != nil { return err } @@ -636,7 +633,7 @@ func (r *FSRepo) SetConfigKey(key string, value interface{}) error { } r.config = conf - if err := serialize.WriteConfigFile(filename, mapconf); err != nil { + if err := serialize.WriteConfigFile(r.configFilePath, mapconf); err != nil { return err } diff --git a/repo/fsrepo/migrations/ipfsfetcher/ipfsfetcher.go b/repo/fsrepo/migrations/ipfsfetcher/ipfsfetcher.go index 21a6038a7..da8f83ac0 100644 --- a/repo/fsrepo/migrations/ipfsfetcher/ipfsfetcher.go +++ b/repo/fsrepo/migrations/ipfsfetcher/ipfsfetcher.go @@ -33,9 +33,10 @@ const ( ) type IpfsFetcher struct { - distPath string - limit int64 - repoRoot *string + distPath string + limit int64 + repoRoot *string + userConfigFile string openOnce sync.Once openErr error @@ -62,11 +63,12 @@ var _ migrations.Fetcher = (*IpfsFetcher)(nil) // Bootstrap and peer information in read from the IPFS config file in // repoRoot, unless repoRoot is nil. If repoRoot is empty (""), then read the // config from the default IPFS directory. -func NewIpfsFetcher(distPath string, fetchLimit int64, repoRoot *string) *IpfsFetcher { +func NewIpfsFetcher(distPath string, fetchLimit int64, repoRoot *string, userConfigFile string) *IpfsFetcher { f := &IpfsFetcher{ - limit: defaultFetchLimit, - distPath: migrations.LatestIpfsDist, - repoRoot: repoRoot, + limit: defaultFetchLimit, + distPath: migrations.LatestIpfsDist, + repoRoot: repoRoot, + userConfigFile: userConfigFile, } if distPath != "" { @@ -92,7 +94,7 @@ func (f *IpfsFetcher) Fetch(ctx context.Context, filePath string) ([]byte, error // Initialize and start IPFS node on first call to Fetch, since the fetcher // may be created by not used. f.openOnce.Do(func() { - bootstrap, peers := readIpfsConfig(f.repoRoot) + bootstrap, peers := readIpfsConfig(f.repoRoot, f.userConfigFile) f.ipfsTmpDir, f.openErr = initTempNode(ctx, bootstrap, peers) if f.openErr != nil { return @@ -288,12 +290,12 @@ func parsePath(fetchPath string) (ipath.Path, error) { return ipfsPath, ipfsPath.IsValid() } -func readIpfsConfig(repoRoot *string) (bootstrap []string, peers []peer.AddrInfo) { +func readIpfsConfig(repoRoot *string, userConfigFile string) (bootstrap []string, peers []peer.AddrInfo) { if repoRoot == nil { return } - cfgPath, err := config.Filename(*repoRoot) + cfgPath, err := config.Filename(*repoRoot, userConfigFile) if err != nil { fmt.Fprintln(os.Stderr, err) return diff --git a/repo/fsrepo/migrations/ipfsfetcher/ipfsfetcher_test.go b/repo/fsrepo/migrations/ipfsfetcher/ipfsfetcher_test.go index e300371a6..4e882b7ad 100644 --- a/repo/fsrepo/migrations/ipfsfetcher/ipfsfetcher_test.go +++ b/repo/fsrepo/migrations/ipfsfetcher/ipfsfetcher_test.go @@ -26,7 +26,7 @@ func TestIpfsFetcher(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - fetcher := NewIpfsFetcher("", 0, nil) + fetcher := NewIpfsFetcher("", 0, nil, "") defer fetcher.Close() out, err := fetcher.Fetch(ctx, "go-ipfs/versions") @@ -62,7 +62,7 @@ func TestInitIpfsFetcher(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - f := NewIpfsFetcher("", 0, nil) + f := NewIpfsFetcher("", 0, nil, "") defer f.Close() // Init ipfs repo @@ -132,7 +132,7 @@ func TestReadIpfsConfig(t *testing.T) { ` noSuchDir := "no_such_dir-5953aa51-1145-4efd-afd1-a069075fcf76" - bootstrap, peers := readIpfsConfig(&noSuchDir) + bootstrap, peers := readIpfsConfig(&noSuchDir, "") if bootstrap != nil { t.Error("expected nil bootstrap") } @@ -142,12 +142,12 @@ func TestReadIpfsConfig(t *testing.T) { tmpDir := makeConfig(t, testConfig) - bootstrap, peers = readIpfsConfig(nil) + bootstrap, peers = readIpfsConfig(nil, "") if bootstrap != nil || peers != nil { t.Fatal("expected nil ipfs config items") } - bootstrap, peers = readIpfsConfig(&tmpDir) + bootstrap, peers = readIpfsConfig(&tmpDir, "") if len(bootstrap) != 2 { t.Fatal("wrong number of bootstrap addresses") } @@ -189,7 +189,7 @@ func TestBadBootstrappingIpfsConfig(t *testing.T) { tmpDir := makeConfig(t, configBadBootstrap) - bootstrap, peers := readIpfsConfig(&tmpDir) + bootstrap, peers := readIpfsConfig(&tmpDir, "") if bootstrap != nil { t.Fatal("expected nil bootstrap") } @@ -219,7 +219,7 @@ func TestBadPeersIpfsConfig(t *testing.T) { tmpDir := makeConfig(t, configBadPeers) - bootstrap, peers := readIpfsConfig(&tmpDir) + bootstrap, peers := readIpfsConfig(&tmpDir, "") if peers != nil { t.Fatal("expected nil peers") } diff --git a/repo/fsrepo/migrations/migrations.go b/repo/fsrepo/migrations/migrations.go index 726870ea0..02738483e 100644 --- a/repo/fsrepo/migrations/migrations.go +++ b/repo/fsrepo/migrations/migrations.go @@ -115,12 +115,12 @@ func ExeName(name string) string { // ReadMigrationConfig reads the Migration section of the IPFS config, avoiding // reading anything other than the Migration section. That way, we're free to // make arbitrary changes to all _other_ sections in migrations. -func ReadMigrationConfig(repoRoot string) (*config.Migration, error) { +func ReadMigrationConfig(repoRoot string, userConfigFile string) (*config.Migration, error) { var cfg struct { Migration config.Migration } - cfgPath, err := config.Filename(repoRoot) + cfgPath, err := config.Filename(repoRoot, userConfigFile) if err != nil { return nil, err } diff --git a/repo/fsrepo/migrations/migrations_test.go b/repo/fsrepo/migrations/migrations_test.go index 2472d4706..4fb757747 100644 --- a/repo/fsrepo/migrations/migrations_test.go +++ b/repo/fsrepo/migrations/migrations_test.go @@ -221,7 +221,7 @@ var testConfig = ` func TestReadMigrationConfigDefaults(t *testing.T) { tmpDir := makeConfig(t, "{}") - cfg, err := ReadMigrationConfig(tmpDir) + cfg, err := ReadMigrationConfig(tmpDir, "") if err != nil { t.Fatal(err) } @@ -243,7 +243,7 @@ func TestReadMigrationConfigDefaults(t *testing.T) { func TestReadMigrationConfigErrors(t *testing.T) { tmpDir := makeConfig(t, `{"Migration": {"Keep": "badvalue"}}`) - _, err := ReadMigrationConfig(tmpDir) + _, err := ReadMigrationConfig(tmpDir, "") if err == nil { t.Fatal("expected error") } @@ -252,13 +252,13 @@ func TestReadMigrationConfigErrors(t *testing.T) { } os.RemoveAll(tmpDir) - _, err = ReadMigrationConfig(tmpDir) + _, err = ReadMigrationConfig(tmpDir, "") if err == nil { t.Fatal("expected error") } tmpDir = makeConfig(t, `}{`) - _, err = ReadMigrationConfig(tmpDir) + _, err = ReadMigrationConfig(tmpDir, "") if err == nil { t.Fatal("expected error") } @@ -267,7 +267,7 @@ func TestReadMigrationConfigErrors(t *testing.T) { func TestReadMigrationConfig(t *testing.T) { tmpDir := makeConfig(t, testConfig) - cfg, err := ReadMigrationConfig(tmpDir) + cfg, err := ReadMigrationConfig(tmpDir, "") if err != nil { t.Fatal(err) }