config.go 6.13 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
	"os"
	"os/exec"

13 14 15 16
	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"
17
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util"
18 19 20 21 22 23 24
)

type ConfigField struct {
	Key   string
	Value interface{}
}

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

37
Examples:
38

39
Get the value of the 'datastore.path' key:
40

41
  $ ipfs config datastore.path
42

43
Set the value of the 'datastore.path' key:
44

45
  $ ipfs config datastore.path ~/.ipfs/datastore
46
`,
47
	},
48 49

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

Jeromy's avatar
Jeromy committed
61
		r, err := fsrepo.Open(req.InvocContext().ConfigRoot)
62
		if err != nil {
63 64
			res.SetError(err, cmds.ErrNormal)
			return
65
		}
66
		defer r.Close()
67

68
		var output *ConfigField
69
		if len(args) == 2 {
70
			value := args[1]
71 72 73 74 75 76 77 78 79 80 81

			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 {
82 83 84 85
				output, err = setConfig(r, key, value == "true")
			} else {
				output, err = setConfig(r, key, value)
			}
86
		} else {
87 88 89 90 91
			output, err = getConfig(r, key)
		}
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
92
		}
93
		res.SetOutput(output)
94
	},
95
	Marshalers: cmds.MarshalerMap{
96
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
97
			if len(res.Request().Arguments()) == 2 {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
98
				return nil, nil // dont output anything
99 100
			}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
101 102 103 104 105 106 107 108 109 110 111
			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)
112 113 114
			if err != nil {
				return nil, err
			}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
115
			buf = append(buf, byte('\n'))
116
			return bytes.NewReader(buf), nil
117 118
		},
	},
119
	Type: ConfigField{},
120
	Subcommands: map[string]*cmds.Command{
121 122 123
		"show":    configShowCmd,
		"edit":    configEditCmd,
		"replace": configReplaceCmd,
124 125 126 127
	},
}

var configShowCmd = &cmds.Command{
128
	Helptext: cmds.HelpText{
rht's avatar
rht committed
129
		Tagline: "Outputs the content of the config file.",
130 131
		ShortDescription: `
WARNING: Your private key is stored in the config file, and it will be
132 133
included in the output of this command.
`,
134
	},
135

136
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
137
		filename, err := config.Filename(req.InvocContext().ConfigRoot)
138
		if err != nil {
139 140
			res.SetError(err, cmds.ErrNormal)
			return
141 142
		}

143 144 145 146 147 148
		output, err := showConfig(filename)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		res.SetOutput(output)
149 150 151 152
	},
}

var configEditCmd = &cmds.Command{
153
	Helptext: cmds.HelpText{
rht's avatar
rht committed
154
		Tagline: "Opens the config file for editing in $EDITOR.",
155 156
		ShortDescription: `
To use 'ipfs config edit', you must have the $EDITOR environment
157 158
variable set to your preferred text editor.
`,
159
	},
160

161
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
162
		filename, err := config.Filename(req.InvocContext().ConfigRoot)
163
		if err != nil {
164 165
			res.SetError(err, cmds.ErrNormal)
			return
166 167
		}

168 169 170 171
		err = editConfig(filename)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
		}
172 173 174
	},
}

175 176
var configReplaceCmd = &cmds.Command{
	Helptext: cmds.HelpText{
rht's avatar
rht committed
177
		Tagline: "Replaces the config with <file>.",
178
		ShortDescription: `
179
Make sure to back up the config file first if neccessary, as this operation
180 181 182 183 184
can't be undone.
`,
	},

	Arguments: []cmds.Argument{
185
		cmds.FileArg("file", true, false, "The file to use as the new config."),
186
	},
187
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
188
		r, err := fsrepo.Open(req.InvocContext().ConfigRoot)
189
		if err != nil {
190 191
			res.SetError(err, cmds.ErrNormal)
			return
192 193 194 195 196
		}
		defer r.Close()

		file, err := req.Files().NextFile()
		if err != nil {
197 198
			res.SetError(err, cmds.ErrNormal)
			return
199 200 201
		}
		defer file.Close()

202 203 204 205 206
		err = replaceConfig(r, file)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
207 208 209
	},
}

210
func getConfig(r repo.Repo, key string) (*ConfigField, error) {
211
	value, err := r.GetConfigKey(key)
212
	if err != nil {
Richard Littauer's avatar
Richard Littauer committed
213
		return nil, fmt.Errorf("Failed to get config value: %q", err)
214 215 216 217 218 219 220
	}
	return &ConfigField{
		Key:   key,
		Value: value,
	}, nil
}

221
func setConfig(r repo.Repo, key string, value interface{}) (*ConfigField, error) {
222
	err := r.SetConfigKey(key, value)
223
	if err != nil {
224
		return nil, fmt.Errorf("Failed to set config value: %s (maybe use --json?)", err)
225
	}
226
	return getConfig(r, key)
227 228 229
}

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

232
	data, err := ioutil.ReadFile(filename)
233 234 235 236
	if err != nil {
		return nil, err
	}

237
	return bytes.NewReader(data), nil
238 239 240 241 242 243 244 245 246 247 248 249
}

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()
}
250 251 252 253 254 255 256 257 258

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)
}