Skip to content

Commit eda1d7a

Browse files
Mario Leyvacopybara-github
Mario Leyva
authored andcommitted
Add TopFS method to get the top-level filesystem of a scalibr Image.
PiperOrigin-RevId: 751481441
1 parent d5523e7 commit eda1d7a

File tree

2 files changed

+148
-1
lines changed

2 files changed

+148
-1
lines changed

artifact/image/layerscanning/image/image.go

+16-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"github.com/google/osv-scalibr/artifact/image/require"
3737
"github.com/google/osv-scalibr/artifact/image/symlink"
3838
"github.com/google/osv-scalibr/artifact/image/whiteout"
39+
scalibrfs "github.com/google/osv-scalibr/fs"
3940
"github.com/google/osv-scalibr/log"
4041
)
4142

@@ -57,6 +58,8 @@ var (
5758
ErrSymlinkPointsOutsideRoot = errors.New("symlink points outside the root")
5859
// ErrInvalidConfig is returned when the image config is invalid.
5960
ErrInvalidConfig = errors.New("invalid image config")
61+
// ErrNoLayersFound is returned when no layers are found in the image.
62+
ErrNoLayersFound = errors.New("no layers found in image")
6063
)
6164

6265
// ========================================================
@@ -110,8 +113,20 @@ type Image struct {
110113
BaseImageIndex int
111114
}
112115

