unpack.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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. written := map[string]struct{}{}
  24. removed := map[string]struct{}{}
  25. chown := os.Getuid() == 0
  26. var out io.Writer
  27. if debug {
  28. out = ioutil.Discard
  29. } else {
  30. out = os.Stderr
  31. }
  32. progress := mpb.New(mpb.WithOutput(out))
  33. bars := make([]*mpb.Bar, len(layers))
  34. for i, layer := range layers {
  35. size, err := layer.Size()
  36. if err != nil {
  37. return err
  38. }
  39. digest, err := layer.Digest()
  40. if err != nil {
  41. return err
  42. }
  43. bars[i] = progress.AddBar(
  44. size,
  45. mpb.PrependDecorators(decor.Name(color.HiBlackString(digest.Hex[0:12]))),
  46. mpb.AppendDecorators(decor.CountersKibiByte("%.1f/%.1f")),
  47. )
  48. }
  49. // iterate over layers in reverse order; no need to write things files that
  50. // are modified by later layers anyway
  51. for i := len(layers) - 1; i >= 0; i-- {
  52. logrus.Debugf("extracting layer %d of %d", i+1, len(layers))
  53. err := extractLayer(dest, layers[i], bars[i], written, removed, chown)
  54. if err != nil {
  55. return err
  56. }
  57. }
  58. progress.Wait()
  59. return nil
  60. }
  61. func extractLayer(dest string, layer v1.Layer, bar *mpb.Bar, written, removed map[string]struct{}, chown bool) error {
  62. r, err := layer.Compressed()
  63. if err != nil {
  64. return err
  65. }
  66. gr, err := gzip.NewReader(bar.ProxyReader(r))
  67. if err != nil {
  68. return err
  69. }
  70. tr := tar.NewReader(gr)
  71. for {
  72. hdr, err := tr.Next()
  73. if err == io.EOF {
  74. break
  75. }
  76. if err != nil {
  77. return err
  78. }
  79. path := filepath.Join(dest, filepath.Clean(hdr.Name))
  80. base := filepath.Base(path)
  81. dir := filepath.Dir(path)
  82. logrus.Debugf("unpacking %s", hdr.Name)
  83. if strings.HasPrefix(base, whiteoutPrefix) {
  84. // layer has marked a file as deleted
  85. name := strings.TrimPrefix(base, whiteoutPrefix)
  86. removedPath := filepath.Join(dir, name)
  87. removed[removedPath] = struct{}{}
  88. logrus.Debugf("whiting out %s", removedPath)
  89. continue
  90. }
  91. if pathIsRemoved(path, removed) {
  92. // path has been removed by lower layer
  93. logrus.Debugf("skipping removed path %s", path)
  94. continue
  95. }
  96. if _, ok := written[path]; ok {
  97. // path has already been written by lower layer
  98. logrus.Debugf("skipping already-written file %s", path)
  99. continue
  100. }
  101. written[path] = struct{}{}
  102. if hdr.Typeflag == tar.TypeBlock || hdr.Typeflag == tar.TypeChar {
  103. // devices can't be created in a user namespace
  104. logrus.Debugf("skipping device %s", hdr.Name)
  105. continue
  106. }
  107. if err := tarfs.ExtractEntry(hdr, dest, tr, chown); err != nil {
  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. }
  121. func pathIsRemoved(path string, removed map[string]struct{}) bool {
  122. if _, ok := removed[path]; ok {
  123. return true
  124. }
  125. // check if parent dir has been removed
  126. for wd := range removed {
  127. if strings.HasPrefix(path, wd) {
  128. return true
  129. }
  130. }
  131. return false
  132. }