diff --git a/artifact/image/image.go b/artifact/image/image.go index 17f9e7dd..ce7d8982 100644 --- a/artifact/image/image.go +++ b/artifact/image/image.go @@ -27,6 +27,7 @@ import ( "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/osv-scalibr/artifact/image/require" "github.com/google/osv-scalibr/artifact/image/unpack" + "github.com/opencontainers/go-digest" scalibrfs "github.com/google/osv-scalibr/fs" ) @@ -43,7 +44,7 @@ type Layer interface { // produced by the FS method. IsEmpty() bool // DiffID is the hash of the uncompressed layer. Will be an empty string if the layer is empty. - DiffID() string + DiffID() digest.Digest // Command is the specific command that produced the layer. Command() string // Uncompressed gives the uncompressed tar as a file reader. diff --git a/artifact/image/layerscanning/image/image.go b/artifact/image/layerscanning/image/image.go index dfff48be..3e5f1a6d 100644 --- a/artifact/image/layerscanning/image/image.go +++ b/artifact/image/layerscanning/image/image.go @@ -153,9 +153,16 @@ func FromV1Image(v1Image v1.Image, config *Config) (*Image, error) { // Add the root directory to each chain layer. If this is not done, then the virtual paths won't // be rooted, and traversal in the virtual filesystem will be broken. for _, chainLayer := range chainLayers { + var layerDigest string + if chainLayer.latestLayer.IsEmpty() { + layerDigest = "" + } else { + layerDigest = chainLayer.latestLayer.DiffID().Encoded() + } + err := chainLayer.fileNodeTree.Insert("/", &fileNode{ extractDir: outputImage.ExtractDir, - originLayerID: chainLayer.latestLayer.DiffID(), + originLayerID: layerDigest, virtualPath: "/", isWhiteout: false, mode: fs.ModeDir, @@ -176,14 +183,15 @@ func FromV1Image(v1Image v1.Image, config *Config) (*Image, error) { continue } + originLayerID := chainLayer.latestLayer.DiffID().Encoded() + // Create the chain layer directory if it doesn't exist. - dirPath := path.Join(tempPath, chainLayer.latestLayer.DiffID()) + dirPath := path.Join(tempPath, originLayerID) if err := os.Mkdir(dirPath, dirPermission); err != nil && !errors.Is(err, fs.ErrExist) { return &outputImage, fmt.Errorf("failed to create chain layer directory: %w", err) } chainLayersToFill := chainLayers[i:] - originLayerID := chainLayer.latestLayer.DiffID() layerReader, err := chainLayer.latestLayer.Uncompressed() if err != nil { return &outputImage, err diff --git a/artifact/image/layerscanning/image/layer.go b/artifact/image/layerscanning/image/layer.go index 5b83e742..d70356c8 100644 --- a/artifact/image/layerscanning/image/layer.go +++ b/artifact/image/layerscanning/image/layer.go @@ -26,6 +26,7 @@ import ( "github.com/google/osv-scalibr/artifact/image" "github.com/google/osv-scalibr/artifact/image/pathtree" scalibrfs "github.com/google/osv-scalibr/fs" + "github.com/opencontainers/go-digest" ) var ( @@ -48,7 +49,7 @@ var ( // Layer implements the Layer interface. type Layer struct { - diffID string + diffID digest.Digest buildCommand string isEmpty bool uncompressed io.ReadCloser @@ -65,7 +66,7 @@ func (layer *Layer) IsEmpty() bool { } // DiffID returns the diff id of the layer. -func (layer *Layer) DiffID() string { +func (layer *Layer) DiffID() digest.Digest { return layer.diffID } @@ -94,7 +95,7 @@ func convertV1Layer(v1Layer v1.Layer, command string, isEmpty bool) (*Layer, err } return &Layer{ - diffID: diffID.Hex, + diffID: digest.FromString(diffID.String()), buildCommand: command, isEmpty: isEmpty, uncompressed: uncompressed, diff --git a/artifact/image/layerscanning/image/layer_test.go b/artifact/image/layerscanning/image/layer_test.go index b8fb9d9f..fd968de2 100644 --- a/artifact/image/layerscanning/image/layer_test.go +++ b/artifact/image/layerscanning/image/layer_test.go @@ -48,7 +48,8 @@ func TestConvertV1Layer(t *testing.T) { command: "ADD file", isEmpty: false, wantLayer: &Layer{ - diffID: "abc123", + // The diffID is the encoded string: digest.FromString("abc123"). + diffID: "sha256:27f2fb5c8ba6b6d955cbc3cd78e89a898cac60a0442260129235683f4cc74e90", buildCommand: "ADD file", isEmpty: false, uncompressed: reader, diff --git a/artifact/image/layerscanning/testing/fakechainlayer/fake_chain_layer.go b/artifact/image/layerscanning/testing/fakechainlayer/fake_chain_layer.go index c27dbbc8..edddce9e 100644 --- a/artifact/image/layerscanning/testing/fakechainlayer/fake_chain_layer.go +++ b/artifact/image/layerscanning/testing/fakechainlayer/fake_chain_layer.go @@ -25,21 +25,22 @@ import ( "github.com/google/osv-scalibr/artifact/image" scalibrfs "github.com/google/osv-scalibr/fs" + "github.com/opencontainers/go-digest" ) // FakeChainLayer is a fake implementation of the image.ChainLayer and scalibrfs.FS interface for // testing purposes. type FakeChainLayer struct { index int - diffID string command string - layer image.Layer testDir string + diffID digest.Digest + layer image.Layer files map[string]string } // New creates a new FakeChainLayer. -func New(testDir string, index int, diffID string, command string, layer image.Layer, files map[string]string) (*FakeChainLayer, error) { +func New(testDir string, index int, diffID digest.Digest, command string, layer image.Layer, files map[string]string) (*FakeChainLayer, error) { for name, contents := range files { filename := filepath.Join(testDir, name) file, err := os.Create(filename) diff --git a/artifact/image/layerscanning/testing/fakelayer/fake_layer.go b/artifact/image/layerscanning/testing/fakelayer/fake_layer.go index f6b5ac36..98eb3a91 100644 --- a/artifact/image/layerscanning/testing/fakelayer/fake_layer.go +++ b/artifact/image/layerscanning/testing/fakelayer/fake_layer.go @@ -21,16 +21,17 @@ import ( "io" scalibrfs "github.com/google/osv-scalibr/fs" + "github.com/opencontainers/go-digest" ) // FakeLayer is a fake implementation of the image.Layer interface for testing purposes. type FakeLayer struct { - diffID string + diffID digest.Digest buildCommand string } // New creates a new FakeLayer. -func New(diffID string, buildCommand string) *FakeLayer { +func New(diffID digest.Digest, buildCommand string) *FakeLayer { return &FakeLayer{ diffID: diffID, buildCommand: buildCommand, @@ -43,7 +44,7 @@ func (fakeLayer *FakeLayer) FS() scalibrfs.FS { } // DiffID returns the diffID of the layer. -func (fakeLayer *FakeLayer) DiffID() string { +func (fakeLayer *FakeLayer) DiffID() digest.Digest { return fakeLayer.diffID } diff --git a/artifact/image/layerscanning/trace/trace.go b/artifact/image/layerscanning/trace/trace.go index 606ed54a..5f6aac03 100644 --- a/artifact/image/layerscanning/trace/trace.go +++ b/artifact/image/layerscanning/trace/trace.go @@ -56,9 +56,16 @@ func PopulateLayerDetails(ctx context.Context, inventory []*extractor.Inventory, // Create list of layer details struct to be referenced by inventory. for i, chainLayer := range chainLayers { + var diffID string + if chainLayer.Layer().IsEmpty() { + diffID = "" + } else { + diffID = chainLayer.Layer().DiffID().Encoded() + } + chainLayerDetailsList = append(chainLayerDetailsList, &extractor.LayerDetails{ Index: i, - DiffID: chainLayer.Layer().DiffID(), + DiffID: diffID, Command: chainLayer.Layer().Command(), InBaseImage: false, }) diff --git a/artifact/image/layerscanning/trace/trace_test.go b/artifact/image/layerscanning/trace/trace_test.go index 1a1d4df6..71c0f5d9 100644 --- a/artifact/image/layerscanning/trace/trace_test.go +++ b/artifact/image/layerscanning/trace/trace_test.go @@ -27,9 +27,10 @@ import ( "github.com/google/osv-scalibr/extractor/filesystem" "github.com/google/osv-scalibr/stats" "github.com/google/osv-scalibr/testing/fakeextractor" + "github.com/opencontainers/go-digest" ) -func setupFakeChainLayer(t *testing.T, testDir string, index int, diffID string, command string, fileContents map[string]string) *fakechainlayer.FakeChainLayer { +func setupFakeChainLayer(t *testing.T, testDir string, index int, diffID digest.Digest, command string, fileContents map[string]string) *fakechainlayer.FakeChainLayer { t.Helper() layer := fakelayer.New(diffID, command) @@ -56,7 +57,8 @@ func TestPopulateLayerDetails(t *testing.T) { // Chain Layer 1: Start with foo and bar packages. // - foo.txt // - bar.txt - fakeChainLayer1 := setupFakeChainLayer(t, t.TempDir(), 0, "diff-id-1", "command-1", map[string]string{ + digest1 := digest.NewDigestFromEncoded(digest.SHA256, "diff-id-1") + fakeChainLayer1 := setupFakeChainLayer(t, t.TempDir(), 0, digest1, "command-1", map[string]string{ fooFile: fooPackage, barFile: barPackage, }) @@ -71,7 +73,8 @@ func TestPopulateLayerDetails(t *testing.T) { // Chain Layer 2: Deletes bar package. // - foo.txt - fakeChainLayer2 := setupFakeChainLayer(t, t.TempDir(), 1, "diff-id-2", "command-2", map[string]string{ + digest2 := digest.NewDigestFromEncoded(digest.SHA256, "diff-id-2") + fakeChainLayer2 := setupFakeChainLayer(t, t.TempDir(), 1, digest2, "command-2", map[string]string{ fooFile: fooPackage, }) fakeExtractor2 := fakeextractor.New("fake-extractor-2", 1, []string{fooFile}, map[string]fakeextractor.NamesErr{ @@ -83,7 +86,8 @@ func TestPopulateLayerDetails(t *testing.T) { // Chain Layer 3: Adds baz package. // - foo.txt // - baz.txt - fakeChainLayer3 := setupFakeChainLayer(t, t.TempDir(), 2, "diff-id-3", "command-3", map[string]string{ + digest3 := digest.NewDigestFromEncoded(digest.SHA256, "diff-id-3") + fakeChainLayer3 := setupFakeChainLayer(t, t.TempDir(), 2, digest3, "command-3", map[string]string{ fooFile: fooPackage, bazFile: bazPackage, }) @@ -100,7 +104,8 @@ func TestPopulateLayerDetails(t *testing.T) { // - foo.txt // - bar.txt // - baz.txt - fakeChainLayer4 := setupFakeChainLayer(t, t.TempDir(), 3, "diff-id-4", "command-4", map[string]string{ + digest4 := digest.NewDigestFromEncoded(digest.SHA256, "diff-id-4") + fakeChainLayer4 := setupFakeChainLayer(t, t.TempDir(), 3, digest4, "command-4", map[string]string{ fooFile: fooPackage, barFile: barPackage, bazFile: bazPackage,