@@ -122,55 +122,75 @@ func CheckSource(ctx context.Context, conf *models.ConnectionTest, imageContent
122122
123123 dbSource .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+ 125147 dbList := conf .DBList
126148
127149 if len (dbList ) == 0 {
128150 dbSourceList , err := getDBList (ctx , conn , conf .Username )
129151 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
135153 }
136154
137155 dbList = dbSourceList
138156 }
139157
158+ dbReport := make (map [string ]* checkContentResp , 0 )
159+ 140160 if len (dbList ) > maxNumberVerifiedDBs {
141161 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 . Only the 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. Only checked these databases: " +
166+ strings .Join (dbList , ", " )+ ". " ,
147167 }
148- dbSource .TestConnection = tcResponse
149168 }
150169
151170 for _ , dbName := range dbList {
152171 dbConn , listTC := checkConnection (ctx , conf , dbName )
153172 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
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
193213 return 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 ) {
197302 connStr := 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
220325 return 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 {
224338 if ! imageContent .IsReady () {
225- return & models. TestConnection {
226- Status : models .TCStatusNotice ,
227- Result : models .TCResultUnexploredImage ,
228- Message : "The connection to the database was successful . " +
229- "Details about the extensions and locales of the Docker image have not yet been 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 yet analyzed. Retry later. " ,
344+ }
231345 }
232346
233347 if missing , unsupported , err := checkExtensions (ctx , conn , imageContent .Extensions ()); err != nil {
234348 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+ }
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
245364 if missing , err := checkLocales (ctx , conn , imageContent .Locales (), imageContent .Databases ()); err != nil {
246365 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+ }
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 {
311434 return v
312435}
313436
314- func buildExtensionsWarningMessage (dbName string , missingExtensions , unsupportedVersions []extension ) string {
437+ func buildExtensionsWarningMessage (missingExtensions , unsupportedVersions map [ string ] []extension ) string {
315438 sb := & strings.Builder {}
316439
317440 if len (missingExtensions ) > 0 {
318- sb .WriteString ("The image specified in section \" databaseContainer\" lacks the following " +
319- "extensions used in the source database ( \" " + dbName + " \" ): " )
441+ sb .WriteString ("Image configured in \" databaseContainer\" missing " +
442+ "extensions installed in source databases: " )
320443
321444 formatExtensionList (sb , missingExtensions )
322- 323- sb .WriteString (".\n " )
324445 }
325446
326447 if len (unsupportedVersions ) > 0 {
327- sb .WriteString ("The source database ( \" " + dbName + " \" ) uses extensions that are present " +
328- "in image specified in section \" databaseContainer\" but their versions are not supported by the image :" )
448+ sb .WriteString ("Source databases have extensions with different versions " +
449+ "than image configured in \" databaseContainer\" :" )
329450
330451 formatExtensionList (sb , unsupportedVersions )
331452 }
332453
333454 return 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
386535 return 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+ 390541 sb := & 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
405572 return sb .String ()
0 commit comments