config.go 5.75 KB
Newer Older
1 2 3
package commands

import (
4
	"bytes"
5
	"encoding/json"
6 7 8
	"errors"
	"fmt"
	"io"
9
	"io/ioutil"
10 11 12 13
	"os"
	"os/exec"

	cmds "github.com/jbenet/go-ipfs/commands"
14
	repo "github.com/jbenet/go-ipfs/repo"
15
	config "github.com/jbenet/go-ipfs/repo/config"
16
	fsrepo "github.com/jbenet/go-ipfs/repo/fsrepo"
17
	u "github.com/jbenet/go-ipfs/util"
18 19 20 21 22 23 24
)

type ConfigField struct {
	Key   string
	Value interface{}
}

25
var ConfigCmd = &cmds.Command{
26 27 28 29 30
	Helptext: cmds.HelpText{
		Tagline: "get and set IPFS config values",
		Synopsis: `
ipfs config <key>          - Get value of <key>
ipfs config <key> <value>  - Set value of <key> to <value>
31 32
ipfs config show           - Show config file
ipfs config edit           - Edit config file in $EDITOR
33
ipfs config replace <file> - Replaces the config file with <file>
34 35 36 37 38 39 40 41 42 43 44
`,
		ShortDescription: `
ipfs config controls configuration variables. It works like 'git config'.
The configuration values are stored in a config file inside your IPFS
repository.`,
		LongDescription: `
ipfs config controls configuration variables. It works
much like 'git config'. The configuration values are stored in a config
file inside your IPFS repository.

EXAMPLES:
45

46
Get the value of the 'datastore.path' key:
47

48
  ipfs config datastore.path
49

50
Set the value of the 'datastore.path' key:
51

52
  ipfs config datastore.path ~/.go-ipfs/datastore
53
`,
54
	},
55 56

	Arguments: []cmds.Argument{
57 58
		cmds.StringArg("key", true, false, "The key of the config entry (e.g. \"Addresses.API\")"),
		cmds.StringArg("value", false, false, "The value to set the config entry to"),
59
	},
60
	Run: func(req cmds.Request, res cmds.Response) {
61
		args := req.Arguments()
62
		key := args[0]
63

64 65
		r := fsrepo.At(req.Context().ConfigRoot)
		if err := r.Open(); err != nil {
66 67
			res.SetError(err, cmds.ErrNormal)
			return
68
		}
69
		defer r.Close()
70

71 72
		var err error
		var output *ConfigField
73
		if len(args) == 2 {
74 75
			value := args[1]
			output, err = setConfig(r, key, value)
76
		} else {
77 78 79 80 81
			output, err = getConfig(r, key)
		}
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
82
		}
83
		res.SetOutput(output)
84
	},
85
	Marshalers: cmds.MarshalerMap{
86
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
87
			if len(res.Request().Arguments()) == 2 {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
88
				return nil, nil // dont output anything
89 90
			}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
91 92 93 94 95 96 97 98 99 100 101
			v := res.Output()
			if v == nil {
				k := res.Request().Arguments()[0]
				return nil, fmt.Errorf("config does not contain key: %s", k)
			}
			vf, ok := v.(*ConfigField)
			if !ok {
				return nil, u.ErrCast()
			}

			buf, err := config.HumanOutput(vf.Value)
102 103 104
			if err != nil {
				return nil, err
			}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
105
			buf = append(buf, byte('\n'))
106
			return bytes.NewReader(buf), nil
107 108
		},
	},
109
	Type: ConfigField{},
110
	Subcommands: map[string]*cmds.Command{
111 112 113
		"show":    configShowCmd,
		"edit":    configEditCmd,
		"replace": configReplaceCmd,
114 115 116 117
	},
}

var configShowCmd = &cmds.Command{
118 119 120 121
	Helptext: cmds.HelpText{
		Tagline: "Outputs the content of the config file",
		ShortDescription: `
WARNING: Your private key is stored in the config file, and it will be
122 123
included in the output of this command.
`,
124
	},
125

126
	Run: func(req cmds.Request, res cmds.Response) {
127 128
		filename, err := config.Filename(req.Context().ConfigRoot)
		if err != nil {
129 130
			res.SetError(err, cmds.ErrNormal)
			return
131 132
		}

133 134 135 136 137 138
		output, err := showConfig(filename)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		res.SetOutput(output)
139 140 141 142
	},
}

var configEditCmd = &cmds.Command{
143 144 145 146
	Helptext: cmds.HelpText{
		Tagline: "Opens the config file for editing in $EDITOR",
		ShortDescription: `
To use 'ipfs config edit', you must have the $EDITOR environment
147 148
variable set to your preferred text editor.
`,
149
	},
150

151
	Run: func(req cmds.Request, res cmds.Response) {
152 153
		filename, err := config.Filename(req.Context().ConfigRoot)
		if err != nil {
154 155
			res.SetError(err, cmds.ErrNormal)
			return
156 157
		}

158 159 160 161
		err = editConfig(filename)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
		}
162 163 164
	},
}

165 166
var configReplaceCmd = &cmds.Command{
	Helptext: cmds.HelpText{
167
		Tagline: "Replaces the config with `file>",
168 169 170 171 172 173 174 175 176
		ShortDescription: `
Make sure to back up the config file first if neccessary, this operation
can't be undone.
`,
	},

	Arguments: []cmds.Argument{
		cmds.FileArg("file", true, false, "The file to use as the new config"),
	},
177
	Run: func(req cmds.Request, res cmds.Response) {
178 179
		r := fsrepo.At(req.Context().ConfigRoot)
		if err := r.Open(); err != nil {
180 181
			res.SetError(err, cmds.ErrNormal)
			return
182 183 184 185 186
		}
		defer r.Close()

		file, err := req.Files().NextFile()
		if err != nil {
187 188
			res.SetError(err, cmds.ErrNormal)
			return
189 190 191
		}
		defer file.Close()

192 193 194 195 196
		err = replaceConfig(r, file)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
197 198 199
	},
}

200
func getConfig(r repo.Repo, key string) (*ConfigField, error) {
201
	value, err := r.GetConfigKey(key)
202 203 204 205 206 207 208 209 210
	if err != nil {
		return nil, fmt.Errorf("Failed to get config value: %s", err)
	}
	return &ConfigField{
		Key:   key,
		Value: value,
	}, nil
}

211
func setConfig(r repo.Repo, key, value string) (*ConfigField, error) {
212
	err := r.SetConfigKey(key, value)
213 214 215
	if err != nil {
		return nil, fmt.Errorf("Failed to set config value: %s", err)
	}
216
	return getConfig(r, key)
217 218 219
}

func showConfig(filename string) (io.Reader, error) {
Brian Tiger Chow's avatar
todo  
Brian Tiger Chow committed
220
	// TODO maybe we should omit privkey so we don't accidentally leak it?
221

222
	data, err := ioutil.ReadFile(filename)
223 224 225 226
	if err != nil {
		return nil, err
	}

227
	return bytes.NewReader(data), nil
228 229 230 231 232 233 234 235 236 237 238 239
}

func editConfig(filename string) error {
	editor := os.Getenv("EDITOR")
	if editor == "" {
		return errors.New("ENV variable $EDITOR not set")
	}

	cmd := exec.Command("sh", "-c", editor+" "+filename)
	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
	return cmd.Run()
}
240 241 242 243 244 245 246 247 248

func replaceConfig(r repo.Repo, file io.Reader) error {
	var cfg config.Config
	if err := json.NewDecoder(file).Decode(&cfg); err != nil {
		return errors.New("Failed to decode file as config")
	}

	return r.SetConfig(&cfg)
}