mirror of
https://github.com/anchore/syft.git
synced 2026-11-01 15:06:36 +01:00
update json schema with optional poweruser data shape
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
@@ -36,7 +36,7 @@ var identityFiles = []parseEntry{
|
||||
}
|
||||
|
||||
// Identify parses distro-specific files to determine distro metadata like version and release.
|
||||
func Identify(resolver source.Resolver) *Distro {
|
||||
func Identify(resolver source.FileResolver) *Distro {
|
||||
var distro *Distro
|
||||
|
||||
identifyLoop:
|
||||
|
||||
@@ -99,7 +99,12 @@ func TestIdentifyDistro(t *testing.T) {
|
||||
t.Fatalf("unable to produce a new source for testing: %s", test.fixture)
|
||||
}
|
||||
|
||||
d := Identify(s.Resolver)
|
||||
resolver, err := s.FileResolver(source.SquashedScope)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get resolver: %+v", err)
|
||||
}
|
||||
|
||||
d := Identify(resolver)
|
||||
if d == nil {
|
||||
if test.Type == UnknownDistroType {
|
||||
return
|
||||
|
||||
+4
-4
@@ -10,11 +10,11 @@ const (
|
||||
// AppUpdateAvailable is a partybus event that occurs when an application update is available
|
||||
AppUpdateAvailable partybus.EventType = "syft-app-update-available"
|
||||
|
||||
// CatalogerStarted is a partybus event that occurs when the package cataloging has begun
|
||||
CatalogerStarted partybus.EventType = "syft-cataloger-started-event"
|
||||
// PackageCatalogerStarted is a partybus event that occurs when the package cataloging has begun
|
||||
PackageCatalogerStarted partybus.EventType = "syft-cataloger-started-event"
|
||||
|
||||
// CatalogerFinished is a partybus event that occurs when the package cataloging has completed
|
||||
CatalogerFinished partybus.EventType = "syft-cataloger-finished-event"
|
||||
// PresenterReady is a partybus event that occurs when an analysis result is ready for final presentation
|
||||
PresenterReady partybus.EventType = "syft-presenter-ready-event"
|
||||
|
||||
// ImportStarted is a partybus event that occurs when an SBOM upload process has begun
|
||||
ImportStarted partybus.EventType = "syft-import-started-event"
|
||||
|
||||
@@ -6,11 +6,12 @@ package parsers
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/internal/presenter"
|
||||
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/syft/syft/cataloger"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/presenter"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
)
|
||||
|
||||
@@ -40,7 +41,7 @@ func checkEventType(actual, expected partybus.EventType) error {
|
||||
}
|
||||
|
||||
func ParseCatalogerStarted(e partybus.Event) (*cataloger.Monitor, error) {
|
||||
if err := checkEventType(e.Type, event.CatalogerStarted); err != nil {
|
||||
if err := checkEventType(e.Type, event.PackageCatalogerStarted); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -52,8 +53,8 @@ func ParseCatalogerStarted(e partybus.Event) (*cataloger.Monitor, error) {
|
||||
return &monitor, nil
|
||||
}
|
||||
|
||||
func ParseCatalogerFinished(e partybus.Event) (presenter.Presenter, error) {
|
||||
if err := checkEventType(e.Type, event.CatalogerFinished); err != nil {
|
||||
func ParsePresenterReady(e partybus.Event) (presenter.Presenter, error) {
|
||||
if err := checkEventType(e.Type, event.PresenterReady); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package file
|
||||
|
||||
type Digest struct {
|
||||
Algorithm string `json:"algorithm"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
var supportedHashAlgorithms = make(map[string]crypto.Hash)
|
||||
|
||||
type DigestsCataloger struct {
|
||||
resolver source.FileResolver
|
||||
hashes []crypto.Hash
|
||||
}
|
||||
|
||||
func init() {
|
||||
for _, h := range []crypto.Hash{
|
||||
crypto.MD5,
|
||||
crypto.SHA1,
|
||||
crypto.SHA256,
|
||||
} {
|
||||
supportedHashAlgorithms[cleanAlgorithmName(h.String())] = h
|
||||
}
|
||||
}
|
||||
|
||||
func NewDigestsCataloger(resolver source.FileResolver, hashAlgorithms []string) (*DigestsCataloger, error) {
|
||||
var hashes []crypto.Hash
|
||||
for _, hashStr := range hashAlgorithms {
|
||||
name := cleanAlgorithmName(hashStr)
|
||||
hashObj, ok := supportedHashAlgorithms[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported hash algorithm: %s", hashStr)
|
||||
}
|
||||
hashes = append(hashes, hashObj)
|
||||
}
|
||||
|
||||
return &DigestsCataloger{
|
||||
resolver: resolver,
|
||||
hashes: hashes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *DigestsCataloger) Catalog() (map[source.Location][]Digest, error) {
|
||||
results := make(map[source.Location][]Digest)
|
||||
for location := range i.resolver.AllLocations() {
|
||||
result, err := i.catalogLocation(location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results[location] = result
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (i *DigestsCataloger) catalogLocation(location source.Location) ([]Digest, error) {
|
||||
contentReader, err := i.resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer contentReader.Close()
|
||||
|
||||
// create a set of hasher objects tied together with a single writer to feed content into
|
||||
hashers := make([]hash.Hash, len(i.hashes))
|
||||
writers := make([]io.Writer, len(i.hashes))
|
||||
for idx, hashObj := range i.hashes {
|
||||
hashers[idx] = hashObj.New()
|
||||
writers[idx] = hashers[idx]
|
||||
}
|
||||
|
||||
size, err := io.Copy(io.MultiWriter(writers...), contentReader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to observe contents of %+v: %+v", location.RealPath, err)
|
||||
}
|
||||
|
||||
result := make([]Digest, len(i.hashes))
|
||||
if size > 0 {
|
||||
// only capture digests when there is content. It is important to do this based on SIZE and not
|
||||
// FILE TYPE. The reasoning is that it is possible for a tar to be crafted with a header-only
|
||||
// file type but a body is still allowed.
|
||||
for idx, hasher := range hashers {
|
||||
result[idx] = Digest{
|
||||
Algorithm: cleanAlgorithmName(i.hashes[idx].String()),
|
||||
Value: fmt.Sprintf("%+x", hasher.Sum(nil)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func cleanAlgorithmName(name string) string {
|
||||
lower := strings.ToLower(name)
|
||||
return strings.Replace(lower, "-", "", -1)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
type MetadataCataloger struct {
|
||||
resolver source.FileResolver
|
||||
}
|
||||
|
||||
func NewMetadataCataloger(resolver source.FileResolver) *MetadataCataloger {
|
||||
return &MetadataCataloger{
|
||||
resolver: resolver,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *MetadataCataloger) Catalog() (map[source.Location]source.FileMetadata, error) {
|
||||
results := make(map[source.Location]source.FileMetadata)
|
||||
for location := range i.resolver.AllLocations() {
|
||||
metadata, err := i.resolver.FileMetadataByLocation(location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results[location] = metadata
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
+13
-47
@@ -17,32 +17,29 @@ Similar to the cataloging process, Linux distribution identification is also per
|
||||
package syft
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/cataloger"
|
||||
"github.com/anchore/syft/syft/distro"
|
||||
"github.com/anchore/syft/syft/logger"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
jsonPresenter "github.com/anchore/syft/syft/presenter/json"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
)
|
||||
|
||||
// Catalog the given image from a particular perspective (e.g. squashed source, all-layers source). Returns the discovered
|
||||
// set of packages, the identified Linux distribution, and the source object used to wrap the data source.
|
||||
func Catalog(userInput string, scope source.Scope) (source.Source, *pkg.Catalog, *distro.Distro, error) {
|
||||
theSource, cleanup, err := source.New(userInput, scope)
|
||||
defer cleanup()
|
||||
// CatalogPackages takes an inventory of packages from the given image from a particular perspective
|
||||
// (e.g. squashed source, all-layers source). Returns the discovered set of packages, the identified Linux
|
||||
// distribution, and the source object used to wrap the data source.
|
||||
func CatalogPackages(src source.Source, scope source.Scope) (*pkg.Catalog, *distro.Distro, error) {
|
||||
resolver, err := src.FileResolver(scope)
|
||||
if err != nil {
|
||||
return source.Source{}, nil, nil, err
|
||||
return nil, nil, fmt.Errorf("unable to determine FileResolver while cataloging packages: %w", err)
|
||||
}
|
||||
|
||||
// find the distro
|
||||
theDistro := distro.Identify(theSource.Resolver)
|
||||
theDistro := distro.Identify(resolver)
|
||||
if theDistro != nil {
|
||||
log.Infof("identified distro: %s", theDistro.String())
|
||||
} else {
|
||||
@@ -51,7 +48,7 @@ func Catalog(userInput string, scope source.Scope) (source.Source, *pkg.Catalog,
|
||||
|
||||
// conditionally use the correct set of loggers based on the input type (container image or directory)
|
||||
var catalogers []cataloger.Cataloger
|
||||
switch theSource.Metadata.Scheme {
|
||||
switch src.Metadata.Scheme {
|
||||
case source.ImageScheme:
|
||||
log.Info("cataloging image")
|
||||
catalogers = cataloger.ImageCatalogers()
|
||||
@@ -59,46 +56,15 @@ func Catalog(userInput string, scope source.Scope) (source.Source, *pkg.Catalog,
|
||||
log.Info("cataloging directory")
|
||||
catalogers = cataloger.DirectoryCatalogers()
|
||||
default:
|
||||
return source.Source{}, nil, nil, fmt.Errorf("unable to determine cataloger set from scheme=%+v", theSource.Metadata.Scheme)
|
||||
return nil, nil, fmt.Errorf("unable to determine cataloger set from scheme=%+v", src.Metadata.Scheme)
|
||||
}
|
||||
|
||||
catalog, err := cataloger.Catalog(theSource.Resolver, theDistro, catalogers...)
|
||||
catalog, err := cataloger.Catalog(resolver, theDistro, catalogers...)
|
||||
if err != nil {
|
||||
return source.Source{}, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return theSource, catalog, theDistro, nil
|
||||
}
|
||||
|
||||
// CatalogFromJSON takes an existing syft report and generates native syft objects.
|
||||
func CatalogFromJSON(reader io.Reader) (source.Metadata, *pkg.Catalog, *distro.Distro, error) {
|
||||
var doc jsonPresenter.Document
|
||||
var err error
|
||||
decoder := json.NewDecoder(reader)
|
||||
if err := decoder.Decode(&doc); err != nil {
|
||||
return source.Metadata{}, nil, nil, err
|
||||
}
|
||||
|
||||
var pkgs = make([]pkg.Package, len(doc.Artifacts))
|
||||
for i, a := range doc.Artifacts {
|
||||
pkgs[i], err = a.ToPackage()
|
||||
if err != nil {
|
||||
return source.Metadata{}, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
catalog := pkg.NewCatalog(pkgs...)
|
||||
|
||||
var theDistro *distro.Distro
|
||||
if doc.Distro.Name != "" {
|
||||
d, err := distro.NewDistro(distro.Type(doc.Distro.Name), doc.Distro.Version, doc.Distro.IDLike)
|
||||
if err != nil {
|
||||
return source.Metadata{}, nil, nil, err
|
||||
}
|
||||
theDistro = &d
|
||||
}
|
||||
|
||||
return doc.Source.ToSourceMetadata(), catalog, theDistro, nil
|
||||
return catalog, theDistro, nil
|
||||
}
|
||||
|
||||
// SetLogger sets the logger object used for all syft logging calls.
|
||||
|
||||
Reference in New Issue
Block a user