@@ -6,11 +6,14 @@ package nosql
6
6
7
7
import (
8
8
"crypto/tls"
9
+ "net/url"
9
10
"path"
10
11
"runtime/pprof"
11
12
"strconv"
12
13
"strings"
13
14
15
+ "code.gitea.io/gitea/modules/log"
16
+
14
17
"github.com/go-redis/redis/v8"
15
18
)
16
19
@@ -41,7 +44,31 @@ func (m *Manager) CloseRedisClient(connection string) error {
41
44
}
42
45
43
46
// GetRedisClient gets a redis client for a particular connection
44
- func (m * Manager ) GetRedisClient (connection string ) redis.UniversalClient {
47
+ func (m * Manager ) GetRedisClient (connection string ) (client redis.UniversalClient ) {
48
+ // Because we want associate any goroutines created by this call to the main nosqldb context we need to
49
+ // wrap this in a goroutine labelled with the nosqldb context
50
+ done := make (chan struct {})
51
+ var recovered interface {}
52
+ go func () {
53
+ defer func () {
54
+ recovered = recover ()
55
+ if recovered != nil {
56
+ log .Critical ("PANIC during GetRedisClient: %v\n Stacktrace: %s" , recovered , log .Stack (2 ))
57
+ }
58
+ close (done )
59
+ }()
60
+ pprof .SetGoroutineLabels (m .ctx )
61
+
62
+ client = m .getRedisClient (connection )
63
+ }()
64
+ <- done
65
+ if recovered != nil {
66
+ panic (recovered )
67
+ }
68
+ return
69
+ }
70
+
71
+ func (m * Manager ) getRedisClient (connection string ) redis.UniversalClient {
45
72
m .mutex .Lock ()
46
73
defer m .mutex .Unlock ()
47
74
client , ok := m .RedisConnections [connection ]
@@ -60,8 +87,59 @@ func (m *Manager) GetRedisClient(connection string) redis.UniversalClient {
60
87
name : []string {connection , uri .String ()},
61
88
}
62
89
90
+ opts := getRedisOptions (uri )
91
+ tlsConfig := getRedisTLSOptions (uri )
92
+
93
+ clientName := uri .Query ().Get ("clientname" )
94
+
95
+ if len (clientName ) > 0 {
96
+ client .name = append (client .name , clientName )
97
+ }
98
+
99
+ switch uri .Scheme {
100
+ case "redis+sentinels" :
101
+ fallthrough
102
+ case "rediss+sentinel" :
103
+ opts .TLSConfig = tlsConfig
104
+ fallthrough
105
+ case "redis+sentinel" :
106
+ client .UniversalClient = redis .NewFailoverClient (opts .Failover ())
107
+ case "redis+clusters" :
108
+ fallthrough
109
+ case "rediss+cluster" :
110
+ opts .TLSConfig = tlsConfig
111
+ fallthrough
112
+ case "redis+cluster" :
113
+ client .UniversalClient = redis .NewClusterClient (opts .Cluster ())
114
+ case "redis+socket" :
115
+ simpleOpts := opts .Simple ()
116
+ simpleOpts .Network = "unix"
117
+ simpleOpts .Addr = path .Join (uri .Host , uri .Path )
118
+ client .UniversalClient = redis .NewClient (simpleOpts )
119
+ case "rediss" :
120
+ opts .TLSConfig = tlsConfig
121
+ fallthrough
122
+ case "redis" :
123
+ client .UniversalClient = redis .NewClient (opts .Simple ())
124
+ default :
125
+ return nil
126
+ }
127
+
128
+ for _ , name := range client .name {
129
+ m .RedisConnections [name ] = client
130
+ }
131
+
132
+ client .count ++
133
+
134
+ return client
135
+ }
136
+
137
+ // getRedisOptions pulls various configuration options based on the RedisUri format and converts them to go-redis's
138
+ // UniversalOptions fields. This function explicitly excludes fields related to TLS configuration, which is
139
+ // conditionally attached to this options struct before being converted to the specific type for the redis scheme being
140
+ // used, and only in scenarios where TLS is applicable (e.g. rediss://, redis+clusters://).
141
+ func getRedisOptions (uri * url.URL ) * redis.UniversalOptions {
63
142
opts := & redis.UniversalOptions {}
64
- tlsConfig := & tls.Config {}
65
143
66
144
// Handle username/password
67
145
if password , ok := uri .User .Password (); ok {
@@ -132,86 +210,54 @@ func (m *Manager) GetRedisClient(connection string) redis.UniversalClient {
132
210
fallthrough
133
211
case "mastername" :
134
212
opts .MasterName = v [0 ]
135
- case "skipverify" :
136
- fallthrough
137
- case "insecureskipverify" :
138
- insecureSkipVerify , _ := strconv .ParseBool (v [0 ])
139
- tlsConfig .InsecureSkipVerify = insecureSkipVerify
140
- case "clientname" :
141
- client .name = append (client .name , v [0 ])
213
+ case "sentinelusername" :
214
+ opts .SentinelUsername = v [0 ]
215
+ case "sentinelpassword" :
216
+ opts .SentinelPassword = v [0 ]
142
217
}
143
218
}
144
219
145
- done := make (chan struct {})
146
- go func () {
147
- defer close (done )
148
- pprof .SetGoroutineLabels (m .ctx )
149
-
150
- switch uri .Scheme {
151
- case "redis+sentinels" :
152
- fallthrough
153
- case "rediss+sentinel" :
154
- opts .TLSConfig = tlsConfig
155
- fallthrough
156
- case "redis+sentinel" :
157
- if uri .Host != "" {
158
- opts .Addrs = append (opts .Addrs , strings .Split (uri .Host , "," )... )
159
- }
160
- if uri .Path != "" {
161
- if db , err := strconv .Atoi (uri .Path [1 :]); err == nil {
162
- opts .DB = db
163
- }
164
- }
220
+ if uri .Host != "" {
221
+ opts .Addrs = append (opts .Addrs , strings .Split (uri .Host , "," )... )
222
+ }
165
223
166
- client .UniversalClient = redis .NewFailoverClient (opts .Failover ()).WithContext (m .ctx )
167
- case "redis+clusters" :
168
- fallthrough
169
- case "rediss+cluster" :
170
- opts .TLSConfig = tlsConfig
171
- fallthrough
172
- case "redis+cluster" :
173
- if uri .Host != "" {
174
- opts .Addrs = append (opts .Addrs , strings .Split (uri .Host , "," )... )
175
- }
176
- if uri .Path != "" {
177
- if db , err := strconv .Atoi (uri .Path [1 :]); err == nil {
178
- opts .DB = db
179
- }
180
- }
181
- client .UniversalClient = redis .NewClusterClient (opts .Cluster ()).WithContext (m .ctx )
182
- case "redis+socket" :
183
- simpleOpts := opts .Simple ()
184
- simpleOpts .Network = "unix"
185
- simpleOpts .Addr = path .Join (uri .Host , uri .Path )
186
- client .UniversalClient = redis .NewClient (simpleOpts ).WithContext (m .ctx )
187
- case "rediss" :
188
- opts .TLSConfig = tlsConfig
189
- fallthrough
190
- case "redis" :
191
- if uri .Host != "" {
192
- opts .Addrs = append (opts .Addrs , strings .Split (uri .Host , "," )... )
193
- }
194
- if uri .Path != "" {
195
- if db , err := strconv .Atoi (uri .Path [1 :]); err == nil {
196
- opts .DB = db
197
- }
198
- }
199
- client .UniversalClient = redis .NewClient (opts .Simple ()).WithContext (m .ctx )
200
- default :
201
- return
224
+ // A redis connection string uses the path section of the URI in two different ways. In a TCP-based connection, the
225
+ // path will be a database index to automatically have the client SELECT. In a Unix socket connection, it will be the
226
+ // file path. We only want to try to coerce this to the database index when we're not expecting a file path so that
227
+ // the error log stays clean.
228
+ if uri .Path != "" && uri .Scheme != "redis+socket" {
229
+ if db , err := strconv .Atoi (uri .Path [1 :]); err == nil {
230
+ opts .DB = db
231
+ } else {
232
+ log .Error ("Provided database identifier '%s' is not a valid integer. Gitea will ignore this option." , uri .Path )
202
233
}
203
- }()
204
- <- done
205
-
206
- if client .UniversalClient == nil {
207
- return nil
208
234
}
209
235
210
- for _ , name := range client .name {
211
- m .RedisConnections [name ] = client
236
+ return opts
237
+ }
238
+
239
+ // getRedisTlsOptions parses RedisUri TLS configuration parameters and converts them to the go TLS configuration
240
+ // equivalent fields.
241
+ func getRedisTLSOptions (uri * url.URL ) * tls.Config {
242
+ tlsConfig := & tls.Config {}
243
+
244
+ skipverify := uri .Query ().Get ("skipverify" )
245
+
246
+ if len (skipverify ) > 0 {
247
+ skipverify , err := strconv .ParseBool (skipverify )
248
+ if err != nil {
249
+ tlsConfig .InsecureSkipVerify = skipverify
250
+ }
212
251
}
213
252
214
- client . count ++
253
+ insecureskipverify := uri . Query (). Get ( "insecureskipverify" )
215
254
216
- return client
255
+ if len (insecureskipverify ) > 0 {
256
+ insecureskipverify , err := strconv .ParseBool (insecureskipverify )
257
+ if err != nil {
258
+ tlsConfig .InsecureSkipVerify = insecureskipverify
259
+ }
260
+ }
261
+
262
+ return tlsConfig
217
263
}
0 commit comments