@@ -17,14 +17,14 @@ import (
1717 "debug/buildinfo"
1818 "debug/elf"
1919 "encoding/hex"
20- "encoding/json"
2120 "errors"
2221 "flag"
2322 "fmt"
2423 "hash"
2524 "io"
2625 "maps"
2726 "os"
27+ "path"
2828 "path/filepath"
2929 "regexp"
3030 "slices"
@@ -34,6 +34,7 @@ import (
3434
3535 "github.com/blakesmith/ar"
3636 "github.com/cavaliergopher/rpm"
37+ "github.com/google/go-containerregistry/pkg/v1/tarball"
3738 "github.com/stretchr/testify/assert"
3839 "github.com/stretchr/testify/require"
3940
@@ -1082,60 +1083,78 @@ func openZip(zipFile string) (*zip.ReadCloser, error) {
10821083func readDocker (t * testing.T , dockerFile string , filterWorkingDir bool ) (* packageFile , * dockerInfo , error ) {
10831084 t .Helper ()
10841085
1085- // Read the manifest file first so that the config file and layer
1086- // names are known in advance.
1087- manifest , err := getDockerManifest (dockerFile )
1086+ // Decompress the .tar.gz file, go-containerregistry wants uncompressed here
1087+ f , err := os .Open (dockerFile )
10881088 require .NoError (t , err )
10891089
1090- file , err := os . Open ( dockerFile )
1090+ gz , err := gzip . NewReader ( f )
10911091 require .NoError (t , err )
1092- defer file .Close ()
10931092
1094- var info * dockerInfo
1093+ _ , dockerFileName := path . Split ( dockerFile )
10951094
1096- stat , err := file .Stat ()
1095+ tempDir := t .TempDir ()
1096+ uncompressed , err := os .CreateTemp (tempDir , dockerFileName )
10971097 require .NoError (t , err )
10981098
1099- layers := make (map [string ]* packageFile )
1100-
1101- gzipReader , err := gzip .NewReader (file )
1099+ _ , err = io .Copy (uncompressed , gz )
11021100 require .NoError (t , err )
1103- defer gzipReader .Close ()
1101+ require . NoError ( t , uncompressed .Close () )
11041102
1105- tarReader := tar .NewReader (gzipReader )
1106- for {
1107- header , err := tarReader .Next ()
1108- if err != nil {
1109- if errors .Is (err , io .EOF ) {
1110- break
1111- }
1112- require .NoError (t , err )
1113- }
1103+ // Load the Docker image tarball using go-containerregistry
1104+ img , err := tarball .ImageFromPath (uncompressed .Name (), nil )
1105+ require .NoError (t , err , "failed to load Docker image from %s" , dockerFile )
11141106
1115- switch {
1116- case header .Name == manifest .Config :
1117- info , err = readDockerInfo (tarReader )
1118- require .NoError (t , err )
1119- case slices .Contains (manifest .Layers , header .Name ):
1120- layer , err := readTarContents (header .Name , tarReader )
1121- require .NoError (t , err )
1122- layers [header .Name ] = layer
1123- }
1107+ // Get the config file which contains entrypoint, labels, user, workingDir
1108+ configFile , err := img .ConfigFile ()
1109+ require .NoError (t , err , "failed to get config file from image" )
1110+
1111+ imgSize , err := img .Size ()
1112+ require .NoError (t , err , "failed to get image size" )
1113+ info := & dockerInfo {
1114+ Size : imgSize ,
11241115 }
1116+ info .Config .Entrypoint = configFile .Config .Entrypoint
1117+ info .Config .Labels = configFile .Config .Labels
1118+ info .Config .User = configFile .Config .User
1119+ info .Config .WorkingDir = configFile .Config .WorkingDir
11251120
11261121 require .NotZero (t , len (info .Config .Entrypoint ), "no entrypoint" )
11271122 workingDir := info .Config .WorkingDir
11281123 entrypoint := info .Config .Entrypoint [0 ]
11291124
1125+ // Get all layers and read their contents
1126+ layers , err := img .Layers ()
1127+ require .NoError (t , err , "failed to get layers from image" )
1128+
11301129 // Read layers in order and for each file keep only the entry seen in the later layer
11311130 p := & packageFile {Name : filepath .Base (dockerFile ), Contents : map [string ]packageEntry {}}
1132- for _ , layer := range manifest .Layers {
1133- layerFile , found := layers [layer ]
1134- require .True (t , found , fmt .Sprintf ("layer not found: %s" , layer ))
1135- for name , entry := range layerFile .Contents {
1131+ for _ , layer := range layers {
1132+ rc , err := layer .Uncompressed ()
1133+ require .NoError (t , err , "failed to get uncompressed layer" )
1134+
1135+ tarReader := tar .NewReader (rc )
1136+ for {
1137+ header , err := tarReader .Next ()
1138+ if err != nil {
1139+ if errors .Is (err , io .EOF ) {
1140+ break
1141+ }
1142+ assert .NoError (t , rc .Close ())
1143+ require .NoError (t , err )
1144+ }
1145+
1146+ name := header .Name
11361147 if excludedPathsPattern .MatchString (name ) {
11371148 continue
11381149 }
1150+
1151+ entry := packageEntry {
1152+ File : name ,
1153+ UID : header .Uid ,
1154+ GID : header .Gid ,
1155+ Mode : header .FileInfo ().Mode (),
1156+ }
1157+
11391158 // Check only files in working dir and entrypoint
11401159 if ! filterWorkingDir || strings .HasPrefix ("/" + name , workingDir ) || "/" + name == entrypoint {
11411160 p .Contents [name ] = entry
@@ -1147,19 +1166,13 @@ func readDocker(t *testing.T, dockerFile string, filterWorkingDir bool) (*packag
11471166 }
11481167 }
11491168 }
1169+ assert .NoError (t , rc .Close ())
11501170 }
11511171
11521172 require .NotZero (t , len (p .Contents ), fmt .Sprintf ("no files found in docker working directory (%s)" , info .Config .WorkingDir ))
1153- info .Size = stat .Size ()
11541173 return p , info , nil
11551174}
11561175
1157- type dockerManifest struct {
1158- Config string
1159- RepoTags []string
1160- Layers []string
1161- }
1162-
11631176type dockerInfo struct {
11641177 Config struct {
11651178 Entrypoint []string
@@ -1170,78 +1183,6 @@ type dockerInfo struct {
11701183 Size int64
11711184}
11721185
1173- func readDockerInfo (r io.Reader ) (* dockerInfo , error ) {
1174- data , err := io .ReadAll (r )
1175- if err != nil {
1176- return nil , err
1177- }
1178-
1179- var info dockerInfo
1180- err = json .Unmarshal (data , & info )
1181- if err != nil {
1182- return nil , err
1183- }
1184-
1185- return & info , nil
1186- }
1187-
1188- // getDockerManifest opens a gzipped tar file to read the Docker manifest.json
1189- // that it is expected to contain.
1190- func getDockerManifest (file string ) (* dockerManifest , error ) {
1191- f , err := os .Open (file )
1192- if err != nil {
1193- return nil , err
1194- }
1195- defer f .Close ()
1196-
1197- gzipReader , err := gzip .NewReader (f )
1198- if err != nil {
1199- return nil , err
1200- }
1201- defer gzipReader .Close ()
1202-
1203- var manifest * dockerManifest
1204- tarReader := tar .NewReader (gzipReader )
1205- for {
1206- header , err := tarReader .Next ()
1207- if err != nil {
1208- if errors .Is (err , io .EOF ) {
1209- break
1210- }
1211- return nil , err
1212- }
1213-
1214- if header .Name == "manifest.json" {
1215- manifest , err = readDockerManifest (tarReader )
1216- if err != nil {
1217- return nil , err
1218- }
1219- break
1220- }
1221- }
1222-
1223- return manifest , nil
1224- }
1225-
1226- func readDockerManifest (r io.Reader ) (* dockerManifest , error ) {
1227- data , err := io .ReadAll (r )
1228- if err != nil {
1229- return nil , err
1230- }
1231-
1232- var manifests []* dockerManifest
1233- err = json .Unmarshal (data , & manifests )
1234- if err != nil {
1235- return nil , err
1236- }
1237-
1238- if len (manifests ) != 1 {
1239- return nil , fmt .Errorf ("one and only one manifest expected, %d found" , len (manifests ))
1240- }
1241-
1242- return manifests [0 ], nil
1243- }
1244-
12451186func checkSha512PackageHash (t * testing.T , packageFile string ) {
12461187 t .Run ("check hash file" , func (t * testing.T ) {
12471188 expectedHashFile := packageFile + ".sha512"
0 commit comments