go-ipfs-config: refactor(repo/config) move config under repo
Brian Tiger Chow committed
Jan 12, 2015 at 11:01 UTC
99929de0506ca78419163476d3506fc47698afd2
4 files changed
+423
config/config.go
new
+219
@@ -0,0 +1,219 @@
1
+// package config implements the ipfs config file datastructures and utilities.
2
+package config
3
+
4
+import (
5
+ "encoding/base64"
6
+ "errors"
7
+ "os"
8
+ "path/filepath"
9
+ "strings"
10
+
11
+ ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
12
+ mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
13
+
14
+ ic "github.com/jbenet/go-ipfs/p2p/crypto"
15
+ u "github.com/jbenet/go-ipfs/util"
16
+ "github.com/jbenet/go-ipfs/util/debugerror"
17
+)
18
+
19
+var log = u.Logger("config")
20
+
21
+// Identity tracks the configuration of the local node's identity.
22
+type Identity struct {
23
+ PeerID string
24
+ PrivKey string
25
+}
26
+
27
+// Logs tracks the configuration of the event logger
28
+type Logs struct {
29
+ Filename string
30
+ MaxSizeMB uint64
31
+ MaxBackups uint64
32
+ MaxAgeDays uint64
33
+}
34
+
35
+// Datastore tracks the configuration of the datastore.
36
+type Datastore struct {
37
+ Type string
38
+ Path string
39
+}
40
+
41
+// Addresses stores the (string) multiaddr addresses for the node.
42
+type Addresses struct {
43
+ Swarm []string // addresses for the swarm network
44
+ API string // address for the local API (RPC)
45
+ Gateway string // address to listen on for IPFS HTTP object gateway
46
+}
47
+
48
+// Mounts stores the (string) mount points
49
+type Mounts struct {
50
+ IPFS string
51
+ IPNS string
52
+}
53
+
54
+// BootstrapPeer is a peer used to bootstrap the network.
55
+type BootstrapPeer struct {
56
+ Address string
57
+ PeerID string // until multiaddr supports ipfs, use another field.
58
+}
59
+
60
+func (bp *BootstrapPeer) String() string {
61
+ return bp.Address + "/" + bp.PeerID
62
+}
63
+
64
+func ParseBootstrapPeer(addr string) (BootstrapPeer, error) {
65
+ // to be replaced with just multiaddr parsing, once ptp is a multiaddr protocol
66
+ idx := strings.LastIndex(addr, "/")
67
+ if idx == -1 {
68
+ return BootstrapPeer{}, errors.New("invalid address")
69
+ }
70
+ addrS := addr[:idx]
71
+ peeridS := addr[idx+1:]
72
+
73
+ // make sure addrS parses as a multiaddr.
74
+ if len(addrS) > 0 {
75
+ maddr, err := ma.NewMultiaddr(addrS)
76
+ if err != nil {
77
+ return BootstrapPeer{}, err
78
+ }
79
+
80
+ addrS = maddr.String()
81
+ }
82
+
83
+ // make sure idS parses as a peer.ID
84
+ _, err := mh.FromB58String(peeridS)
85
+ if err != nil {
86
+ return BootstrapPeer{}, err
87
+ }
88
+
89
+ return BootstrapPeer{
90
+ Address: addrS,
91
+ PeerID: peeridS,
92
+ }, nil
93
+}
94
+
95
+func ParseBootstrapPeers(addrs []string) ([]BootstrapPeer, error) {
96
+ peers := make([]BootstrapPeer, len(addrs))
97
+ var err error
98
+ for i, addr := range addrs {
99
+ peers[i], err = ParseBootstrapPeer(addr)
100
+ if err != nil {
101
+ return nil, err
102
+ }
103
+ }
104
+ return peers, nil
105
+}
106
+
107
+// Tour stores the ipfs tour read-list and resume point
108
+type Tour struct {
109
+ Last string // last tour topic read
110
+ // Done []string // all topics done so far
111
+}
112
+
113
+// Config is used to load IPFS config files.
114
+type Config struct {
115
+ Identity Identity // local node's peer identity
116
+ Datastore Datastore // local node's storage
117
+ Addresses Addresses // local node's addresses
118
+ Mounts Mounts // local node's mount points
119
+ Version Version // local node's version management
120
+ Bootstrap []BootstrapPeer // local nodes's bootstrap peers
121
+ Tour Tour // local node's tour position
122
+ Logs Logs // local node's event log configuration
123
+}
124
+
125
+// DefaultPathRoot is the path to the default config dir location.
126
+const DefaultPathRoot = "~/.go-ipfs"
127
+
128
+// DefaultConfigFile is the filename of the configuration file
129
+const DefaultConfigFile = "config"
130
+
131
+// DefaultDataStoreDirectory is the directory to store all the local IPFS data.
132
+const DefaultDataStoreDirectory = "datastore"
133
+
134
+// EnvDir is the environment variable used to change the path root.
135
+const EnvDir = "IPFS_DIR"
136
+
137
+// LogsDefaultDirectory is the directory to store all IPFS event logs.
138
+var LogsDefaultDirectory = "logs"
139
+
140
+// PathRoot returns the default configuration root directory
141
+func PathRoot() (string, error) {
142
+ dir := os.Getenv(EnvDir)
143
+ var err error
144
+ if len(dir) == 0 {
145
+ dir, err = u.TildeExpansion(DefaultPathRoot)
146
+ }
147
+ return dir, err
148
+}
149
+
150
+// Path returns the path `extension` relative to the configuration root. If an
151
+// empty string is provided for `configroot`, the default root is used.
152
+func Path(configroot, extension string) (string, error) {
153
+ if len(configroot) == 0 {
154
+ dir, err := PathRoot()
155
+ if err != nil {
156
+ return "", err
157
+ }
158
+ return filepath.Join(dir, extension), nil
159
+
160
+ }
161
+ return filepath.Join(configroot, extension), nil
162
+}
163
+
164
+// DataStorePath returns the default data store path given a configuration root
165
+// (set an empty string to have the default configuration root)
166
+func DataStorePath(configroot string) (string, error) {
167
+ return Path(configroot, DefaultDataStoreDirectory)
168
+}
169
+
170
+// LogsPath returns the default path for event logs given a configuration root
171
+// (set an empty string to have the default configuration root)
172
+func LogsPath(configroot string) (string, error) {
173
+ return Path(configroot, LogsDefaultDirectory)
174
+}
175
+
176
+// Filename returns the configuration file path given a configuration root
177
+// directory. If the configuration root directory is empty, use the default one
178
+func Filename(configroot string) (string, error) {
179
+ return Path(configroot, DefaultConfigFile)
180
+}
181
+
182
+// DecodePrivateKey is a helper to decode the users PrivateKey
183
+func (i *Identity) DecodePrivateKey(passphrase string) (ic.PrivKey, error) {
184
+ pkb, err := base64.StdEncoding.DecodeString(i.PrivKey)
185
+ if err != nil {
186
+ return nil, err
187
+ }
188
+
189
+ // currently storing key unencrypted. in the future we need to encrypt it.
190
+ // TODO(security)
191
+ return ic.UnmarshalPrivateKey(pkb)
192
+}
193
+
194
+// Load reads given file and returns the read config, or error.
195
+func Load(filename string) (*Config, error) {
196
+ // if nothing is there, fail. User must run 'ipfs init'
197
+ if !u.FileExists(filename) {
198
+ return nil, debugerror.New("ipfs not initialized, please run 'ipfs init'")
199
+ }
200
+
201
+ var cfg Config
202
+ err := ReadConfigFile(filename, &cfg)
203
+ if err != nil {
204
+ return nil, err
205
+ }
206
+
207
+ // tilde expansion on datastore path
208
+ cfg.Datastore.Path, err = u.TildeExpansion(cfg.Datastore.Path)
209
+ if err != nil {
210
+ return nil, err
211
+ }
212
+
213
+ return &cfg, err
214
+}
215
+
216
+// Set sets the value of a particular config key
217
+func Set(filename, key, value string) error {
218
+ return WriteConfigKey(filename, key, value)
219
+}
config/config_test.go
new
+24
@@ -0,0 +1,24 @@
1
+package config
2
+
3
+import (
4
+ "testing"
5
+)
6
+
7
+func TestConfig(t *testing.T) {
8
+ const filename = ".ipfsconfig"
9
+ const dsPath = "/path/to/datastore"
10
+ cfgWritten := new(Config)
11
+ cfgWritten.Datastore.Path = dsPath
12
+ err := WriteConfigFile(filename, cfgWritten)
13
+ if err != nil {
14
+ t.Error(err)
15
+ }
16
+ cfgRead, err := Load(filename)
17
+ if err != nil {
18
+ t.Error(err)
19
+ return
20
+ }
21
+ if cfgWritten.Datastore.Path != cfgRead.Datastore.Path {
22
+ t.Fail()
23
+ }
24
+}
config/serialize.go
new
+144
@@ -0,0 +1,144 @@
1
+package config
2
+
3
+import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "io"
7
+ "os"
8
+ "path/filepath"
9
+ "strings"
10
+)
11
+
12
+// ReadConfigFile reads the config from `filename` into `cfg`.
13
+func ReadConfigFile(filename string, cfg interface{}) error {
14
+ f, err := os.Open(filename)
15
+ if err != nil {
16
+ return err
17
+ }
18
+ defer f.Close()
19
+
20
+ if err := Decode(f, cfg); err != nil {
21
+ return fmt.Errorf("Failure to decode config: %s", err)
22
+ }
23
+ return nil
24
+}
25
+
26
+// WriteConfigFile writes the config from `cfg` into `filename`.
27
+func WriteConfigFile(filename string, cfg interface{}) error {
28
+ err := os.MkdirAll(filepath.Dir(filename), 0775)
29
+ if err != nil {
30
+ return err
31
+ }
32
+
33
+ f, err := os.Create(filename)
34
+ if err != nil {
35
+ return err
36
+ }
37
+ defer f.Close()
38
+
39
+ return Encode(f, cfg)
40
+}
41
+
42
+// WriteFile writes the buffer at filename
43
+func WriteFile(filename string, buf []byte) error {
44
+ err := os.MkdirAll(filepath.Dir(filename), 0775)
45
+ if err != nil {
46
+ return err
47
+ }
48
+
49
+ f, err := os.Create(filename)
50
+ if err != nil {
51
+ return err
52
+ }
53
+ defer f.Close()
54
+
55
+ _, err = f.Write(buf)
56
+ return err
57
+}
58
+
59
+// HumanOutput gets a config value ready for printing
60
+func HumanOutput(value interface{}) ([]byte, error) {
61
+ s, ok := value.(string)
62
+ if ok {
63
+ return []byte(strings.Trim(s, "\n")), nil
64
+ }
65
+ return Marshal(value)
66
+}
67
+
68
+// Marshal configuration with JSON
69
+func Marshal(value interface{}) ([]byte, error) {
70
+ // need to prettyprint, hence MarshalIndent, instead of Encoder
71
+ return json.MarshalIndent(value, "", " ")
72
+}
73
+
74
+// Encode configuration with JSON
75
+func Encode(w io.Writer, value interface{}) error {
76
+ // need to prettyprint, hence MarshalIndent, instead of Encoder
77
+ buf, err := Marshal(value)
78
+ if err != nil {
79
+ return err
80
+ }
81
+
82
+ _, err = w.Write(buf)
83
+ return err
84
+}
85
+
86
+// Decode configuration with JSON
87
+func Decode(r io.Reader, value interface{}) error {
88
+ return json.NewDecoder(r).Decode(value)
89
+}
90
+
91
+// ReadConfigKey retrieves only the value of a particular key
92
+func ReadConfigKey(filename, key string) (interface{}, error) {
93
+ var cfg interface{}
94
+ if err := ReadConfigFile(filename, &cfg); err != nil {
95
+ return nil, err
96
+ }
97
+
98
+ var ok bool
99
+ cursor := cfg
100
+ parts := strings.Split(key, ".")
101
+ for i, part := range parts {
102
+ cursor, ok = cursor.(map[string]interface{})[part]
103
+ if !ok {
104
+ sofar := strings.Join(parts[:i], ".")
105
+ return nil, fmt.Errorf("%s key has no attributes", sofar)
106
+ }
107
+ }
108
+ return cursor, nil
109
+}
110
+
111
+// WriteConfigKey writes the value of a particular key
112
+func WriteConfigKey(filename, key string, value interface{}) error {
113
+ var cfg interface{}
114
+ if err := ReadConfigFile(filename, &cfg); err != nil {
115
+ return err
116
+ }
117
+
118
+ var ok bool
119
+ var mcursor map[string]interface{}
120
+ cursor := cfg
121
+
122
+ parts := strings.Split(key, ".")
123
+ for i, part := range parts {
124
+ mcursor, ok = cursor.(map[string]interface{})
125
+ if !ok {
126
+ sofar := strings.Join(parts[:i], ".")
127
+ return fmt.Errorf("%s key is not a map", sofar)
128
+ }
129
+
130
+ // last part? set here
131
+ if i == (len(parts) - 1) {
132
+ mcursor[part] = value
133
+ break
134
+ }
135
+
136
+ cursor, ok = mcursor[part]
137
+ if !ok { // create map if this is empty
138
+ mcursor[part] = map[string]interface{}{}
139
+ cursor = mcursor[part]
140
+ }
141
+ }
142
+
143
+ return WriteConfigFile(filename, cfg)
144
+}
config/version_test.go
new
+36
@@ -0,0 +1,36 @@
1
+package config
2
+
3
+import (
4
+ "strings"
5
+ "testing"
6
+)
7
+
8
+func TestAutoUpdateValues(t *testing.T) {
9
+ var tval struct {
10
+ AutoUpdate AutoUpdateSetting
11
+ }
12
+ tests := []struct {
13
+ input string
14
+ val AutoUpdateSetting
15
+ err error
16
+ }{
17
+ {`{"hello":123}`, AutoUpdateNever, nil}, // zero value
18
+ {`{"AutoUpdate": "never"}`, AutoUpdateNever, nil},
19
+ {`{"AutoUpdate": "patch"}`, AutoUpdatePatch, nil},
20
+ {`{"AutoUpdate": "minor"}`, AutoUpdateMinor, nil},
21
+ {`{"AutoUpdate": "major"}`, AutoUpdateMajor, nil},
22
+ {`{"AutoUpdate": "blarg"}`, AutoUpdateMinor, ErrUnknownAutoUpdateSetting},
23
+ }
24
+
25
+ for i, tc := range tests {
26
+ err := Decode(strings.NewReader(tc.input), &tval)
27
+ if err != tc.err {
28
+ t.Fatalf("%d failed - got err %q wanted %v", i, err, tc.err)
29
+ }
30
+
31
+ if tval.AutoUpdate != tc.val {
32
+ t.Fatalf("%d failed - got val %q where we wanted %q", i, tval.AutoUpdate, tc.val)
33
+ }
34
+ }
35
+
36
+}