config.go 7.61 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
	"os"
	"os/exec"
12
	"strings"
13

14 15 16 17
	cmds "github.com/ipfs/go-ipfs/commands"
	repo "github.com/ipfs/go-ipfs/repo"
	config "github.com/ipfs/go-ipfs/repo/config"
	fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
18

19
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util"
20 21 22 23 24 25 26
)

type ConfigField struct {
	Key   string
	Value interface{}
}

27
var ConfigCmd = &cmds.Command{
28
	Helptext: cmds.HelpText{
29
		Tagline: "Get and set IPFS config values.",
30
		ShortDescription: `
Richard Littauer's avatar
Richard Littauer committed
31
'ipfs config' controls configuration variables. It works like 'git config'.
32 33 34
The configuration values are stored in a config file inside your IPFS
repository.`,
		LongDescription: `
Richard Littauer's avatar
Richard Littauer committed
35
'ipfs config' controls configuration variables. It works
36 37 38
much like 'git config'. The configuration values are stored in a config
file inside your IPFS repository.

39
Examples:
40

41
Get the value of the 'datastore.path' key:
42

43
  $ ipfs config datastore.path
44

45
Set the value of the 'datastore.path' key:
46

47
  $ ipfs config datastore.path ~/.ipfs/datastore
48
`,
49
	},
50 51

	Arguments: []cmds.Argument{
52 53
		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."),
54
	},
55
	Options: []cmds.Option{
56 57
		cmds.BoolOption("bool", "Set a boolean value.").Default(false),
		cmds.BoolOption("json", "Parse stringified JSON.").Default(false),
58
	},
59
	Run: func(req cmds.Request, res cmds.Response) {
60
		args := req.Arguments()
61
		key := args[0]
62

63
		// This is a temporary fix until we move the private key out of the config file
64 65
		switch strings.ToLower(key) {
		case "identity", "identity.privkey":
66 67 68 69 70
			res.SetError(fmt.Errorf("cannot show or change private key through API"), cmds.ErrNormal)
			return
		default:
		}

Jeromy's avatar
Jeromy committed
71
		r, err := fsrepo.Open(req.InvocContext().ConfigRoot)
72
		if err != nil {
73 74
			res.SetError(err, cmds.ErrNormal)
			return
75
		}
76
		defer r.Close()
77

78
		var output *ConfigField
79
		if len(args) == 2 {
80
			value := args[1]
81 82 83 84 85 86 87 88 89 90 91

			if parseJson, _, _ := req.Option("json").Bool(); parseJson {
				var jsonVal interface{}
				if err := json.Unmarshal([]byte(value), &jsonVal); err != nil {
					err = fmt.Errorf("failed to unmarshal json. %s", err)
					res.SetError(err, cmds.ErrNormal)
					return
				}

				output, err = setConfig(r, key, jsonVal)
			} else if isbool, _, _ := req.Option("bool").Bool(); isbool {
92 93 94 95
				output, err = setConfig(r, key, value == "true")
			} else {
				output, err = setConfig(r, key, value)
			}
96
		} else {
97 98 99 100 101
			output, err = getConfig(r, key)
		}
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
102
		}
103
		res.SetOutput(output)
104
	},
105
	Marshalers: cmds.MarshalerMap{
106
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
107
			if len(res.Request().Arguments()) == 2 {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
108
				return nil, nil // dont output anything
109 110
			}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
111 112 113 114 115 116 117 118 119 120 121
			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)
122 123 124
			if err != nil {
				return nil, err
			}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
125
			buf = append(buf, byte('\n'))
126
			return bytes.NewReader(buf), nil
127 128
		},
	},
129
	Type: ConfigField{},
130
	Subcommands: map[string]*cmds.Command{
131 132 133
		"show":    configShowCmd,
		"edit":    configEditCmd,
		"replace": configReplaceCmd,
134 135 136 137
	},
}