116+
// TopFS returns the filesystem of the top-most chainlayer of the image. All available files should
117+
// be present in the filesystem returned.
118+
func (img *Image) TopFS() (scalibrfs.FS, error) {
119+
if len(img.chainLayers) == 0 {
120+
return nil, ErrNoLayersFound
121+
}
122+
return img.chainLayers[len(img.chainLayers)-1].FS(), nil
123+
}
124+
113125
// ChainLayers returns the chain layers of the image.
114126
func (img *Image) ChainLayers() ([]scalibrImage.ChainLayer, error) {
127+
if len(img.chainLayers) == 0 {
128+
return nil, ErrNoLayersFound
129+
}
115130
scalibrChainLayers := make([]scalibrImage.ChainLayer, 0, len(img.chainLayers))
116131
for _, chainLayer := range img.chainLayers {
117132
scalibrChainLayers = append(scalibrChainLayers, chainLayer)
@@ -282,7 +297,7 @@ func layerDirectory(layerIndex int) string {
282297
return fmt.Sprintf("layer-%d", layerIndex)
283298
}
284299

285-
// addRootDirectoryToChainLayers adds the root ("\"") directory to each chain layer.
300+
// addRootDirectoryToChainLayers adds the root ("/") directory to each chain layer.
286301
func addRootDirectoryToChainLayers(chainLayers []*chainLayer, extractDir string) error {
287302
for i, chainLayer := range chainLayers {
288303
err := chainLayer.fileNodeTree.Insert("/", &fileNode{

artifact/image/layerscanning/image/image_test.go

+132
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,138 @@ func TestInitializeChainLayers(t *testing.T) {
11751175
}
11761176
}
11771177

1178+
func TestTopFS(t *testing.T) {
1179+
tests := []struct {
1180+
name string
1181+
image *Image
1182+
wantFilesFromFS []string
1183+
wantErr bool
1184+
}{
1185+
{
1186+
name: "no chain layers",
1187+
image: &Image{
1188+
chainLayers: []*chainLayer{},
1189+
},
1190+
wantErr: true,
1191+
},
1192+
{
1193+
name: "single chain layer",
1194+
image: &Image{
1195+
chainLayers: []*chainLayer{
1196+
{
1197+
fileNodeTree: func() *pathtree.Node[fileNode] {
1198+
root := pathtree.NewNode[fileNode]()
1199+
_ = root.Insert("/", &fileNode{
1200+
virtualPath: "/",
1201+
isWhiteout: false,
1202+
mode: fs.ModeDir | dirPermission,
1203+
})
1204+
_ = root.Insert("/foo.txt", &fileNode{
1205+
virtualPath: "/foo.txt",
1206+
mode: filePermission,
1207+
})
1208+
return root
1209+
}(),
1210+
index: 0,
1211+
latestLayer: &Layer{
1212+
buildCommand: "",
1213+
diffID: "sha256:123",
1214+
isEmpty: false,
1215+
},
1216+
},
1217+
},
1218+
},
1219+
wantFilesFromFS: []string{"/foo.txt"},
1220+
},
1221+
{
1222+
name: "multiple chain layers",
1223+
image: &Image{
1224+
chainLayers: []*chainLayer{
1225+
{
1226+
fileNodeTree: func() *pathtree.Node[fileNode] {
1227+
root := pathtree.NewNode[fileNode]()
1228+
_ = root.Insert("/", &fileNode{
1229+
virtualPath: "/",
1230+
isWhiteout: false,
1231+
mode: fs.ModeDir | dirPermission,
1232+
})
1233+
_ = root.Insert("/foo.txt", &fileNode{
1234+
virtualPath: "/foo.txt",
1235+
mode: filePermission,
1236+
})
1237+
return root
1238+
}(),
1239+
index: 0,
1240+
latestLayer: &Layer{
1241+
buildCommand: "",
1242+
diffID: "sha256:123",
1243+
isEmpty: false,
1244+
},
1245+
},
1246+
{
1247+
fileNodeTree: func() *pathtree.Node[fileNode] {
1248+
root := pathtree.NewNode[fileNode]()
1249+
_ = root.Insert("/", &fileNode{
1250+
extractDir: "",
1251+
layerDir: "",
1252+
virtualPath: "/",
1253+
isWhiteout: false,
1254+
mode: fs.ModeDir | dirPermission,
1255+
})
1256+
_ = root.Insert("/foo.txt", &fileNode{
1257+
virtualPath: "/foo.txt",
1258+
mode: filePermission,
1259+
})
1260+
_ = root.Insert("/bar.txt", &fileNode{
1261+
virtualPath: "/bar.txt",
1262+
mode: filePermission,
1263+
})
1264+
return root
1265+
}(),
1266+
index: 0,
1267+
latestLayer: &Layer{
1268+
buildCommand: "",
1269+
diffID: "sha256:123",
1270+
isEmpty: false,
1271+
},
1272+
},
1273+
},
1274+
},
1275+
wantFilesFromFS: []string{"/foo.txt", "/bar.txt"},
1276+
},
1277+
}
1278+
1279+
for _, tc := range tests {
1280+
t.Run(tc.name, func(t *testing.T) {
1281+
gotFS, err := tc.image.TopFS()
1282+
if tc.wantErr {
1283+
if err == nil {
1284+
t.Fatalf("TopFS() returned nil error, but want non-nil error")
1285+
}
1286+
return
1287+
}
1288+
1289+
var gotPaths []string
1290+
err = fs.WalkDir(gotFS, "/", func(path string, d fs.DirEntry, err error) error {
1291+
if err != nil || d.IsDir() {
1292+
return err
1293+
}
1294+
1295+
gotPaths = append(gotPaths, path)
1296+
return nil
1297+
})
1298+
1299+
if err != nil {
1300+
t.Fatalf("WalkDir() returned error: %v", err)
1301+
}
1302+
1303+
if diff := cmp.Diff(tc.wantFilesFromFS, gotPaths, cmpopts.SortSlices(func(a, b string) bool { return a < b })); diff != "" {
1304+
t.Errorf("TopFS() returned incorrect files: got %v, want %v", gotPaths, tc.wantFilesFromFS)
1305+
}
1306+
})
1307+
}
1308+
}
1309+
11781310
// tarEntry represents a single entry in a tarball. It contains the header and data for the entry.
11791311
// If the data is nil, the entry will be written without any content.
11801312
type tarEntry struct {

0 commit comments

Comments
 (0)