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