github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/pkg/cataloger/generic/cataloger.go (about)

     1  package generic
     2  
     3  import (
     4  	"github.com/anchore/syft/syft/artifact"
     5  	"github.com/anchore/syft/syft/file"
     6  	"github.com/anchore/syft/syft/linux"
     7  	"github.com/anchore/syft/syft/pkg"
     8  	"github.com/lineaje-labs/syft/internal"
     9  	"github.com/lineaje-labs/syft/internal/log"
    10  )
    11  
    12  type processor func(resolver file.Resolver, env Environment) []request
    13  
    14  type request struct {
    15  	file.Location
    16  	Parser
    17  }
    18  
    19  // Cataloger implements the Catalog interface and is responsible for dispatching the proper parser function for
    20  // a given path or glob pattern. This is intended to be reusable across many package cataloger types.
    21  type Cataloger struct {
    22  	processor         []processor
    23  	upstreamCataloger string
    24  }
    25  
    26  func (c *Cataloger) WithParserByGlobs(parser Parser, globs ...string) *Cataloger {
    27  	c.processor = append(c.processor,
    28  		func(resolver file.Resolver, env Environment) []request {
    29  			var requests []request
    30  			for _, g := range globs {
    31  				log.WithFields("glob", g).Trace("searching for paths matching glob")
    32  
    33  				matches, err := resolver.FilesByGlob(g)
    34  				if err != nil {
    35  					log.Warnf("unable to process glob=%q: %+v", g, err)
    36  					continue
    37  				}
    38  				requests = append(requests, makeRequests(parser, matches)...)
    39  			}
    40  			return requests
    41  		},
    42  	)
    43  	return c
    44  }
    45  
    46  func (c *Cataloger) WithParserByMimeTypes(parser Parser, types ...string) *Cataloger {
    47  	c.processor = append(c.processor,
    48  		func(resolver file.Resolver, env Environment) []request {
    49  			var requests []request
    50  			log.WithFields("mimetypes", types).Trace("searching for paths matching mimetype")
    51  			matches, err := resolver.FilesByMIMEType(types...)
    52  			if err != nil {
    53  				log.Warnf("unable to process mimetypes=%+v: %+v", types, err)
    54  				return nil
    55  			}
    56  			requests = append(requests, makeRequests(parser, matches)...)
    57  			return requests
    58  		},
    59  	)
    60  	return c
    61  }
    62  
    63  func (c *Cataloger) WithParserByPath(parser Parser, paths ...string) *Cataloger {
    64  	c.processor = append(c.processor,
    65  		func(resolver file.Resolver, env Environment) []request {
    66  			var requests []request
    67  			for _, p := range paths {
    68  				log.WithFields("path", p).Trace("searching for path")
    69  
    70  				matches, err := resolver.FilesByPath(p)
    71  				if err != nil {
    72  					log.Warnf("unable to process path=%q: %+v", p, err)
    73  					continue
    74  				}
    75  				requests = append(requests, makeRequests(parser, matches)...)
    76  			}
    77  			return requests
    78  		},
    79  	)
    80  	return c
    81  }
    82  
    83  func makeRequests(parser Parser, locations []file.Location) []request {
    84  	var requests []request
    85  	for _, l := range locations {
    86  		requests = append(requests, request{
    87  			Location: l,
    88  			Parser:   parser,
    89  		})
    90  	}
    91  	return requests
    92  }
    93  
    94  // NewCataloger if provided path-to-parser-function and glob-to-parser-function lookups creates a Cataloger
    95  func NewCataloger(upstreamCataloger string) *Cataloger {
    96  	return &Cataloger{
    97  		upstreamCataloger: upstreamCataloger,
    98  	}
    99  }
   100  
   101  // Name returns a string that uniquely describes the upstream cataloger that this Generic Cataloger represents.
   102  func (c *Cataloger) Name() string {
   103  	return c.upstreamCataloger
   104  }
   105  
   106  // Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing the catalog source.
   107  func (c *Cataloger) Catalog(resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
   108  	var packages []pkg.Package
   109  	var relationships []artifact.Relationship
   110  
   111  	logger := log.Nested("cataloger", c.upstreamCataloger)
   112  
   113  	env := Environment{
   114  		// TODO: consider passing into the cataloger, this would affect the cataloger interface (and all implementations). This can be deferred until later.
   115  		LinuxRelease: linux.IdentifyRelease(resolver),
   116  	}
   117  
   118  	for _, req := range c.selectFiles(resolver) {
   119  		location, parser := req.Location, req.Parser
   120  
   121  		log.WithFields("path", location.RealPath).Trace("parsing file contents")
   122  
   123  		contentReader, err := resolver.FileContentsByLocation(location)
   124  		if err != nil {
   125  			logger.WithFields("location", location.RealPath, "error", err).Warn("unable to fetch contents")
   126  			continue
   127  		}
   128  
   129  		discoveredPackages, discoveredRelationships, err := parser(resolver, &env, file.NewLocationReadCloser(location, contentReader))
   130  		internal.CloseAndLogError(contentReader, location.AccessPath)
   131  		if err != nil {
   132  			logger.WithFields("location", location.RealPath, "error", err).Warnf("cataloger failed")
   133  			continue
   134  		}
   135  
   136  		for _, p := range discoveredPackages {
   137  			p.FoundBy = c.upstreamCataloger
   138  			packages = append(packages, p)
   139  		}
   140  
   141  		relationships = append(relationships, discoveredRelationships...)
   142  	}
   143  	return packages, relationships, nil
   144  }
   145  
   146  // selectFiles takes a set of file trees and resolves and file references of interest for future cataloging
   147  func (c *Cataloger) selectFiles(resolver file.Resolver) []request {
   148  	var requests []request
   149  	for _, proc := range c.processor {
   150  		requests = append(requests, proc(resolver, Environment{})...)
   151  	}
   152  	return requests
   153  }