@@ -6,6 +6,7 @@ package action
66import (
77 "context"
88 "fmt"
9+ "io"
910 "log/slog"
1011 "maps"
1112 "os"
@@ -20,6 +21,7 @@ import (
2021 "github.com/chainguard-dev/malcontent/pkg/archive"
2122 "github.com/chainguard-dev/malcontent/pkg/malcontent"
2223 "github.com/chainguard-dev/malcontent/pkg/programkind"
24+ "github.com/chainguard-dev/malcontent/pkg/report"
2325 orderedmap "github.com/wk8/go-ordered-map/v2"
2426 "golang.org/x/sync/errgroup"
2527)
@@ -234,8 +236,9 @@ func Diff(ctx context.Context, c malcontent.Config, _ *clog.Logger) (*malcontent
234236 // If diffing images, use their temporary directories as scan paths
235237 // Flip c.OCI to false when finished to block other image code paths
236238 var (
237- err error
238- isImage bool
239+ err error
240+ isImage bool
241+ isReport bool
239242 )
240243
241244 if c .OCI {
@@ -250,44 +253,86 @@ func Diff(ctx context.Context, c malcontent.Config, _ *clog.Logger) (*malcontent
250253 isImage , c .OCI = true , false
251254 }
252255
253- var g errgroup.Group
254-
255256 srcCh , destCh := make (chan ScanResult , 1 ), make (chan ScanResult , 1 )
256-
257257 srcIsArchive , destIsArchive := programkind .IsSupportedArchive (ctx , srcPath ), programkind .IsSupportedArchive (ctx , destPath )
258+ srcResult , destResult := ScanResult {}, ScanResult {}
259+
260+ // If diffing existing reports, we just need to unmarshal them into a ScanResult and run the diff
261+ // Only JSON or YAML reports are supported, however
262+ switch c .Report {
263+ case true :
264+ isReport = true
265+ srcFile , err := os .Open (srcPath )
266+ if err != nil {
267+ return nil , err
268+ }
269+ defer srcFile .Close ()
270+ src , err := io .ReadAll (srcFile )
271+ if err != nil {
272+ return nil , err
273+ }
274+ srcFiles , err := report .Load (src )
275+ srcResult .err = err
276+ srcResult .files = srcFiles .FileReports
258277
259- g .Go (func () error {
260- files , base , err := relFileReport (ctx , c , srcPath , isImage )
261- res := ScanResult {files : files , base : base , err : err }
262- if isImage {
263- res .imageURI , res .tmpRoot = c .ScanPaths [0 ], srcPath
278+ // Extract image URI and temp root from the report's file paths
279+ srcResult .imageURI = report .ExtractImageURI (srcResult .files )
280+ srcResult .tmpRoot = report .ExtractTmpRoot (srcResult .files )
281+ srcResult .base = filepath .Base (srcPath )
282+
283+ destFile , err := os .Open (destPath )
284+ if err != nil {
285+ return nil , err
286+ }
287+ defer destFile .Close ()
288+ dst , err := io .ReadAll (destFile )
289+ if err != nil {
290+ return nil , err
264291 }
265- srcCh <- res
266- return err
267- })
268292
269- srcResult := <- srcCh
270- if srcResult .err != nil {
271- return nil , fmt .Errorf ("source scan error: %w" , srcResult .err )
272- }
293+ destFiles , err := report .Load (dst )
294+ destResult .err = err
295+ destResult .files = destFiles .FileReports
273296
274- g .Go (func () error {
275- files , base , err := relFileReport (ctx , c , destPath , isImage )
276- res := ScanResult {files : files , base : base , err : err }
277- if isImage {
278- res .imageURI , res .tmpRoot = c .ScanPaths [1 ], destPath
297+ // Extract image URI and temp root from the report's file paths
298+ destResult .imageURI = report .ExtractImageURI (destResult .files )
299+ destResult .tmpRoot = report .ExtractTmpRoot (destResult .files )
300+ default :
301+ var g errgroup.Group
302+
303+ g .Go (func () error {
304+ files , base , err := relFileReport (ctx , c , srcPath , isImage )
305+ res := ScanResult {files : files , base : base , err : err }
306+ if isImage {
307+ res .imageURI , res .tmpRoot = c .ScanPaths [0 ], srcPath
308+ }
309+ srcCh <- res
310+ return err
311+ })
312+
313+ srcResult = <- srcCh
314+ if srcResult .err != nil {
315+ return nil , fmt .Errorf ("source scan error: %w" , srcResult .err )
279316 }
280- destCh <- res
281- return err
282- })
283317
284- destResult := <- destCh
285- if destResult .err != nil {
286- return nil , fmt .Errorf ("destination scan error: %w" , destResult .err )
287- }
318+ g .Go (func () error {
319+ files , base , err := relFileReport (ctx , c , destPath , isImage )
320+ res := ScanResult {files : files , base : base , err : err }
321+ if isImage {
322+ res .imageURI , res .tmpRoot = c .ScanPaths [1 ], destPath
323+ }
324+ destCh <- res
325+ return err
326+ })
288327
289- if err := g .Wait (); err != nil {
290- return nil , err
328+ destResult = <- destCh
329+ if destResult .err != nil {
330+ return nil , fmt .Errorf ("destination scan error: %w" , destResult .err )
331+ }
332+
333+ if err := g .Wait (); err != nil {
334+ return nil , err
335+ }
291336 }
292337
293338 d := & malcontent.DiffReport {
@@ -310,11 +355,11 @@ func Diff(ctx context.Context, c malcontent.Config, _ *clog.Logger) (*malcontent
310355 // and employ add/delete for files that are not the same
311356 // When scanning two files, do a 1:1 comparison and
312357 // consider the source -> destination as a change rather than an add/delete
313- shouldHandleDir := ((srcInfo .IsDir () && destInfo .IsDir ()) || (srcIsArchive && destIsArchive )) || isImage
358+ shouldHandleDir := ((srcInfo .IsDir () && destInfo .IsDir ()) || (srcIsArchive && destIsArchive )) || isImage || isReport
314359 archiveOrImage := (srcIsArchive && destIsArchive ) || isImage
315360
316361 if shouldHandleDir {
317- handleDir (ctx , c , srcResult , destResult , d , archiveOrImage )
362+ handleDir (ctx , c , srcResult , destResult , d , archiveOrImage , isReport )
318363 } else {
319364 srcFile := selectPrimaryFile (srcResult .files )
320365 destFile := selectPrimaryFile (destResult .files )
@@ -325,14 +370,14 @@ func Diff(ctx context.Context, c malcontent.Config, _ *clog.Logger) (*malcontent
325370 d .Removed .Set (removed , srcFile )
326371 d .Added .Set (added , destFile )
327372 } else {
328- handleFile (ctx , c , srcFile , destFile , removed , added , d , srcResult , destResult , archiveOrImage )
373+ handleFile (ctx , c , srcFile , destFile , removed , added , d , srcResult , destResult , archiveOrImage , isReport )
329374 }
330375 }
331376 }
332377
333378 // infer moves only if there are entries in both Added and Removed
334379 if d .Added .Len () > 0 && d .Removed .Len () > 0 {
335- inferMoves (ctx , c , d , srcResult , destResult , archiveOrImage )
380+ inferMoves (ctx , c , d , srcResult , destResult , archiveOrImage , isReport )
336381 }
337382
338383 defer func () {
@@ -343,7 +388,7 @@ func Diff(ctx context.Context, c malcontent.Config, _ *clog.Logger) (*malcontent
343388 return & malcontent.Report {Diff : d }, nil
344389}
345390
346- func handleDir (ctx context.Context , c malcontent.Config , src , dest ScanResult , d * malcontent.DiffReport , archiveOrImage bool ) {
391+ func handleDir (ctx context.Context , c malcontent.Config , src , dest ScanResult , d * malcontent.DiffReport , archiveOrImage , isReport bool ) {
347392 if ctx .Err () != nil {
348393 return
349394 }
@@ -380,17 +425,27 @@ func handleDir(ctx context.Context, c malcontent.Config, src, dest ScanResult, d
380425 // These files are considered removals from the destination
381426 for _ , name := range srcKeys {
382427 srcFr := srcFiles [name ]
383- removed := formatKey (src , CleanPath (srcFr .Path , src .tmpRoot ))
428+ var removed string
429+ if isReport {
430+ removed = report .FormatReportKey (srcFr .Path , src .tmpRoot , src .imageURI )
431+ } else {
432+ removed = formatKey (src , CleanPath (srcFr .Path , src .tmpRoot ))
433+ }
384434 if destFr , exists := destFiles [name ]; exists {
385- added := formatKey (dest , CleanPath (destFr .Path , dest .tmpRoot ))
435+ var added string
436+ if isReport {
437+ added = report .FormatReportKey (destFr .Path , dest .tmpRoot , dest .imageURI )
438+ } else {
439+ added = formatKey (dest , CleanPath (destFr .Path , dest .tmpRoot ))
440+ }
386441 if filterDiff (ctx , c , srcFr , destFr ) {
387442 continue
388443 }
389444 if c .ScoreAll || scoreFile (srcFr , destFr ) {
390445 d .Removed .Set (removed , srcFr )
391446 d .Added .Set (added , destFr )
392447 } else {
393- handleFile (ctx , c , srcFr , destFr , removed , added , d , src , dest , archiveOrImage )
448+ handleFile (ctx , c , srcFr , destFr , removed , added , d , src , dest , archiveOrImage , isReport )
394449 }
395450 } else {
396451 d .Removed .Set (removed , srcFr )
@@ -401,14 +456,19 @@ func handleDir(ctx context.Context, c malcontent.Config, src, dest ScanResult, d
401456 // These files are considered additions to the destination
402457 for _ , name := range destKeys {
403458 destFr := destFiles [name ]
404- added := formatKey (dest , CleanPath (destFr .Path , dest .tmpRoot ))
459+ var added string
460+ if isReport {
461+ added = report .FormatReportKey (destFr .Path , dest .tmpRoot , dest .imageURI )
462+ } else {
463+ added = formatKey (dest , CleanPath (destFr .Path , dest .tmpRoot ))
464+ }
405465 if _ , exists := srcFiles [name ]; ! exists {
406466 d .Added .Set (added , destFr )
407467 }
408468 }
409469}
410470
411- func handleFile (ctx context.Context , c malcontent.Config , fr , tr * malcontent.FileReport , removed , added string , d * malcontent.DiffReport , _ , dest ScanResult , archiveOrImage bool ) {
471+ func handleFile (ctx context.Context , c malcontent.Config , fr , tr * malcontent.FileReport , removed , added string , d * malcontent.DiffReport , _ , dest ScanResult , archiveOrImage , isReport bool ) {
412472 if ctx .Err () != nil {
413473 return
414474 }
@@ -450,7 +510,9 @@ func handleFile(ctx context.Context, c malcontent.Config, fr, tr *malcontent.Fil
450510 return rbs .Behaviors [i ].ID < rbs .Behaviors [j ].ID
451511 })
452512
453- if archiveOrImage {
513+ if isReport {
514+ rbs .Path = report .FormatReportKey (rbs .Path , dest .tmpRoot , dest .imageURI )
515+ } else if archiveOrImage {
454516 rbs .Path = CleanPath (rbs .Path , "/private" )
455517 rbs .Path = formatKey (dest , CleanPath (rbs .Path , dest .tmpRoot ))
456518 }
@@ -571,17 +633,17 @@ func combineReports(ctx context.Context, c malcontent.Config, removed, added *or
571633 return combined
572634}
573635
574- func inferMoves (ctx context.Context , c malcontent.Config , d * malcontent.DiffReport , src , dest ScanResult , archiveOrImage bool ) {
636+ func inferMoves (ctx context.Context , c malcontent.Config , d * malcontent.DiffReport , src , dest ScanResult , archiveOrImage , isReport bool ) {
575637 if ctx .Err () != nil {
576638 return
577639 }
578640
579641 for _ , cr := range combineReports (ctx , c , d .Removed , d .Added ) {
580- fileMove (ctx , c , cr .RemovedFR , cr .AddedFR , cr .Removed , cr .Added , d , cr .Score , src , dest , archiveOrImage )
642+ fileMove (ctx , c , cr .RemovedFR , cr .AddedFR , cr .Removed , cr .Added , d , cr .Score , src , dest , archiveOrImage , isReport )
581643 }
582644}
583645
584- func fileMove (ctx context.Context , c malcontent.Config , fr , tr * malcontent.FileReport , rpath , apath string , d * malcontent.DiffReport , score float64 , src ScanResult , dest ScanResult , archiveOrImage bool ) {
646+ func fileMove (ctx context.Context , c malcontent.Config , fr , tr * malcontent.FileReport , rpath , apath string , d * malcontent.DiffReport , score float64 , src ScanResult , dest ScanResult , archiveOrImage , isReport bool ) {
585647 if ctx .Err () != nil {
586648 return
587649 }
@@ -634,7 +696,10 @@ func fileMove(ctx context.Context, c malcontent.Config, fr, tr *malcontent.FileR
634696 return abs .Behaviors [i ].ID < abs .Behaviors [j ].ID
635697 })
636698
637- if archiveOrImage {
699+ if isReport {
700+ abs .Path = report .FormatReportKey (abs .Path , dest .tmpRoot , dest .imageURI )
701+ abs .PreviousPath = report .FormatReportKey (abs .PreviousPath , src .tmpRoot , src .imageURI )
702+ } else if archiveOrImage {
638703 abs .Path = CleanPath (abs .Path , "/private" )
639704 abs .PreviousPath = CleanPath (abs .PreviousPath , "/private" )
640705 abs .Path = formatKey (dest , CleanPath (abs .Path , dest .tmpRoot ))
0 commit comments