unpack.go 2.8 KB

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