-
Notifications
You must be signed in to change notification settings - Fork 1
/
config.go
244 lines (220 loc) · 6.76 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
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
// Copyright 2014 Daniel Theophanes.
// Use of this source code is governed by a zlib-style
// license that can be found in the LICENSE file.
package rdb
import (
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"net/url"
"strconv"
"strings"
"time"
)
// Config Database connection configuration.
// Drivers may have additional properties held in KV.
// If a driver is file based, the file name should be in the "Instance" field.
type Config struct {
DriverName string
Username string
Password string
Hostname string
Port int
Instance string
Database string // Initial database to connect to.
// Timeout time for connection dial.
// Zero for no timeout.
DialTimeout time.Duration
// Max time for a connection to live.
ConnectionMaxLifetime time.Duration
// Time for an idle connection to be closed.
// Zero if there should be no timeout.
PoolIdleTimeout time.Duration
// Time for a query to reset the connection to complete.
// Zero if there should be no timeout.
ResetConnectionTimeout time.Duration
// How many connection should be created at startup.
// Valid range is (0 < init, init <= max).
PoolInitCapacity int
// Max number of connections to create.
// Valid range is (0 < max).
PoolMaxCapacity int
// Require the driver to establish a secure connection.
Secure bool
// Disable encryption on the connection.
InsecureDisableEncryption bool
// Do not require the secure connection to verify the remote host name.
// Ignored if Secure is false.
InsecureSkipVerify bool
// Root Certificate Authorities for server.
RootCAs *x509.CertPool
// ResetQuery is executed after the connection is reset.
ResetQuery string
KV map[string]interface{}
}
const optPrefix = "opt_"
// ParseConfigURL provides a standard method to parse configuration options from a text.
// The instance field can also hold the filename in case of a file based connection.
//
// driver://[username:password@][url[:port]]/[Instance]?db=mydatabase&opt1=valA&opt2=valB
// sqlite:///C:/folder/file.sqlite3?opt1=valA&opt2=valB
// sqlite:///srv/folder/file.sqlite3?opt1=valA&opt2=valB
// ms://TESTU@localhost/SqlExpress?db=master&dial_timeout=3s
//
// This will attempt to find the driver to load additional parameters.
//
// Additional field options:
// db=<string>: Database
// dial_timeout=<time.Duration>: Dial Timeout
// max_lifetime=<time.Duration>: Max Connection Lifetime
// init_cap=<int>: Pool Init Capacity
// max_cap=<int>: Pool Max Capacity
// idle_timeout=<time.Duration>: Pool Idle Timeout
// reset_timeout=<time.Duration>:Reset Connection Timeout
// require_encryption=<bool>: Require Connection Encryption
// disable_encryption=<bool>: Disable Connection Encryption
// cert=<string>: Load the cert file as root CA, repeatable.
// SQL Server doens't send intermediate certificates.
// May be required even if root CA is known and trusted.
// insecure_skip_verify=<bool>: INSECURE. Skip encryption certificate verification.
// opt_<any>=<any>: include values, unchecked here, into KV. "opt_" prefix is stripped.
func ParseConfigURL(connectionString string) (*Config, error) {
if len(connectionString) == 0 {
return nil, errors.New("empty DSN")
}
u, err := url.Parse(connectionString)
if err != nil {
return nil, err
}
var user, pass string
if u.User != nil {
user = u.User.Username()
pass, _ = u.User.Password()
}
port := 0
host := ""
if len(u.Host) > 0 {
hostPort := strings.Split(u.Host, ":")
host = hostPort[0]
if len(hostPort) > 1 {
parsedPort, err := strconv.ParseUint(hostPort[1], 10, 16)
if err != nil {
return nil, err
}
port = int(parsedPort)
}
}
conf := &Config{
DriverName: u.Scheme,
Username: user,
Password: pass,
Hostname: host,
Port: port,
KV: map[string]interface{}{},
}
val := u.Query()
for key, vv := range u.Query() {
if len(vv) == 0 {
return nil, fmt.Errorf("invalid setting: %v", key)
}
allowMultiple := false
v0 := vv[0]
switch key {
default:
if !strings.HasPrefix(key, optPrefix) {
return nil, fmt.Errorf("unknown setting: %v", key)
}
key := strings.TrimPrefix(key, optPrefix)
conf.KV[key] = v0
case "db":
conf.Database = v0
case "dial_timeout":
conf.DialTimeout, err = time.ParseDuration(v0)
if err != nil {
return nil, fmt.Errorf("DSN property %q: %w", key, err)
}
case "max_lifetime":
conf.ConnectionMaxLifetime, err = time.ParseDuration(v0)
if err != nil {
return nil, fmt.Errorf("DSN property %q: %w", key, err)
}
case "idle_timeout":
conf.PoolIdleTimeout, err = time.ParseDuration(v0)
if err != nil {
return nil, fmt.Errorf("DSN property %q: %w", key, err)
}
case "reset_timeout":
conf.ResetConnectionTimeout, err = time.ParseDuration(v0)
if err != nil {
return nil, fmt.Errorf("DSN property %q: %w", key, err)
}
case "query_timeout":
// Ignore this.
// All query timeouts controlled from context.
continue
case "init_cap":
conf.PoolInitCapacity, err = strconv.Atoi(v0)
if err != nil {
return nil, fmt.Errorf("DSN property %q: %w", key, err)
}
case "max_cap":
conf.PoolMaxCapacity, err = strconv.Atoi(v0)
if err != nil {
return nil, fmt.Errorf("DSN property %q: %w", key, err)
}
case "insecure_skip_verify":
conf.InsecureSkipVerify, err = strconv.ParseBool(v0)
if err != nil {
return nil, fmt.Errorf("DSN property %q: %w", key, err)
}
case "require_encryption":
conf.Secure, err = strconv.ParseBool(v0)
if err != nil {
return nil, fmt.Errorf("DSN property %q: %w", key, err)
}
case "disable_encryption":
conf.InsecureDisableEncryption, err = strconv.ParseBool(v0)
if err != nil {
return nil, fmt.Errorf("DSN property %q: %w", key, err)
}
case "cert":
allowMultiple = true
certs := x509.NewCertPool()
for index, v := range vv {
b, err := ioutil.ReadFile(v)
if err != nil {
return nil, fmt.Errorf("DSN property %q[%d], cert %q: %w", key, index, v, err)
}
ok := certs.AppendCertsFromPEM(b)
if !ok {
return nil, fmt.Errorf("DSN property %q[%d], cert %q: failed to append cert", key, index, v)
}
}
conf.RootCAs = certs
}
if !allowMultiple && len(vv) > 1 {
return nil, fmt.Errorf("DSN property %q must not be repeated", key)
}
}
if len(u.Path) > 0 {
conf.Instance = u.Path[1:]
}
// Now attempt to call specific driver and parse Key-Value options.
dr, err := getDriver(conf.DriverName)
if err != nil {
return conf, err
}
meta := dr.DriverInfo()
for _, op := range meta.Options {
if op.Parse == nil {
continue
}
v, err := op.Parse(val.Get(op.Name))
if err != nil {
return nil, err
}
conf.KV[op.Name] = v
}
return conf, nil
}