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