@@ -122,55 +122,75 @@ func CheckSource(ctx context.Context, conf *models.ConnectionTest, imageContent
122122
123123dbSource .TuningParams = tuningParameters
124124
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+
125147dbList := conf .DBList
126148
127149if len (dbList )== 0 {
128150dbSourceList ,err := getDBList (ctx ,conn ,conf .Username )
129151if 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
135153}
136154
137155dbList = dbSourceList
138156}
139157
158+ dbReport := make (map [string ]* checkContentResp ,0 )
159+
140160if len (dbList )> maxNumberVerifiedDBs {
141161dbList = 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 ,", " )+ ". " ,
147167}
148- dbSource .TestConnection = tcResponse
149168}
150169
151170for _ ,dbName := range dbList {
152171dbConn ,listTC := checkConnection (ctx ,conf ,dbName )
153172if 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
156180}
157181
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 )
163183
164- return dbSource ,err
184+ if contentChecks != nil {
185+ dbReport [dbName ]= contentChecks
165186}
187+ }
166188
167- if listTC != nil {
168- dbSource .TestConnection = listTC
169- return dbSource ,nil
170- }
189+ if len (dbReport )> 0 {
190+ tcResponse = aggregate (tcResponse ,dbReport )
171191}
172192
173- return dbSource ,nil
193+ return tcResponse ,nil
174194}
175195
176196func 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
193213return dbList ,nil
194214}
195215
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+
196301func checkConnection (ctx context.Context ,conf * models.ConnectionTest ,dbName string ) (* pgx.Conn ,* models.TestConnection ) {
197302connStr := ConnectionString (conf .Host ,conf .Port ,conf .Username ,dbName ,conf .Password )
198303
@@ -220,41 +325,59 @@ func checkConnection(ctx context.Context, conf *models.ConnectionTest, dbName st
220325return conn ,nil
221326}
222327
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 {
224338if ! 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+ }
231345}
232346
233347if missing ,unsupported ,err := checkExtensions (ctx ,conn ,imageContent .Extensions ());err != nil {
234348if 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+ }
236354}
237355
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+ }
243362}
244363
245364if missing ,err := checkLocales (ctx ,conn ,imageContent .Locales (),imageContent .Databases ());err != nil {
246365if 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+ }
248371}
249372
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+ }
255378}
256379
257- return nil , nil
380+ return nil
258381}
259382
260383func checkExtensions (ctx context.Context ,conn * pgx.Conn ,imageExtensions map [string ]string ) ([]extension , []extension ,error ) {
@@ -311,38 +434,64 @@ func toCanonicalSemver(v string) string {
311434return v
312435}
313436
314- func buildExtensionsWarningMessage (dbName string , missingExtensions ,unsupportedVersions []extension )string {
437+ func buildExtensionsWarningMessage (missingExtensions ,unsupportedVersions map [ string ] []extension )string {
315438sb := & strings.Builder {}
316439
317440if 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: " )
320443
321444formatExtensionList (sb ,missingExtensions )
322-
323- sb .WriteString (".\n " )
324445}
325446
326447if 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\" :" )
329450
330451formatExtensionList (sb ,unsupportedVersions )
331452}
332453
333454return sb .String ()
334455}
335456
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+ }
338474
339- for i ,missing := range extensions {
340- sb .WriteString (" " + missing .name + " " + missing .defaultVersion )
475+ sb .WriteString (")" )
341476
342- if i != length - 1 {
343- sb .WriteRune (', ' )
477+ if j != lengthDBs - 1 {
478+ sb .WriteRune ('; ' )
344479}
480+
481+ j ++
345482}
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 ,"," ))
346495}
347496
348497func 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
386535return nil ,nil
387536}
388537
389- func buildLocalesWarningMessage (dbName string ,missingLocales []locale )string {
538+ func buildLocalesWarningMessage (localeMap map [string ][]locale )string {
539+ var j int
540+
390541sb := & strings.Builder {}
391542
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 + " (" )
395551
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 ))
398554
399- if i != length - 1 {
400- sb .WriteRune (',' )
555+ if i != lengthLoc - 1 {
556+ sb .WriteRune (',' )
557+ }
401558}
559+
560+ sb .WriteString (")" )
561+
562+ if j != lengthDBs - 1 {
563+ sb .WriteRune (';' )
564+ }
565+
566+ j ++
402567}
568+
569+ sb .WriteString (".\n " )
403570}
404571
405572return sb .String ()