Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ require (
github.com/google/go-containerregistry v0.11.0
github.com/in-toto/in-toto-golang v0.3.4-0.20220709202702-fa494aaa0add
github.com/knqyf263/go-rpmdb v0.0.0-20220629110411-9a3bd2ebb923
github.com/opencontainers/go-digest v1.0.0
github.com/sassoftware/go-rpmutils v0.2.0
github.com/sigstore/cosign v1.12.1
github.com/sigstore/rekor v0.12.1-0.20220915152154-4bb6f441c1b2
Expand Down Expand Up @@ -218,7 +219,6 @@ require (
github.com/mozillazg/docker-credential-acr-helper v0.3.0 // indirect
github.com/nwaples/rardecode v1.1.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
Expand Down
73 changes: 73 additions & 0 deletions syft/source/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@ import (

"github.com/bmatcuk/doublestar/v4"
"github.com/mholt/archiver/v3"
digest "github.com/opencontainers/go-digest"
"github.com/spf13/afero"

"github.com/anchore/stereoscope"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
)

// Source is an object that captures the data source to be cataloged, configuration, and a specific resolver used
// in cataloging (based on the data source and configuration)
type Source struct {
id artifact.ID
Image *image.Image // the image object to be cataloged (image only)
Metadata Metadata
directoryResolver *directoryResolver
Expand Down Expand Up @@ -304,6 +307,76 @@ func NewFromImage(img *image.Image, userImageStr string) (Source, error) {
}, nil
}

func (s Source) ID() artifact.ID {
if s.id == "" {
s.SetID()
}
return s.id
}

func (s *Source) SetID() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this PR include a call to this function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ Second PR is going to do that so we can link it into relationships - Just wanted to keep this PR small, but I can add the call so we're setting it on construction

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking again, there also isn't ONE great place to put the single call since we have functions like New, NewFromImage NewFromDirectory NewFromFile.

Let me see all the places it needs to be included so we have full coverage of all the source creation

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could just have it get set on the first call to ID if s.id = ""

var d string
switch s.Metadata.Scheme {
case DirectoryScheme:
d = digest.FromString(s.Metadata.Path).String()
case FileScheme:
// attempt to use the digest of the contents of the file as the ID
file, err := os.Open(s.Metadata.Path)
if err != nil {
d = digest.FromString(s.Metadata.Path).String()
break
}
di, err := digest.FromReader(file)
if err != nil {
d = digest.FromString(s.Metadata.Path).String()
break
}
d = di.String()
case ImageScheme:
manifestDigest := digest.FromBytes(s.Image.Metadata.RawManifest).String()
if manifestDigest != "" {
d = manifestDigest
break
}

// calcuate chain ID for image sources where manifestDigest is not available
// https://github.com/opencontainers/image-spec/blob/main/config.md#layer-chainid
d = calculateChainID(s.Image)
if d == "" {
// TODO what happens here if image has no layers?
// Is this case possible
d = digest.FromString(s.Metadata.ImageMetadata.UserInput).String()
}
default: // for UnknownScheme we hash the struct
id, _ := artifact.IDByHash(s)
d = string(id)
}

s.id = artifact.ID(strings.TrimPrefix(d, "sha256:"))
}

func calculateChainID(img *image.Image) string {
Comment thread
spiffcs marked this conversation as resolved.
if len(img.Layers) < 1 {
return ""
}

// DiffID(L0) = digest of layer 0
// https://github.com/anchore/stereoscope/blob/1b1b744a919964f38d14e1416fb3f25221b761ce/pkg/image/layer_metadata.go#L19-L32
chainID := img.Layers[0].Metadata.Digest
id := chain(chainID, img.Layers[1:])

return id
}

func chain(chainID string, layers []*image.Layer) string {
if len(layers) < 1 {
return chainID
}

chainID = digest.FromString(layers[0].Metadata.Digest + " " + chainID).String()
return chain(chainID, layers[1:])
}

func (s *Source) FileResolver(scope Scope) (FileResolver, error) {
switch s.Metadata.Scheme {
case DirectoryScheme, FileScheme:
Expand Down
67 changes: 67 additions & 0 deletions syft/source/source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft/artifact"
)

func TestParseInput(t *testing.T) {
Expand Down Expand Up @@ -65,6 +66,72 @@ func TestNewFromImageFails(t *testing.T) {
})
}

func TestSetID(t *testing.T) {
layer := image.NewLayer(nil)
layer.Metadata = image.LayerMetadata{
Digest: "sha256:6f4fb385d4e698647bf2a450749dfbb7bc2831ec9a730ef4046c78c08d468e89",
}
img := image.Image{
Layers: []*image.Layer{layer},
}

tests := []struct {
name string
input *Source
expected artifact.ID
}{
{
name: "source.SetID sets the ID for FileScheme",
input: &Source{
Metadata: Metadata{
Scheme: FileScheme,
Path: "test-fixtures/image-simple/file-1.txt",
},
},
expected: artifact.ID("55096713247489add592ce977637be868497132b36d1e294a3831925ec64319a"),
},
{
name: "source.SetID sets the ID for ImageScheme",
input: &Source{
Image: &img,
Metadata: Metadata{
Scheme: ImageScheme,
},
},
expected: artifact.ID("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"),
},
{
name: "source.SetID sets the ID for DirectoryScheme",
input: &Source{
Image: &img,
Metadata: Metadata{
Scheme: DirectoryScheme,
Path: "test-fixtures/image-simple",
},
},
expected: artifact.ID("91db61e5e0ae097ef764796ce85e442a93f2a03e5313d4c7307e9b413f62e8c4"),
},
{
name: "source.SetID sets the ID for UnknownScheme",
input: &Source{
Image: &img,
Metadata: Metadata{
Scheme: UnknownScheme,
Path: "test-fixtures/image-simple",
},
},
expected: artifact.ID("febd2d6148dc327d"),
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
test.input.SetID()
assert.Equal(t, test.expected, test.input.ID())
})
}
}

func TestNewFromImage(t *testing.T) {
layer := image.NewLayer(nil)
img := image.Image{
Expand Down