@@ -17,7 +17,6 @@ import (
1717 "debug/buildinfo"
1818 "debug/elf"
1919 "encoding/hex"
20- "encoding/json"
2120 "errors"
2221 "flag"
2322 "fmt"
@@ -34,6 +33,7 @@ import (
3433
3534 "github.com/blakesmith/ar"
3635 "github.com/cavaliergopher/rpm"
36+ "github.com/google/go-containerregistry/pkg/v1/tarball"
3737 "github.com/stretchr/testify/assert"
3838 "github.com/stretchr/testify/require"
3939
@@ -1082,60 +1082,80 @@ func openZip(zipFile string) (*zip.ReadCloser, error) {
10821082func readDocker (t * testing.T , dockerFile string , filterWorkingDir bool ) (* packageFile , * dockerInfo , error ) {
10831083 t .Helper ()
10841084
1085- // Read the manifest file first so that the config file and layer
1086- // names are known in advance.
1087- manifest , err := getDockerManifest (dockerFile )
1085+ // Decompress the .tar.gz file, go-containerregistry wants uncompressed here
1086+ f , err := os .Open (dockerFile )
10881087 require .NoError (t , err )
10891088
1090- file , err := os . Open ( dockerFile )
1089+ gz , err := gzip . NewReader ( f )
10911090 require .NoError (t , err )
1092- defer file .Close ()
10931091
1094- var info * dockerInfo
1092+ tempDir := t .TempDir ()
1093+ uncompressed , err := os .CreateTemp (tempDir , "docker" )
1094+ require .NoError (t , err )
10951095
1096- stat , err := file . Stat ( )
1096+ _ , err = io . Copy ( uncompressed , gz )
10971097 require .NoError (t , err )
1098+ require .NoError (t , uncompressed .Close ())
10981099
1099- layers := make (map [string ]* packageFile )
1100+ opener := func () (io.ReadCloser , error ) {
1101+ return os .Open (uncompressed .Name ())
1102+ }
11001103
1101- gzipReader , err := gzip . NewReader ( file )
1102- require . NoError ( t , err )
1103- defer gzipReader . Close ( )
1104+ // Load the Docker image tarball using go-containerregistry
1105+ img , err := tarball . Image ( opener , nil )
1106+ require . NoError ( t , err , "failed to load Docker image from %s" , dockerFile )
11041107
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- }
1108+ // Get the config file which contains entrypoint, labels, user, workingDir
1109+ configFile , err := img .ConfigFile ()
1110+ require .NoError (t , err , "failed to get config file from image" )
11141111
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- }
1112+ imgSize , err := img .Size ()
1113+ require .NoError (t , err , "failed to get image size" )
1114+ info := & dockerInfo {
1115+ Size : imgSize ,
11241116 }
1117+ info .Config .Entrypoint = configFile .Config .Entrypoint
1118+ info .Config .Labels = configFile .Config .Labels
1119+ info .Config .User = configFile .Config .User
1120+ info .Config .WorkingDir = configFile .Config .WorkingDir
11251121
11261122 require .NotZero (t , len (info .Config .Entrypoint ), "no entrypoint" )
11271123 workingDir := info .Config .WorkingDir
11281124 entrypoint := info .Config .Entrypoint [0 ]
11291125
1126+ // Get all layers and read their contents
1127+ layers , err := img .Layers ()
1128+ require .NoError (t , err , "failed to get layers from image" )
1129+
11301130 // Read layers in order and for each file keep only the entry seen in the later layer
11311131 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 {
1132+ for _ , layer := range layers {
1133+ rc , err := layer .Uncompressed ()
1134+ require .NoError (t , err , "failed to get uncompressed layer" )
1135+
1136+ tarReader := tar .NewReader (rc )
1137+ for {
1138+ header , err := tarReader .Next ()
1139+ if err != nil {
1140+ if errors .Is (err , io .EOF ) {
1141+ break
1142+ }
1143+ assert .NoError (t , rc .Close ())
1144+ require .NoError (t , err )
1145+ }
1146+
1147+ name := header .Name
11361148 if excludedPathsPattern .MatchString (name ) {
11371149 continue
11381150 }
1151+
1152+ entry := packageEntry {
1153+ File : name ,
1154+ UID : header .Uid ,
1155+ GID : header .Gid ,
1156+ Mode : header .FileInfo ().Mode (),
1157+ }
1158+
11391159 // Check only files in working dir and entrypoint
11401160 if ! filterWorkingDir || strings .HasPrefix ("/" + name , workingDir ) || "/" + name == entrypoint {
11411161 p .Contents [name ] = entry
@@ -1147,19 +1167,13 @@ func readDocker(t *testing.T, dockerFile string, filterWorkingDir bool) (*packag
11471167 }
11481168 }
11491169 }
1170+ assert .NoError (t , rc .Close ())
11501171 }
11511172
11521173 require .NotZero (t , len (p .Contents ), fmt .Sprintf ("no files found in docker working directory (%s)" , info .Config .WorkingDir ))
1153- info .Size = stat .Size ()
11541174 return p , info , nil
11551175}
11561176
1157- type dockerManifest struct {
1158- Config string
1159- RepoTags []string
1160- Layers []string
1161- }
1162-
11631177type dockerInfo struct {
11641178 Config struct {
11651179 Entrypoint []string
@@ -1170,78 +1184,6 @@ type dockerInfo struct {
11701184 Size int64
11711185}
11721186
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-
12451187func checkSha512PackageHash (t * testing.T , packageFile string ) {
12461188 t .Run ("check hash file" , func (t * testing.T ) {
12471189 expectedHashFile := packageFile + ".sha512"
0 commit comments