Skip to content

Commit

Permalink
Changed the type of Layer DiffID from string to digest.Digest in …
Browse files Browse the repository at this point in the history
…order to preserve the hashing algorithm used. This change also facilitates usage of `ChainID` function for base image identification.

PiperOrigin-RevId: 715455605
  • Loading branch information
Mario Leyva authored and copybara-github committed Jan 14, 2025
1 parent 9491c94 commit b486b70
Show file tree
Hide file tree
Showing 9 changed files with 47 additions and 25 deletions.
3 changes: 2 additions & 1 deletion artifact/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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.
Expand Down
19 changes: 13 additions & 6 deletions artifact/image/layerscanning/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -321,8 +329,6 @@ func fillChainLayerWithFilesFromTar(img *Image, tarReader *tar.Reader, originLay

// realFilePath is where the file will be written to disk. filepath.Clean first to convert
// to OS specific file path.
// TODO: b/377553499 - Escape invalid characters on windows that's valid on linux
// realFilePath := filepath.Join(dirPath, filepath.Clean(cleanedFilePath))
realFilePath := filepath.Join(dirPath, filepath.FromSlash(cleanedFilePath))

var newNode *fileNode
Expand Down Expand Up @@ -399,7 +405,8 @@ func (img *Image) handleDir(realFilePath, virtualPath, originLayerID string, tar
func (img *Image) handleFile(realFilePath, virtualPath, originLayerID string, tarReader *tar.Reader, header *tar.Header, isWhiteout bool) (*fileNode, error) {
// Write all files as read/writable by the current user, inaccessible by anyone else
// Actual permission bits are stored in FileNode
f, err := os.OpenFile(realFilePath, os.O_CREATE|os.O_RDWR, filePermission)
modeWithOwnerReadWrite := header.FileInfo().Mode() | 0600
f, err := os.OpenFile(realFilePath, os.O_CREATE|os.O_RDWR, modeWithOwnerReadWrite)

if err != nil {
return nil, err
Expand Down
7 changes: 4 additions & 3 deletions artifact/image/layerscanning/image/layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
Expand All @@ -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
}

Expand Down Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion artifact/image/layerscanning/image/layer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 4 additions & 3 deletions artifact/image/layerscanning/testing/fakelayer/fake_layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
}

Expand Down
9 changes: 8 additions & 1 deletion artifact/image/layerscanning/trace/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand Down
15 changes: 10 additions & 5 deletions artifact/image/layerscanning/trace/trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
})
Expand All @@ -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{
Expand All @@ -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,
})
Expand All @@ -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,
Expand Down
2 changes: 0 additions & 2 deletions scalibr.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,6 @@ func (Scanner) Scan(ctx context.Context, config *ScanConfig) (sr *ScanResult) {
// details. Functions to create an Image from a tarball, remote name, or v1.Image are available in
// the artifact/image/layerscanning/image package.
func (s Scanner) ScanContainer(ctx context.Context, img *image.Image, config *ScanConfig) (sr *ScanResult, err error) {
defer img.CleanUp()

chainLayers, err := img.ChainLayers()
if err != nil {
return nil, fmt.Errorf("failed to get chain layers: %w", err)
Expand Down

0 comments on commit b486b70

Please sign in to comment.