container_images/registry-image-forked/commands/unpack.go (139 lines of code) (raw):
package commands
import (
"archive/tar"
"compress/gzip"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/concourse/go-archive/tarfs"
"github.com/fatih/color"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/sirupsen/logrus"
"github.com/vbauerster/mpb"
"github.com/vbauerster/mpb/decor"
)
const whiteoutPrefix = ".wh."
const whiteoutOpaqueDir = whiteoutPrefix + whiteoutPrefix + ".opq"
func unpackImage(dest string, img v1.Image, debug bool, out io.Writer) error {
layers, err := img.Layers()
if err != nil {
return err
}
chown := os.Getuid() == 0
if debug {
out = ioutil.Discard
}
progress := mpb.New(mpb.WithOutput(out))
bars := make([]*mpb.Bar, len(layers))
for i, layer := range layers {
size, err := layer.Size()
if err != nil {
return err
}
digest, err := layer.Digest()
if err != nil {
return err
}
bars[i] = progress.AddBar(
size,
mpb.PrependDecorators(decor.Name(color.HiBlackString(digest.Hex[0:12]))),
mpb.AppendDecorators(decor.CountersKibiByte("%.1f/%.1f")),
)
}
// iterate over layers in reverse order; no need to write things files that
// are modified by later layers anyway
for i, layer := range layers {
logrus.Debugf("extracting layer %d of %d", i+1, len(layers))
err := extractLayer(dest, layer, bars[i], chown)
if err != nil {
return err
}
}
progress.Wait()
return nil
}
func extractLayer(dest string, layer v1.Layer, bar *mpb.Bar, chown bool) error {
r, err := layer.Compressed()
if err != nil {
return err
}
gr, err := gzip.NewReader(bar.ProxyReader(r))
if err != nil {
return err
}
tr := tar.NewReader(gr)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
path := filepath.Join(dest, filepath.Clean(hdr.Name))
base := filepath.Base(path)
dir := filepath.Dir(path)
log := logrus.WithFields(logrus.Fields{
"Name": hdr.Name,
})
log.Debug("unpacking")
if base == whiteoutOpaqueDir {
fi, err := os.Lstat(dir)
if err != nil && !os.IsNotExist(err) {
return err
}
log.Debugf("removing contents of %s", dir)
if err := os.RemoveAll(dir); err != nil {
return err
}
if err := os.Mkdir(dir, fi.Mode()&os.ModePerm); err != nil {
return err
}
continue
} else if strings.HasPrefix(base, whiteoutPrefix) {
// layer has marked a file as deleted
name := strings.TrimPrefix(base, whiteoutPrefix)
removedPath := filepath.Join(dir, name)
log.Debugf("removing %s", removedPath)
err := os.RemoveAll(removedPath)
if err != nil {
return nil
}
continue
}
if hdr.Typeflag == tar.TypeBlock || hdr.Typeflag == tar.TypeChar {
// devices can't be created in a user namespace
log.Debugf("skipping device %s", hdr.Name)
continue
}
if hdr.Typeflag == tar.TypeSymlink {
log.Debugf("symlinking to %s", hdr.Linkname)
}
if hdr.Typeflag == tar.TypeLink {
log.Debugf("hardlinking to %s", hdr.Linkname)
}
if fi, err := os.Lstat(path); err == nil {
if fi.IsDir() && hdr.Name == "." {
continue
}
if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
log.Debugf("removing existing path")
if err := os.RemoveAll(path); err != nil {
return err
}
}
}
if err := tarfs.ExtractEntry(hdr, dest, tr, chown); err != nil {
log.Debugf("extracting")
return err
}
}
err = gr.Close()
if err != nil {
return err
}
err = r.Close()
if err != nil {
return err
}
bar.SetTotal(bar.Current(), true)
return nil
}