github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/testutil/fixtures/load/frozen.go (about)

     1  package load // import "github.com/demonoid81/moby/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/demonoid81/moby/api/types"
    14  	"github.com/demonoid81/moby/client"
    15  	"github.com/demonoid81/moby/pkg/jsonmessage"
    16  	"github.com/moby/term"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  const frozenImgDir = "/docker-frozen-images"
    21  
    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(client client.APIClient, images ...string) error {
    28  	var loadImages []struct{ srcName, destName string }
    29  	for _, img := range images {
    30  		if !imageExists(client, img) {
    31  			srcName := img
    32  			// hello-world:latest gets re-tagged as hello-world:frozen
    33  			// there are some tests that use hello-world:latest specifically so it pulls
    34  			// the image and hello-world:frozen is used for when we just want a super
    35  			// small image
    36  			if img == "hello-world:frozen" {
    37  				srcName = "hello-world:latest"
    38  			}
    39  			loadImages = append(loadImages, struct{ srcName, destName string }{
    40  				srcName:  srcName,
    41  				destName: img,
    42  			})
    43  		}
    44  	}
    45  	if len(loadImages) == 0 {
    46  		// everything is loaded, we're done
    47  		return nil
    48  	}
    49  
    50  	ctx := context.Background()
    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  
    66  	for _, img := range loadImages {
    67  		if img.srcName != img.destName {
    68  			if err := client.ImageTag(ctx, img.srcName, img.destName); err != nil {
    69  				return errors.Wrapf(err, "failed to tag %s as %s", img.srcName, img.destName)
    70  			}
    71  			if _, err := client.ImageRemove(ctx, img.srcName, types.ImageRemoveOptions{}); err != nil {
    72  				return errors.Wrapf(err, "failed to remove %s", img.srcName)
    73  			}
    74  		}
    75  	}
    76  	return nil
    77  }
    78  
    79  func imageExists(client client.APIClient, name string) bool {
    80  	_, _, err := client.ImageInspectWithRaw(context.Background(), name)
    81  	return err == nil
    82  }
    83  
    84  func loadFrozenImages(ctx context.Context, client client.APIClient) error {
    85  	tar, err := exec.LookPath("tar")
    86  	if err != nil {
    87  		return errors.Wrap(err, "could not find tar binary")
    88  	}
    89  	tarCmd := exec.Command(tar, "-cC", frozenImgDir, ".")
    90  	out, err := tarCmd.StdoutPipe()
    91  	if err != nil {
    92  		return errors.Wrap(err, "error getting stdout pipe for tar command")
    93  	}
    94  
    95  	errBuf := bytes.NewBuffer(nil)
    96  	tarCmd.Stderr = errBuf
    97  	tarCmd.Start()
    98  	defer tarCmd.Wait()
    99  
   100  	resp, err := client.ImageLoad(ctx, out, true)
   101  	if err != nil {
   102  		return errors.Wrap(err, "failed to load frozen images")
   103  	}
   104  	defer resp.Body.Close()
   105  	fd, isTerminal := term.GetFdInfo(os.Stdout)
   106  	return jsonmessage.DisplayJSONMessagesStream(resp.Body, os.Stdout, fd, isTerminal, nil)
   107  }
   108  
   109  func pullImages(ctx context.Context, client client.APIClient, images []string) error {
   110  	cwd, err := os.Getwd()
   111  	if err != nil {
   112  		return errors.Wrap(err, "error getting path to dockerfile")
   113  	}
   114  	dockerfile := os.Getenv("DOCKERFILE")
   115  	if dockerfile == "" {
   116  		dockerfile = "Dockerfile"
   117  	}
   118  	dockerfilePath := filepath.Join(filepath.Dir(filepath.Clean(cwd)), dockerfile)
   119  	pullRefs, err := readFrozenImageList(dockerfilePath, images)
   120  	if err != nil {
   121  		return errors.Wrap(err, "error reading frozen image list")
   122  	}
   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  
   141  func pullTagAndRemove(ctx context.Context, client client.APIClient, ref string, tag string) error {
   142  	resp, err := client.ImagePull(ctx, ref, types.ImagePullOptions{})
   143  	if err != nil {
   144  		return errors.Wrapf(err, "failed to pull %s", ref)
   145  	}
   146  	defer resp.Close()
   147  	fd, isTerminal := term.GetFdInfo(os.Stdout)
   148  	if err := jsonmessage.DisplayJSONMessagesStream(resp, os.Stdout, fd, isTerminal, nil); err != nil {
   149  		return err
   150  	}
   151  
   152  	if err := client.ImageTag(ctx, ref, tag); err != nil {
   153  		return errors.Wrapf(err, "failed to tag %s as %s", ref, tag)
   154  	}
   155  	_, err = client.ImageRemove(ctx, ref, types.ImageRemoveOptions{})
   156  	return errors.Wrapf(err, "failed to remove %s", ref)
   157  
   158  }
   159  
   160  func readFrozenImageList(dockerfilePath string, images []string) (map[string]string, error) {
   161  	f, err := os.Open(dockerfilePath)
   162  	if err != nil {
   163  		return nil, errors.Wrap(err, "error reading dockerfile")
   164  	}
   165  	defer f.Close()
   166  	ls := make(map[string]string)
   167  
   168  	scanner := bufio.NewScanner(f)
   169  	for scanner.Scan() {
   170  		line := strings.Fields(scanner.Text())
   171  		if len(line) < 3 {
   172  			continue
   173  		}
   174  		if !(line[0] == "RUN" && line[1] == "./contrib/download-frozen-image-v2.sh") {
   175  			continue
   176  		}
   177  
   178  		for scanner.Scan() {
   179  			img := strings.TrimSpace(scanner.Text())
   180  			img = strings.TrimSuffix(img, "\\")
   181  			img = strings.TrimSpace(img)
   182  			split := strings.Split(img, "@")
   183  			if len(split) < 2 {
   184  				break
   185  			}
   186  
   187  			for _, i := range images {
   188  				if split[0] == i {
   189  					ls[i] = img
   190  					break
   191  				}
   192  			}
   193  		}
   194  	}
   195  	return ls, nil
   196  }