var configShowCmd = &cmds.Command{
138
	Helptext: cmds.HelpText{
rht's avatar
rht committed
139
		Tagline: "Outputs the content of the config file.",
140 141
		ShortDescription: `
WARNING: Your private key is stored in the config file, and it will be
142 143
included in the output of this command.
`,
144
	},
145

146
	Run: func(req cmds.Request, res cmds.Response) {
147
		fname, err := config.Filename(req.InvocContext().ConfigRoot)
148
		if err != nil {
149 150
			res.SetError(err, cmds.ErrNormal)
			return
151 152
		}

153
		data, err := ioutil.ReadFile(fname)
154 155 156 157
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
158 159 160 161 162 163 164 165

		var cfg map[string]interface{}
		err = json.Unmarshal(data, &cfg)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

Jeromy's avatar
Jeromy committed
166 167
		idmap, ok := cfg["Identity"].(map[string]interface{})
		if !ok {
168
			res.SetError(errors.New("config has no identity"), cmds.ErrNormal)
Jeromy's avatar
Jeromy committed
169 170 171
			return
		}

172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
		privKeyKey := "" // make sure we both find the name of privkey and we delete it
		for key, _ := range idmap {
			if strings.ToLower(key) == "privkey" {
				if privKeyKey != "" {
					res.SetError(errors.New("found multiple PrivKey keys"), cmds.ErrNormal)
					return
				}
				privKeyKey = key
			}
		}
		if privKeyKey == "" {
			res.SetError(errors.New("haven't found PriveKey key"), cmds.ErrNormal)
		}

		delete(idmap, privKeyKey)
187 188 189 190 191 192 193 194

		output, err := config.HumanOutput(cfg)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		res.SetOutput(bytes.NewReader(output))
195 196 197 198
	},
}

var configEditCmd = &cmds.Command{
199
	Helptext: cmds.HelpText{
rht's avatar
rht committed
200
		Tagline: "Opens the config file for editing in $EDITOR.",
201 202
		ShortDescription: `
To use 'ipfs config edit', you must have the $EDITOR environment
203 204
variable set to your preferred text editor.
`,
205
	},
206

207
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
208
		filename, err := config.Filename(req.InvocContext().ConfigRoot)
209
		if err != nil {
210 211
			res.SetError(err, cmds.ErrNormal)
			return
212 213
		}

214 215 216 217
		err = editConfig(filename)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
		}
218 219 220
	},
}

221 222
var configReplaceCmd = &cmds.Command{
	Helptext: cmds.HelpText{
rht's avatar
rht committed
223
		Tagline: "Replaces the config with <file>.",
224
		ShortDescription: `
225
Make sure to back up the config file first if neccessary, as this operation
226 227 228 229 230
can't be undone.
`,
	},

	Arguments: []cmds.Argument{
231
		cmds.FileArg("file", true, false, "The file to use as the new config."),
232
	},
233
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
234
		r, err := fsrepo.Open(req.InvocContext().ConfigRoot)
235
		if err != nil {
236 237
			res.SetError(err, cmds.ErrNormal)
			return
238 239 240 241 242
		}
		defer r.Close()

		file, err := req.Files().NextFile()
		if err != nil {
243 244
			res.SetError(err, cmds.ErrNormal)
			return
245 246 247
		}
		defer file.Close()

248 249 250 251 252
		err = replaceConfig(r, file)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
253 254 255
	},
}

256
func getConfig(r repo.Repo, key string) (*ConfigField, error) {
257
	value, err := r.GetConfigKey(key)
258
	if err != nil {
Richard Littauer's avatar
Richard Littauer committed
259
		return nil, fmt.Errorf("Failed to get config value: %q", err)
260 261 262 263 264 265 266
	}
	return &ConfigField{
		Key:   key,
		Value: value,
	}, nil
}

267
func setConfig(r repo.Repo, key string, value interface{}) (*ConfigField, error) {
268 269
	keyF, err := getConfig(r, "Identity.PrivKey")
	if err != nil {
270
		return nil, errors.New("failed to get PrivKey")
271 272 273
	}
	privkey := keyF.Value
	err = r.SetConfigKey(key, value)
274
	if err != nil {
275
		return nil, fmt.Errorf("failed to set config value: %s (maybe use --json?)", err)
276
	}
277
	err = r.SetConfigKey("Identity.PrivKey", privkey)
278
	if err != nil {
279
		return nil, errors.New("failed to set PrivKey")
280
	}
281
	return getConfig(r, key)
282 283 284 285 286 287 288 289 290 291 292 293
}

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()
}
294 295 296 297

func replaceConfig(r repo.Repo, file io.Reader) error {
	var cfg config.Config
	if err := json.NewDecoder(file).Decode(&cfg); err != nil {
298 299 300 301
		return errors.New("failed to decode file as config")
	}
	if len(cfg.Identity.PrivKey) != 0 {
		return errors.New("setting private key with API is not supported")
302 303
	}

304 305 306 307
	keyF, err := getConfig(r, "Identity.PrivKey")
	if err != nil {
		return fmt.Errorf("Failed to get PrivKey")
	}
308 309 310 311 312 313 314

	pkstr, ok := keyF.Value.(string)
	if !ok {
		return fmt.Errorf("private key in config was not a string")
	}

	cfg.Identity.PrivKey = pkstr
315

316 317
	return r.SetConfig(&cfg)
}