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  }