unpack.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. package main
  2. import (
  3. "archive/tar"
  4. "compress/gzip"
  5. "io"
  6. "io/ioutil"
  7. "os"
  8. "path/filepath"
  9. "strings"
  10. "github.com/concourse/go-archive/tarfs"
  11. "github.com/fatih/color"
  12. "github.com/google/go-containerregistry/pkg/v1"
  13. "github.com/sirupsen/logrus"
  14. "github.com/vbauerster/mpb"
  15. "github.com/vbauerster/mpb/decor"
  16. )
  17. const whiteoutPrefix = ".wh."
  18. func unpackImage(dest string, img v1.Image, debug bool) error {
  19. layers, err := img.Layers()
  20. if err != nil {
  21. return err
  22. }
  23. chown := os.Getuid() == 0
  24. var out io.Writer
  25. if debug {
  26. out = ioutil.Discard
  27. } else {
  28. out = os.Stderr
  29. }
  30. progress := mpb.New(mpb.WithOutput(out))
  31. bars := make([]*mpb.Bar, len(layers))
  32. for i, layer := range layers {
  33. size, err := layer.Size()
  34. if err != nil {
  35. return err
  36. }
  37. digest, err := layer.Digest()
  38. if err != nil {
  39. return err
  40. }
  41. bars[i] = progress.AddBar(
  42. size,
  43. mpb.PrependDecorators(decor.Name(color.HiBlackString(digest.Hex[0:12]))),
  44. mpb.AppendDecorators(decor.CountersKibiByte("%.1f/%.1f")),
  45. )
  46. }
  47. // iterate over layers in reverse order; no need to write things files that
  48. // are modified by later layers anyway
  49. for i, layer := range layers {
  50. logrus.Debugf("extracting layer %d of %d", i+1, len(layers))
  51. err := extractLayer(dest, layer, bars[i], chown)
  52. if err != nil {
  53. return err
  54. }
  55. }
  56. progress.Wait()
  57. return nil
  58. }
  59. func extractLayer(dest string, layer v1.Layer, bar *mpb.Bar, chown bool) error {
  60. r, err := layer.Compressed()
  61. if err != nil {
  62. return err
  63. }
  64. gr, err := gzip.NewReader(bar.ProxyReader(r))
  65. if err != nil {
  66. return err
  67. }
  68. tr := tar.NewReader(gr)
  69. for {
  70. hdr, err := tr.Next()
  71. if err == io.EOF {
  72. break
  73. }
  74. if err != nil {
  75. return err
  76. }
  77. path := filepath.Join(dest, filepath.Clean(hdr.Name))
  78. base := filepath.Base(path)
  79. dir := filepath.Dir(path)
  80. log := logrus.WithFields(logrus.Fields{
  81. "Name": hdr.Name,
  82. })
  83. log.Debug("unpacking")
  84. if strings.HasPrefix(base, whiteoutPrefix) {
  85. // layer has marked a file as deleted
  86. name := strings.TrimPrefix(base, whiteoutPrefix)
  87. removedPath := filepath.Join(dir, name)
  88. log.Debugf("removing %s", removedPath)
  89. err := os.RemoveAll(removedPath)
  90. if err != nil {
  91. return nil
  92. }
  93. continue
  94. }
  95. if hdr.Typeflag == tar.TypeBlock || hdr.Typeflag == tar.TypeChar {
  96. // devices can't be created in a user namespace
  97. log.Debugf("skipping device %s", hdr.Name)
  98. continue
  99. }
  100. if hdr.Typeflag == tar.TypeSymlink {
  101. log.Warnf("symlinking to %s", hdr.Linkname)
  102. }
  103. if hdr.Typeflag == tar.TypeLink {
  104. log.Warnf("hardlinking to %s", hdr.Linkname)
  105. }
  106. if err := tarfs.ExtractEntry(hdr, dest, tr, chown); err != nil {
  107. log.Infof("extracting")
  108. return err
  109. }
  110. }
  111. err = gr.Close()
  112. if err != nil {
  113. return err
  114. }
  115. err = r.Close()
  116. if err != nil {
  117. return err
  118. }
  119. return nil
  120. }