diff --git a/dev-tools/packaging/package_test.go b/dev-tools/packaging/package_test.go index 363e6d904d4..e01b6c566e5 100644 --- a/dev-tools/packaging/package_test.go +++ b/dev-tools/packaging/package_test.go @@ -30,10 +30,10 @@ import ( "flag" "fmt" "io" - "io/ioutil" "os" "path/filepath" "regexp" + "slices" "strings" "testing" @@ -106,6 +106,7 @@ func TestZip(t *testing.T) { func TestDocker(t *testing.T) { dockers := getFiles(t, regexp.MustCompile(`\.docker\.tar\.gz$`)) for _, docker := range dockers { + t.Log(docker) checkDocker(t, docker) } } @@ -713,13 +714,19 @@ func readZip(t *testing.T, zipFile string, inspectors ...inspector) (*packageFil } func readDocker(dockerFile string) (*packageFile, *dockerInfo, error) { + // Read the manifest file first so that the config file and layer + // names are known in advance. + manifest, err := getDockerManifest(dockerFile) + if err != nil { + return nil, nil, err + } + file, err := os.Open(dockerFile) if err != nil { return nil, nil, err } defer file.Close() - var manifest *dockerManifest var info *dockerInfo layers := make(map[string]*packageFile) @@ -740,22 +747,17 @@ func readDocker(dockerFile string) (*packageFile, *dockerInfo, error) { } switch { - case header.Name == "manifest.json": - manifest, err = readDockerManifest(tarReader) - if err != nil { - return nil, nil, err - } - case strings.HasSuffix(header.Name, ".json") && header.Name != "manifest.json": + case header.Name == manifest.Config: info, err = readDockerInfo(tarReader) if err != nil { return nil, nil, err } - case strings.HasSuffix(header.Name, "/layer.tar"): + case slices.Contains(manifest.Layers, header.Name): layer, err := readTarContents(header.Name, tarReader) if err != nil { return nil, nil, err } - layers[filepath.Dir(header.Name)] = layer + layers[header.Name] = layer } } @@ -769,10 +771,9 @@ func readDocker(dockerFile string) (*packageFile, *dockerInfo, error) { // Read layers in order and for each file keep only the entry seen in the later layer p := &packageFile{Name: filepath.Base(dockerFile), Contents: map[string]packageEntry{}} for _, layer := range manifest.Layers { - layerID := filepath.Dir(layer) - layerFile, found := layers[layerID] + layerFile, found := layers[layer] if !found { - return nil, nil, fmt.Errorf("layer not found: %s", layerID) + return nil, nil, fmt.Errorf("layer not found: %s", layer) } for name, entry := range layerFile.Contents { // Check only files in working dir and entrypoint @@ -798,6 +799,44 @@ func readDocker(dockerFile string) (*packageFile, *dockerInfo, error) { return p, info, nil } +// getDockerManifest opens a gzipped tar file to read the Docker manifest.json +// that it is expected to contain. +func getDockerManifest(file string) (*dockerManifest, error) { + f, err := os.Open(file) + if err != nil { + return nil, err + } + defer f.Close() + + gzipReader, err := gzip.NewReader(f) + if err != nil { + return nil, err + } + defer gzipReader.Close() + + var manifest *dockerManifest + tarReader := tar.NewReader(gzipReader) + for { + header, err := tarReader.Next() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + return nil, err + } + + if header.Name == "manifest.json" { + manifest, err = readDockerManifest(tarReader) + if err != nil { + return nil, err + } + break + } + } + + return manifest, nil +} + type dockerManifest struct { Config string RepoTags []string @@ -805,7 +844,7 @@ type dockerManifest struct { } func readDockerManifest(r io.Reader) (*dockerManifest, error) { - data, err := ioutil.ReadAll(r) + data, err := io.ReadAll(r) if err != nil { return nil, err } @@ -833,7 +872,7 @@ type dockerInfo struct { } func readDockerInfo(r io.Reader) (*dockerInfo, error) { - data, err := ioutil.ReadAll(r) + data, err := io.ReadAll(r) if err != nil { return nil, err }