@cryptotaxi247 / kubo / commits / 99929de05

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