e22ff5d176
Docker expects hashes of compressed tarballs in the manifest (as these are used to fetch from the content-addressable layer store), but for some reason it expects hashes in the configuration layer to be of uncompressed tarballs. To achieve this an additional SHA256 hash is calculcated while creating the layer tarballs, but before passing them to the gzip writer. In the current constellation the symlink layer is first compressed and then decompressed again to calculate its hash. This can be refactored in a future change.
103 lines
2.3 KiB
Go
103 lines
2.3 KiB
Go
package builder
|
|
|
|
// This file implements logic for walking through a directory and creating a
|
|
// tarball of it.
|
|
//
|
|
// The tarball is written straight to the supplied reader, which makes it
|
|
// possible to create an image layer from the specified store paths, hash it and
|
|
// upload it in one reading pass.
|
|
|
|
import (
|
|
"archive/tar"
|
|
"compress/gzip"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/google/nixery/server/layers"
|
|
)
|
|
|
|
// Create a new compressed tarball from each of the paths in the list
|
|
// and write it to the supplied writer.
|
|
//
|
|
// The uncompressed tarball is hashed because image manifests must
|
|
// contain both the hashes of compressed and uncompressed layers.
|
|
func packStorePaths(l *layers.Layer, w io.Writer) (string, error) {
|
|
shasum := sha256.New()
|
|
gz := gzip.NewWriter(w)
|
|
multi := io.MultiWriter(shasum, gz)
|
|
t := tar.NewWriter(multi)
|
|
|
|
for _, path := range l.Contents {
|
|
err := filepath.Walk(path, tarStorePath(t))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
if err := t.Close(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if err := gz.Close(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return fmt.Sprintf("sha256:%x", shasum.Sum([]byte{})), nil
|
|
}
|
|
|
|
func tarStorePath(w *tar.Writer) filepath.WalkFunc {
|
|
return func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If the entry is not a symlink or regular file, skip it.
|
|
if info.Mode()&os.ModeSymlink == 0 && !info.Mode().IsRegular() {
|
|
return nil
|
|
}
|
|
|
|
// the symlink target is read if this entry is a symlink, as it
|
|
// is required when creating the file header
|
|
var link string
|
|
if info.Mode()&os.ModeSymlink != 0 {
|
|
link, err = os.Readlink(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
header, err := tar.FileInfoHeader(info, link)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// The name retrieved from os.FileInfo only contains the file's
|
|
// basename, but the full path is required within the layer
|
|
// tarball.
|
|
header.Name = path
|
|
if err = w.WriteHeader(header); err != nil {
|
|
return err
|
|
}
|
|
|
|
// At this point, return if no file content needs to be written
|
|
if !info.Mode().IsRegular() {
|
|
return nil
|
|
}
|
|
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := io.Copy(w, f); err != nil {
|
|
return err
|
|
}
|
|
|
|
f.Close()
|
|
|
|
return nil
|
|
}
|
|
}
|