-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Expand file tree
/
Copy pathconfig.go
More file actions
258 lines (226 loc) · 6.91 KB
/
config.go
File metadata and controls
258 lines (226 loc) · 6.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// Package config implements the ipfs config file datastructures and utilities.
package config
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"reflect"
"strings"
"github.com/ipfs/kubo/misc/fsutil"
)
// Config is used to load ipfs config files.
type Config struct {
Identity Identity // local node's peer identity
Datastore Datastore // local node's storage
Addresses Addresses // local node's addresses
Mounts Mounts // local node's mount points
Discovery Discovery // local node's discovery mechanisms
Routing Routing // local node's routing settings
Ipns Ipns // Ipns settings
Bootstrap []string // local nodes's bootstrap peer addresses
Gateway Gateway // local node's gateway server options
API API // local node's API settings
Swarm SwarmConfig
AutoNAT AutoNATConfig
AutoTLS AutoTLS
Pubsub PubsubConfig
Peering Peering
DNS DNS
Migration Migration
Provider Provider
Reprovider Reprovider
HTTPRetrieval HTTPRetrieval
Experimental Experiments
Plugins Plugins
Pinning Pinning
Import Import
Version Version
Internal Internal // experimental/unstable options
Bitswap Bitswap `json:",omitempty"`
}
const (
// DefaultPathName is the default config dir name.
DefaultPathName = ".ipfs"
// DefaultPathRoot is the path to the default config dir location.
DefaultPathRoot = "~/" + DefaultPathName
// DefaultConfigFile is the filename of the configuration file.
DefaultConfigFile = "config"
// EnvDir is the environment variable used to change the path root.
EnvDir = "IPFS_PATH"
)
// PathRoot returns the default configuration root directory.
func PathRoot() (string, error) {
dir := os.Getenv(EnvDir)
var err error
if len(dir) == 0 {
dir, err = fsutil.ExpandHome(DefaultPathRoot)
}
return dir, err
}
// Path returns the path `extension` relative to the configuration root. If an
// empty string is provided for `configroot`, the default root is used.
func Path(configroot, extension string) (string, error) {
if len(configroot) == 0 {
dir, err := PathRoot()
if err != nil {
return "", err
}
return filepath.Join(dir, extension), nil
}
return filepath.Join(configroot, extension), nil
}
// Filename returns the configuration file path given a configuration root
// directory and a user-provided configuration file path argument with the
// following rules:
// - If the user-provided configuration file path is empty, use the default one.
// - If the configuration root directory is empty, use the default one.
// - If the user-provided configuration file path is only a file name, use the
// configuration root directory, otherwise use only the user-provided path
// and ignore the configuration root.
func Filename(configroot, userConfigFile string) (string, error) {
if userConfigFile == "" {
return Path(configroot, DefaultConfigFile)
}
if filepath.Dir(userConfigFile) == "." {
return Path(configroot, userConfigFile)
}
return userConfigFile, nil
}
// HumanOutput gets a config value ready for printing.
func HumanOutput(value interface{}) ([]byte, error) {
s, ok := value.(string)
if ok {
return []byte(strings.Trim(s, "\n")), nil
}
return Marshal(value)
}
// Marshal configuration with JSON.
func Marshal(value interface{}) ([]byte, error) {
// need to prettyprint, hence MarshalIndent, instead of Encoder
return json.MarshalIndent(value, "", " ")
}
func FromMap(v map[string]interface{}) (*Config, error) {
buf := new(bytes.Buffer)
if err := json.NewEncoder(buf).Encode(v); err != nil {
return nil, err
}
var conf Config
if err := json.NewDecoder(buf).Decode(&conf); err != nil {
return nil, fmt.Errorf("failure to decode config: %w", err)
}
return &conf, nil
}
func ToMap(conf *Config) (map[string]interface{}, error) {
buf := new(bytes.Buffer)
if err := json.NewEncoder(buf).Encode(conf); err != nil {
return nil, err
}
var m map[string]interface{}
if err := json.NewDecoder(buf).Decode(&m); err != nil {
return nil, fmt.Errorf("failure to decode config: %w", err)
}
return m, nil
}
// ReflectToMap converts config to a map, without using encoding/json, since
// zero/empty/'omitempty' fields are excluded by encoding/json during
// marshaling.
func ReflectToMap(conf interface{}) interface{} {
v := reflect.ValueOf(conf)
if !v.IsValid() {
return nil
}
// Handle pointer type
if v.Kind() == reflect.Ptr {
if v.IsNil() {
// Create a zero value of the pointer's element type
elemType := v.Type().Elem()
zero := reflect.Zero(elemType)
return ReflectToMap(zero.Interface())
}
v = v.Elem()
}
switch v.Kind() {
case reflect.Struct:
result := make(map[string]interface{})
t := v.Type()
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
// Only include exported fields
if field.CanInterface() {
result[t.Field(i).Name] = ReflectToMap(field.Interface())
}
}
return result
case reflect.Map:
result := make(map[string]interface{})
iter := v.MapRange()
for iter.Next() {
key := iter.Key()
// Convert map keys to strings for consistency
keyStr := fmt.Sprint(ReflectToMap(key.Interface()))
result[keyStr] = ReflectToMap(iter.Value().Interface())
}
// Add a sample to differentiate between a map and a struct on validation.
sample := reflect.Zero(v.Type().Elem())
if sample.CanInterface() {
result["*"] = ReflectToMap(sample.Interface())
}
return result
case reflect.Slice, reflect.Array:
result := make([]interface{}, v.Len())
for i := 0; i < v.Len(); i++ {
result[i] = ReflectToMap(v.Index(i).Interface())
}
return result
default:
// For basic types (int, string, etc.), just return the value
if v.CanInterface() {
return v.Interface()
}
return nil
}
}
// Clone copies the config. Use when updating.
func (c *Config) Clone() (*Config, error) {
var newConfig Config
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(c); err != nil {
return nil, fmt.Errorf("failure to encode config: %w", err)
}
if err := json.NewDecoder(&buf).Decode(&newConfig); err != nil {
return nil, fmt.Errorf("failure to decode config: %w", err)
}
return &newConfig, nil
}
// CheckKey checks if the provided key is present in the structure.
func CheckKey(key string) error {
conf := Config{}
// Convert an empty config to a map without JSON.
cursor := ReflectToMap(&conf)
// Parse the key and verify it's presence in the map.
var ok bool
var mapCursor map[string]interface{}
parts := strings.Split(key, ".")
for i, part := range parts {
mapCursor, ok = cursor.(map[string]interface{})
if !ok {
if cursor == nil {
return nil
}
path := strings.Join(parts[:i], ".")
return fmt.Errorf("%s key is not a map", path)
}
cursor, ok = mapCursor[part]
if !ok {
// If the config sections is a map, validate against the default entry.
if cursor, ok = mapCursor["*"]; ok {
continue
}
path := strings.Join(parts[:i+1], ".")
return fmt.Errorf("%s not found", path)
}
}
return nil
}