frozen.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. package load // import "github.com/docker/docker/testutil/fixtures/load"
  2. import (
  3. "bufio"
  4. "bytes"
  5. "context"
  6. "os"
  7. "os/exec"
  8. "path/filepath"
  9. "strings"
  10. "sync"
  11. "github.com/docker/docker/api/types"
  12. "github.com/docker/docker/client"
  13. "github.com/docker/docker/pkg/jsonmessage"
  14. "github.com/moby/term"
  15. "github.com/pkg/errors"
  16. "go.opentelemetry.io/otel"
  17. "go.opentelemetry.io/otel/attribute"
  18. "go.opentelemetry.io/otel/codes"
  19. "go.opentelemetry.io/otel/trace"
  20. )
  21. const frozenImgDir = "/docker-frozen-images"
  22. // FrozenImagesLinux loads the frozen image set for the integration suite
  23. // If the images are not available locally it will download them
  24. // TODO: This loads whatever is in the frozen image dir, regardless of what
  25. // images were passed in. If the images need to be downloaded, then it will respect
  26. // the passed in images
  27. func FrozenImagesLinux(ctx context.Context, client client.APIClient, images ...string) error {
  28. ctx, span := otel.Tracer("").Start(ctx, "LoadFrozenImages")
  29. defer span.End()
  30. var loadImages []struct{ srcName, destName string }
  31. for _, img := range images {
  32. if !imageExists(ctx, client, img) {
  33. srcName := img
  34. // hello-world:latest gets re-tagged as hello-world:frozen
  35. // there are some tests that use hello-world:latest specifically so it pulls
  36. // the image and hello-world:frozen is used for when we just want a super
  37. // small image
  38. if img == "hello-world:frozen" {
  39. srcName = "hello-world:latest"
  40. }
  41. loadImages = append(loadImages, struct{ srcName, destName string }{
  42. srcName: srcName,
  43. destName: img,
  44. })
  45. }
  46. }
  47. if len(loadImages) == 0 {
  48. // everything is loaded, we're done
  49. return nil
  50. }
  51. fi, err := os.Stat(frozenImgDir)
  52. if err != nil || !fi.IsDir() {
  53. srcImages := make([]string, 0, len(loadImages))
  54. for _, img := range loadImages {
  55. srcImages = append(srcImages, img.srcName)
  56. }
  57. if err := pullImages(ctx, client, srcImages); err != nil {
  58. return errors.Wrap(err, "error pulling image list")
  59. }
  60. } else {
  61. if err := loadFrozenImages(ctx, client); err != nil {
  62. return err
  63. }
  64. }
  65. for _, img := range loadImages {
  66. if img.srcName != img.destName {
  67. if err := client.ImageTag(ctx, img.srcName, img.destName); err != nil {
  68. return errors.Wrapf(err, "failed to tag %s as %s", img.srcName, img.destName)
  69. }
  70. if _, err := client.ImageRemove(ctx, img.srcName, types.ImageRemoveOptions{}); err != nil {
  71. return errors.Wrapf(err, "failed to remove %s", img.srcName)
  72. }
  73. }
  74. }
  75. return nil
  76. }
  77. func imageExists(ctx context.Context, client client.APIClient, name string) bool {
  78. ctx, span := otel.Tracer("").Start(ctx, "check image exists: "+name)
  79. defer span.End()
  80. _, _, err := client.ImageInspectWithRaw(ctx, name)
  81. if err != nil {
  82. span.RecordError(err)
  83. }
  84. return err == nil
  85. }
  86. func loadFrozenImages(ctx context.Context, client client.APIClient) error {
  87. ctx, span := otel.Tracer("").Start(ctx, "load frozen images")
  88. defer span.End()
  89. tar, err := exec.LookPath("tar")
  90. if err != nil {
  91. return errors.Wrap(err, "could not find tar binary")
  92. }
  93. tarCmd := exec.Command(tar, "-cC", frozenImgDir, ".")
  94. out, err := tarCmd.StdoutPipe()
  95. if err != nil {
  96. return errors.Wrap(err, "error getting stdout pipe for tar command")
  97. }
  98. errBuf := bytes.NewBuffer(nil)
  99. tarCmd.Stderr = errBuf
  100. tarCmd.Start()
  101. defer tarCmd.Wait()
  102. resp, err := client.ImageLoad(ctx, out, true)
  103. if err != nil {
  104. return errors.Wrap(err, "failed to load frozen images")
  105. }
  106. defer resp.Body.Close()
  107. fd, isTerminal := term.GetFdInfo(os.Stdout)
  108. return jsonmessage.DisplayJSONMessagesStream(resp.Body, os.Stdout, fd, isTerminal, nil)
  109. }
  110. func pullImages(ctx context.Context, client client.APIClient, images []string) error {
  111. cwd, err := os.Getwd()
  112. if err != nil {
  113. return errors.Wrap(err, "error getting path to dockerfile")
  114. }
  115. dockerfile := os.Getenv("DOCKERFILE")
  116. if dockerfile == "" {
  117. dockerfile = "Dockerfile"
  118. }
  119. dockerfilePath := filepath.Join(filepath.Dir(filepath.Clean(cwd)), dockerfile)
  120. pullRefs, err := readFrozenImageList(ctx, dockerfilePath, images)
  121. if err != nil {
  122. return errors.Wrap(err, "error reading frozen image list")
  123. }
  124. var wg sync.WaitGroup
  125. chErr := make(chan error, len(images))
  126. for tag, ref := range pullRefs {
  127. wg.Add(1)
  128. go func(tag, ref string) {
  129. defer wg.Done()
  130. if err := pullTagAndRemove(ctx, client, ref, tag); err != nil {
  131. chErr <- err
  132. return
  133. }
  134. }(tag, ref)
  135. }
  136. wg.Wait()
  137. close(chErr)
  138. return <-chErr
  139. }
  140. func pullTagAndRemove(ctx context.Context, client client.APIClient, ref string, tag string) (retErr error) {
  141. ctx, span := otel.Tracer("").Start(ctx, "pull image: "+ref+" with tag: "+tag)
  142. defer func() {
  143. if retErr != nil {
  144. // An error here is a real error for the span, so set the span status
  145. span.SetStatus(codes.Error, retErr.Error())
  146. }
  147. span.End()
  148. }()
  149. resp, err := client.ImagePull(ctx, ref, types.ImagePullOptions{})
  150. if err != nil {
  151. return errors.Wrapf(err, "failed to pull %s", ref)
  152. }
  153. defer resp.Close()
  154. fd, isTerminal := term.GetFdInfo(os.Stdout)
  155. if err := jsonmessage.DisplayJSONMessagesStream(resp, os.Stdout, fd, isTerminal, nil); err != nil {
  156. return err
  157. }
  158. if err := client.ImageTag(ctx, ref, tag); err != nil {
  159. return errors.Wrapf(err, "failed to tag %s as %s", ref, tag)
  160. }
  161. _, err = client.ImageRemove(ctx, ref, types.ImageRemoveOptions{})
  162. return errors.Wrapf(err, "failed to remove %s", ref)
  163. }
  164. func readFrozenImageList(ctx context.Context, dockerfilePath string, images []string) (map[string]string, error) {
  165. f, err := os.Open(dockerfilePath)
  166. if err != nil {
  167. return nil, errors.Wrap(err, "error reading dockerfile")
  168. }
  169. defer f.Close()
  170. ls := make(map[string]string)
  171. span := trace.SpanFromContext(ctx)
  172. scanner := bufio.NewScanner(f)
  173. for scanner.Scan() {
  174. line := strings.Fields(scanner.Text())
  175. if len(line) < 3 {
  176. continue
  177. }
  178. if !(line[0] == "RUN" && line[1] == "./contrib/download-frozen-image-v2.sh") {
  179. continue
  180. }
  181. for scanner.Scan() {
  182. img := strings.TrimSpace(scanner.Text())
  183. img = strings.TrimSuffix(img, "\\")
  184. img = strings.TrimSpace(img)
  185. split := strings.Split(img, "@")
  186. if len(split) < 2 {
  187. break
  188. }
  189. for _, i := range images {
  190. if split[0] == i {
  191. ls[i] = img
  192. if span.IsRecording() {
  193. span.AddEvent("found frozen image", trace.WithAttributes(attribute.String("image", i)))
  194. }
  195. break
  196. }
  197. }
  198. }
  199. }
  200. return ls, nil
  201. }