-
Notifications
You must be signed in to change notification settings - Fork 46
/
config.go
170 lines (142 loc) · 4.31 KB
/
config.go
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
package main
import (
"fmt"
"path"
"reflect"
"strings"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/go-ini/ini"
"github.com/urfave/cli/v2"
)
// This is the global configuration, it's loaded from .s3cfg (by default) then with added
// overrides from the command line
//
// Command lines are by default the snake case version of the the struct names with "-" instead of "_"
//
type Config struct {
AccessKey string `ini:"access_key"`
SecretKey string `ini:"secret_key"`
StorageClass string `ini:"storage-class"`
Concurrency int `ini:"concurrency"`
PartSize int64 `ini:"part-size"`
CheckMD5 bool `ini:"check_md5" cli:"check-md5"`
DryRun bool `ini:"dry_run"`
Verbose bool `ini:"verbose"`
Recursive bool `ini:"recursive"`
Force bool `ini:"force"`
SkipExisting bool `ini:"skip_existing"`
HostBase string `ini:"host_base"`
HostBucket string `ini:"host_bucket"`
}
var (
validStorageClasses = map[string]bool{
"": true,
s3.ObjectStorageClassStandard: true,
s3.ObjectStorageClassReducedRedundancy: true,
s3.ObjectStorageClassGlacier: true,
s3.ObjectStorageClassStandardIa: true,
s3.ObjectStorageClassOnezoneIa: true,
s3.ObjectStorageClassIntelligentTiering: true,
s3.ObjectStorageClassDeepArchive: true,
}
)
// Read the configuration file if found, otherwise return default configuration
// Precedence order (most important to least):
// - Command Line options
// - Environment Variables
// - Config File
// - Default Values
func NewConfig(c *cli.Context) (*Config, error) {
var cfgPath string
// if obj := c.GlobalStringSlice("config"); len(obj) > 1 {
// cfgPath = obj[1]
// } else
if obj := c.StringSlice("config"); len(obj) > 1 {
cfgPath = obj[1]
} else if value := GetEnv("HOME"); value != nil {
cfgPath = path.Join(*value, ".s3cfg")
} else {
cfgPath = ".s3cfg"
}
config, err := loadConfigFile(cfgPath)
if err != nil {
return nil, err
}
parseOptions(config, c)
if /*c.GlobalIsSet("no-check-md5") || */ c.IsSet("no-check-md5") {
config.CheckMD5 = false
}
// Some additional validation
if _, found := validStorageClasses[config.StorageClass]; !found {
return nil, fmt.Errorf("Invalid storage class provided: %s", config.StorageClass)
}
return config, nil
}
// Load the config file if possible, but if there is an error return the default configuration file
func loadConfigFile(path string) (*Config, error) {
config := &Config{CheckMD5: false}
cfg, err := ini.Load(path)
if err != nil {
return config, nil
}
// s3cmd ini files are not Python configfiles --
//
// The INI file parser will use the %(bucket) and do a
// lookup on the key, if not found it will panic
// this puts a key in that causes the recursive lookup to limit
// out but allows things to work.
if _, err := cfg.Section("").NewKey("bucket", "%(bucket)s"); err != nil {
// this shouldn't fail -- if it does the MapTo will fail
return nil, fmt.Errorf("Unable to create bucket key")
}
if err := cfg.Section("default").MapTo(config); err != nil {
return nil, err
}
return config, nil
}
// Pull the options out of the cli.Context and save them into the configuration object
func parseOptions(config *Config, c *cli.Context) {
rt := reflect.TypeOf(*config)
rv := reflect.ValueOf(config)
for i := 0; i < rt.NumField(); i++ {
field := rt.Field(i)
name := ""
if field.Tag.Get("cli") != "" {
name = field.Tag.Get("cli")
} else {
name = strings.Replace(CamelToSnake(field.Name), "_", "-", -1)
}
// gset := c.GlobalIsSet(name)
gset := false
lset := c.IsSet(name)
// fmt.Println(name, gset, lset, c.String(name))
// FIXME: This isn't great, "IsSet()" isn't triggered for environment variables
if !gset && !lset && c.String(name) == "" {
continue
}
f := rv.Elem().FieldByName(field.Name)
if !f.IsValid() || !f.CanSet() {
continue
}
switch f.Kind() {
case reflect.Bool:
if lset {
f.SetBool(c.Bool(name))
} else {
// f.SetBool(c.GlobalBool(name))
}
case reflect.String:
if lset {
f.SetString(c.String(name))
} else {
// f.SetString(c.GlobalString(name))
}
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
if lset {
f.SetInt(c.Int64(name))
} else {
// f.SetInt(c.GlobalInt64(name))
}
}
}
}