@@ -339,6 +339,36 @@ var extensionBinaries = []TestBinary{
339339 },
340340}
341341
342+ // extractJSON finds the first JSON object or array in output, skipping any non-JSON log lines
343+ // that precede it. This is necessary because some extension binaries output warnings or debug
344+ // logging to stdout before the JSON payload.
345+ func extractJSON (output []byte ) ([]byte , error ) {
346+ jsonBegins := - 1
347+ lines := bytes .Split (output , []byte ("\n " ))
348+ for i , line := range lines {
349+ trimmed := bytes .TrimSpace (line )
350+ if len (trimmed ) > 0 && (trimmed [0 ] == '{' || trimmed [0 ] == '[' ) {
351+ jsonBegins = 0
352+ for j := 0 ; j < i ; j ++ {
353+ jsonBegins += len (lines [j ]) + 1 // +1 for the newline character
354+ }
355+ jsonBegins += len (line ) - len (trimmed ) // Add any leading whitespace
356+ break
357+ }
358+ }
359+
360+ if jsonBegins == - 1 {
361+ return nil , fmt .Errorf ("no valid JSON found in output: %s" , string (output ))
362+ }
363+
364+ var raw json.RawMessage
365+ dec := json .NewDecoder (bytes .NewReader (output [jsonBegins :]))
366+ if err := dec .Decode (& raw ); err != nil {
367+ return nil , fmt .Errorf ("no valid JSON found in output: %w" , err )
368+ }
369+ return raw , nil
370+ }
371+
342372// Info returns information about this particular extension.
343373func (b * TestBinary ) Info (ctx context.Context ) (* Extension , error ) {
344374 if b .info != nil {
@@ -356,30 +386,13 @@ func (b *TestBinary) Info(ctx context.Context) (*Extension, error) {
356386 logrus .Errorf ("Command output for %s: %s" , binName , string (infoJson ))
357387 return nil , fmt .Errorf ("failed running '%s info': %w\n Output: %s" , b .binaryPath , err , infoJson )
358388 }
359- // Some binaries may output logging that includes JSON-like data, so we need to find the first line that starts with '{'
360- jsonBegins := - 1
361- lines := bytes .Split (infoJson , []byte ("\n " ))
362- for i , line := range lines {
363- trimmed := bytes .TrimSpace (line )
364- if bytes .HasPrefix (trimmed , []byte ("{" )) {
365- // Calculate the byte offset of this line in the original output
366- jsonBegins = 0
367- for j := 0 ; j < i ; j ++ {
368- jsonBegins += len (lines [j ]) + 1 // +1 for the newline character
369- }
370- jsonBegins += len (line ) - len (trimmed ) // Add any leading whitespace
371- break
372- }
373- }
374-
375- jsonEnds := bytes .LastIndexByte (infoJson , '}' )
376- if jsonBegins == - 1 || jsonEnds == - 1 || jsonBegins > jsonEnds {
389+ jsonData , err := extractJSON (infoJson )
390+ if err != nil {
377391 logrus .Errorf ("No valid JSON found in output from %s info command" , binName )
378392 logrus .Errorf ("Raw output from %s: %s" , binName , string (infoJson ))
379393 return nil , fmt .Errorf ("no valid JSON found in output from '%s info' command" , binName )
380394 }
381395 var info Extension
382- jsonData := infoJson [jsonBegins : jsonEnds + 1 ]
383396 err = json .Unmarshal (jsonData , & info )
384397 if err != nil {
385398 logrus .Errorf ("Failed to unmarshal JSON from %s: %v" , binName , err )
@@ -561,13 +574,27 @@ func (b *TestBinary) ListImages(ctx context.Context) (ImageSet, error) {
561574 command := exec .Command (b .binaryPath , "images" )
562575 output , err := runWithTimeout (ctx , command , 10 * time .Minute )
563576 if err != nil {
564- return nil , fmt .Errorf ("failed running '%s list': %w\n Output: %s" , b .binaryPath , err , output )
577+ return nil , fmt .Errorf ("failed running '%s images': %w\n Output: %s" , b .binaryPath , err , output )
578+ }
579+
580+ jsonData , err := extractJSON (output )
581+ if err != nil {
582+ // Extensions that have no images may output "null" instead of an array
583+ if bytes .Contains (output , []byte ("null" )) {
584+ logrus .Infof ("Extension %q reported null images, treating as empty" , binName )
585+ return ImageSet {}, nil
586+ }
587+ logrus .Errorf ("No valid JSON found in output from %s images command" , binName )
588+ logrus .Errorf ("Raw output from %s: %s" , binName , string (output ))
589+ return nil , fmt .Errorf ("no valid JSON found in output from '%s images' command" , binName )
565590 }
566591
567592 var images []Image
568- err = json .Unmarshal (output , & images )
593+ err = json .Unmarshal (jsonData , & images )
569594 if err != nil {
570- return nil , err
595+ logrus .Errorf ("Failed to unmarshal JSON from %s: %v" , binName , err )
596+ logrus .Errorf ("JSON data from %s: %s" , binName , string (jsonData ))
597+ return nil , errors .Wrapf (err , "couldn't unmarshal extension images from %s: %s" , binName , string (jsonData ))
571598 }
572599
573600 result := make (ImageSet , len (images ))
0 commit comments