@@ -122,55 +122,75 @@ func CheckSource(ctx context.Context, conf *models.ConnectionTest, imageContent
122
122
123
123
dbSource .TuningParams = tuningParameters
124
124
125
+ dbCheck ,err := checkDatabases (ctx ,conn ,conf ,imageContent )
126
+ if err != nil {
127
+ dbSource .Status = models .TCStatusError
128
+ dbSource .Result = models .TCResultQueryError
129
+ dbSource .Message = err .Error ()
130
+
131
+ return dbSource ,err
132
+ }
133
+
134
+ dbSource .TestConnection = dbCheck
135
+
136
+ return dbSource ,nil
137
+ }
138
+
139
+ func checkDatabases (ctx context.Context ,conn * pgx.Conn ,conf * models.ConnectionTest ,
140
+ imageContent * ImageContent ) (* models.TestConnection ,error ) {
141
+ var tcResponse = & models.TestConnection {
142
+ Status :models .TCStatusOK ,
143
+ Result :models .TCResultOK ,
144
+ Message :models .TCMessageOK ,
145
+ }
146
+
125
147
dbList := conf .DBList
126
148
127
149
if len (dbList )== 0 {
128
150
dbSourceList ,err := getDBList (ctx ,conn ,conf .Username )
129
151
if err != nil {
130
- dbSource .Status = models .TCStatusError
131
- dbSource .Result = models .TCResultQueryError
132
- dbSource .Message = err .Error ()
133
-
134
- return dbSource ,err
152
+ return nil ,err
135
153
}
136
154
137
155
dbList = dbSourceList
138
156
}
139
157
158
+ dbReport := make (map [string ]* checkContentResp ,0 )
159
+
140
160
if len (dbList )> maxNumberVerifiedDBs {
141
161
dbList = dbList [:maxNumberVerifiedDBs ]
142
- tcResponse = & models. TestConnection {
143
- Status :models .TCStatusNotice ,
144
- Result :models .TCResultUnverifiedDB ,
145
- Message :"Too many databases were requested to be checked . Onlythe following databases have been verified : " +
146
- strings .Join (dbList ,", " ),
162
+ dbReport [ "" ] = & checkContentResp {
163
+ status :models .TCStatusNotice ,
164
+ result :models .TCResultUnverifiedDB ,
165
+ message :"Too many databases. Onlychecked these databases: " +
166
+ strings .Join (dbList ,", " )+ ". " ,
147
167
}
148
- dbSource .TestConnection = tcResponse
149
168
}
150
169
151
170
for _ ,dbName := range dbList {
152
171
dbConn ,listTC := checkConnection (ctx ,conf ,dbName )
153
172
if listTC != nil {
154
- dbSource .TestConnection = listTC
155
- return dbSource ,nil
173
+ dbReport [dbName ]= & checkContentResp {
174
+ status :listTC .Status ,
175
+ result :listTC .Result ,
176
+ message :listTC .Message ,
177
+ }
178
+
179
+ continue
156
180
}
157
181
158
- listTC ,err := checkContent (ctx ,dbConn ,dbName ,imageContent )
159
- if err != nil {
160
- dbSource .Status = models .TCStatusError
161
- dbSource .Result = models .TCResultQueryError
162
- dbSource .Message = err .Error ()
182
+ contentChecks := checkDBContent (ctx ,dbConn ,imageContent )
163
183
164
- return dbSource ,err
184
+ if contentChecks != nil {
185
+ dbReport [dbName ]= contentChecks
165
186
}
187
+ }
166
188
167
- if listTC != nil {
168
- dbSource .TestConnection = listTC
169
- return dbSource ,nil
170
- }
189
+ if len (dbReport )> 0 {
190
+ tcResponse = aggregate (tcResponse ,dbReport )
171
191
}
172
192
173
- return dbSource ,nil
193
+ return tcResponse ,nil
174
194
}
175
195
176
196
func getDBList (ctx context.Context ,conn * pgx.Conn ,dbUsername string ) ([]string ,error ) {
@@ -193,6 +213,91 @@ func getDBList(ctx context.Context, conn *pgx.Conn, dbUsername string) ([]string
193
213
return dbList ,nil
194
214
}
195
215
216
+ type aggregateState struct {
217
+ general string
218
+ errors map [string ]string
219
+ missingExt map [string ][]extension
220
+ unsupportedExt map [string ][]extension
221
+ missingLocales map [string ][]locale
222
+ unexploredDBs []string
223
+ }
224
+
225
+ func newAggregateState ()aggregateState {
226
+ return aggregateState {
227
+ general :"" ,
228
+ errors :make (map [string ]string ,0 ),
229
+ missingExt :make (map [string ][]extension ,0 ),
230
+ unsupportedExt :make (map [string ][]extension ,0 ),
231
+ missingLocales :make (map [string ][]locale ,0 ),
232
+ unexploredDBs :make ([]string ,0 ),
233
+ }
234
+ }
235
+
236
+ func aggregate (tcResponse * models.TestConnection ,collection map [string ]* checkContentResp )* models.TestConnection {
237
+ agg := newAggregateState ()
238
+ sb := strings.Builder {}
239
+
240
+ for dbName ,contentResponse := range collection {
241
+ if contentResponse .status > tcResponse .Status {
242
+ tcResponse .Status = contentResponse .status
243
+ tcResponse .Result = contentResponse .result
244
+ }
245
+
246
+ switch contentResponse .result {
247
+ case models .TCResultUnverifiedDB :
248
+ agg .general += contentResponse .message
249
+
250
+ case models .TCResultQueryError ,models .TCResultConnectionError :
251
+ agg .errors [dbName ]= contentResponse .message
252
+
253
+ case models .TCResultMissingExtension :
254
+ if len (contentResponse .missingExt )> 0 {
255
+ agg .missingExt [dbName ]= append (agg .missingExt [dbName ],contentResponse .missingExt ... )
256
+ }
257
+
258
+ if len (contentResponse .unsupportedExt )> 0 {
259
+ agg .unsupportedExt [dbName ]= append (agg .unsupportedExt [dbName ],contentResponse .unsupportedExt ... )
260
+ }
261
+
262
+ case models .TCResultMissingLocale :
263
+ agg .missingLocales [dbName ]= append (agg .missingLocales [dbName ],contentResponse .missingLocales ... )
264
+
265
+ case models .TCResultUnexploredImage :
266
+ agg .unexploredDBs = append (agg .unexploredDBs ,dbName )
267
+
268
+ case models .TCResultOK :
269
+ default :
270
+ }
271
+ }
272
+
273
+ sb .WriteString (agg .general )
274
+ sb .WriteString (buildErrorMessage (agg .errors ))
275
+ sb .WriteString (buildExtensionsWarningMessage (agg .missingExt ,agg .unsupportedExt ))
276
+ sb .WriteString (buildLocalesWarningMessage (agg .missingLocales ))
277
+ sb .WriteString (unexploredDBsNoticeMessage (agg .unexploredDBs ))
278
+
279
+ tcResponse .Message = sb .String ()
280
+
281
+ return tcResponse
282
+ }
283
+
284
+ func buildErrorMessage (errors map [string ]string )string {
285
+ if len (errors )== 0 {
286
+ return ""
287
+ }
288
+
289
+ sb := strings.Builder {}
290
+ sb .WriteString ("Issues detected in databases:\n " )
291
+
292
+ for dbName ,message := range errors {
293
+ sb .WriteString (fmt .Sprintf (" %q - %s;\n " ,dbName ,message ))
294
+ }
295
+
296
+ sb .WriteString ("\n " )
297
+
298
+ return sb .String ()
299
+ }
300
+
196
301
func checkConnection (ctx context.Context ,conf * models.ConnectionTest ,dbName string ) (* pgx.Conn ,* models.TestConnection ) {
197
302
connStr := ConnectionString (conf .Host ,conf .Port ,conf .Username ,dbName ,conf .Password )
198
303
@@ -220,41 +325,59 @@ func checkConnection(ctx context.Context, conf *models.ConnectionTest, dbName st
220
325
return conn ,nil
221
326
}
222
327
223
- func checkContent (ctx context.Context ,conn * pgx.Conn ,dbName string ,imageContent * ImageContent ) (* models.TestConnection ,error ) {
328
+ type checkContentResp struct {
329
+ status models.StatusType
330
+ result string
331
+ message string
332
+ missingExt []extension
333
+ unsupportedExt []extension
334
+ missingLocales []locale
335
+ }
336
+
337
+ func checkDBContent (ctx context.Context ,conn * pgx.Conn ,imageContent * ImageContent )* checkContentResp {
224
338
if ! imageContent .IsReady () {
225
- return & models. TestConnection {
226
- Status :models .TCStatusNotice ,
227
- Result :models .TCResultUnexploredImage ,
228
- Message :"The connection tothe database was successful . " +
229
- "Details about the extensions and localesof the Docker image have not yetbeen collected. Please try again later" ,
230
- }, nil
339
+ return & checkContentResp {
340
+ status :models .TCStatusNotice ,
341
+ result :models .TCResultUnexploredImage ,
342
+ message :"Connected to database. " +
343
+ "Docker image extensions and locales not yetanalyzed. Retry later. " ,
344
+ }
231
345
}
232
346
233
347
if missing ,unsupported ,err := checkExtensions (ctx ,conn ,imageContent .Extensions ());err != nil {
234
348
if err != errExtensionWarning {
235
- return nil ,fmt .Errorf ("failed to check database extensions: %w" ,err )
349
+ return & checkContentResp {
350
+ status :models .TCStatusError ,
351
+ result :models .TCResultQueryError ,
352
+ message :fmt .Sprintf ("failed to check database extensions: %s" ,err ),
353
+ }
236
354
}
237
355
238
- return & models.TestConnection {
239
- Status :models .TCStatusWarning ,
240
- Result :models .TCResultMissingExtension ,
241
- Message :buildExtensionsWarningMessage (dbName ,missing ,unsupported ),
242
- },nil
356
+ return & checkContentResp {
357
+ status :models .TCStatusWarning ,
358
+ result :models .TCResultMissingExtension ,
359
+ missingExt :missing ,
360
+ unsupportedExt :unsupported ,
361
+ }
243
362
}
244
363
245
364
if missing ,err := checkLocales (ctx ,conn ,imageContent .Locales (),imageContent .Databases ());err != nil {
246
365
if err != errLocaleWarning {
247
- return nil ,fmt .Errorf ("failed to check database locales: %w" ,err )
366
+ return & checkContentResp {
367
+ status :models .TCStatusError ,
368
+ result :models .TCResultQueryError ,
369
+ message :fmt .Sprintf ("failed to check database locales: %s" ,err ),
370
+ }
248
371
}
249
372
250
- return & models. TestConnection {
251
- Status : models .TCStatusWarning ,
252
- Result : models .TCResultMissingLocale ,
253
- Message : buildLocalesWarningMessage ( dbName , missing ) ,
254
- }, nil
373
+ return & checkContentResp {
374
+ status : models .TCStatusWarning ,
375
+ result : models .TCResultMissingLocale ,
376
+ missingLocales : missing ,
377
+ }
255
378
}
256
379
257
- return nil , nil
380
+ return nil
258
381
}
259
382
260
383
func checkExtensions (ctx context.Context ,conn * pgx.Conn ,imageExtensions map [string ]string ) ([]extension , []extension ,error ) {
@@ -311,38 +434,64 @@ func toCanonicalSemver(v string) string {
311
434
return v
312
435
}
313
436
314
- func buildExtensionsWarningMessage (dbName string , missingExtensions ,unsupportedVersions []extension )string {
437
+ func buildExtensionsWarningMessage (missingExtensions ,unsupportedVersions map [ string ] []extension )string {
315
438
sb := & strings.Builder {}
316
439
317
440
if len (missingExtensions )> 0 {
318
- sb .WriteString ("The image specified insection \" databaseContainer\" lacks the following " +
319
- "extensionsused inthe sourcedatabase ( \" " + dbName + " \" ): " )
441
+ sb .WriteString ("Image configured in\" databaseContainer\" missing " +
442
+ "extensionsinstalled in sourcedatabases: " )
320
443
321
444
formatExtensionList (sb ,missingExtensions )
322
-
323
- sb .WriteString (".\n " )
324
445
}
325
446
326
447
if len (unsupportedVersions )> 0 {
327
- sb .WriteString ("The source database ( \" " + dbName + " \" ) uses extensionsthat are present "+
328
- "in imagespecified insection \" databaseContainer\" but their versions are not supported by the image :" )
448
+ sb .WriteString ("Source databases have extensionswith different versions " +
449
+ "than imageconfigured in\" databaseContainer\" :" )
329
450
330
451
formatExtensionList (sb ,unsupportedVersions )
331
452
}
332
453
333
454
return sb .String ()
334
455
}
335
456
336
- func formatExtensionList (sb * strings.Builder ,extensions []extension ) {
337
- length := len (extensions )
457
+ func formatExtensionList (sb * strings.Builder ,extensionMap map [string ][]extension ) {
458
+ var j int
459
+
460
+ lengthDBs := len (extensionMap )
461
+
462
+ for dbName ,extensions := range extensionMap {
463
+ lengthExt := len (extensions )
464
+
465
+ sb .WriteString (" " + dbName + " (" )
466
+
467
+ for i ,missing := range extensions {
468
+ sb .WriteString (missing .name + " " + missing .defaultVersion )
469
+
470
+ if i != lengthExt - 1 {
471
+ sb .WriteString (", " )
472
+ }
473
+ }
338
474
339
- for i ,missing := range extensions {
340
- sb .WriteString (" " + missing .name + " " + missing .defaultVersion )
475
+ sb .WriteString (")" )
341
476
342
- if i != length - 1 {
343
- sb .WriteRune (', ' )
477
+ if j != lengthDBs - 1 {
478
+ sb .WriteRune ('; ' )
344
479
}
480
+
481
+ j ++
345
482
}
483
+
484
+ sb .WriteString (".\n " )
485
+ }
486
+
487
+ func unexploredDBsNoticeMessage (dbs []string )string {
488
+ if len (dbs )== 0 {
489
+ return ""
490
+ }
491
+
492
+ return fmt .Sprintf ("Connected to databases: %s. " +
493
+ "Docker image extensions and locales not analyzed. Retry later.\n " ,
494
+ strings .Join (dbs ,"," ))
346
495
}
347
496
348
497
func checkLocales (ctx context.Context ,conn * pgx.Conn ,imageLocales ,databases map [string ]struct {}) ([]locale ,error ) {
@@ -386,20 +535,38 @@ func checkLocales(ctx context.Context, conn *pgx.Conn, imageLocales, databases m
386
535
return nil ,nil
387
536
}
388
537
389
- func buildLocalesWarningMessage (dbName string ,missingLocales []locale )string {
538
+ func buildLocalesWarningMessage (localeMap map [string ][]locale )string {
539
+ var j int
540
+
390
541
sb := & strings.Builder {}
391
542
392
- if length := len (missingLocales );length > 0 {
393
- sb .WriteString ("The image specified in section\" databaseContainer\" lacks the following " +
394
- "locales used in the source database (\" " + dbName + "\" ):" )
543
+ if lengthDBs := len (localeMap );lengthDBs > 0 {
544
+ sb .WriteString ("Image configured in\" databaseContainer\" missing " +
545
+ "locales from source databases: " )
546
+
547
+ for dbName ,missingLocales := range localeMap {
548
+ lengthLoc := len (missingLocales )
549
+
550
+ sb .WriteString (" " + dbName + " (" )
395
551
396
- for i ,missing := range missingLocales {
397
- sb .WriteString (fmt .Sprintf (" '%s' (collate: %s, ctype: %s)" ,missing .name ,missing .collate ,missing .ctype ))
552
+ for i ,missing := range missingLocales {
553
+ sb .WriteString (fmt .Sprintf (" '%s' (collate: %s, ctype: %s)" ,missing .name ,missing .collate ,missing .ctype ))
398
554
399
- if i != length - 1 {
400
- sb .WriteRune (',' )
555
+ if i != lengthLoc - 1 {
556
+ sb .WriteRune (',' )
557
+ }
401
558
}
559
+
560
+ sb .WriteString (")" )
561
+
562
+ if j != lengthDBs - 1 {
563
+ sb .WriteRune (';' )
564
+ }
565
+
566
+ j ++
402
567
}
568
+
569
+ sb .WriteString (".\n " )
403
570
}
404
571
405
572
return sb .String ()