blob: 7e104fe76c49a917f28fda7b7fda1557f113cdb8 [file] [log] [blame]
package load // import "github.com/docker/docker/integration-cli/fixtures/load"
import (
"bufio"
"bytes"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/term"
"github.com/pkg/errors"
)
const frozenImgDir = "/docker-frozen-images"
// FrozenImagesLinux loads the frozen image set for the integration suite
// If the images are not available locally it will download them
// TODO: This loads whatever is in the frozen image dir, regardless of what
// images were passed in. If the images need to be downloaded, then it will respect
// the passed in images
func FrozenImagesLinux(client client.APIClient, images ...string) error {
var loadImages []struct{ srcName, destName string }
for _, img := range images {
if !imageExists(client, img) {
srcName := img
// hello-world:latest gets re-tagged as hello-world:frozen
// there are some tests that use hello-world:latest specifically so it pulls
// the image and hello-world:frozen is used for when we just want a super
// small image
if img == "hello-world:frozen" {
srcName = "hello-world:latest"
}
if img == "busybox:1.27-glibc" {
img = "busybox:latest"
}
loadImages = append(loadImages, struct{ srcName, destName string }{
srcName: srcName,
destName: img,
})
}
}
if len(loadImages) == 0 {
// everything is loaded, we're done
return nil
}
ctx := context.Background()
fi, err := os.Stat(frozenImgDir)
if err != nil || !fi.IsDir() {
srcImages := make([]string, 0, len(loadImages))
for _, img := range loadImages {
srcImages = append(srcImages, img.srcName)
}
if err := pullImages(ctx, client, srcImages); err != nil {
return errors.Wrap(err, "error pulling image list")
}
} else {
if err := loadFrozenImages(ctx, client); err != nil {
return err
}
}
for _, img := range loadImages {
if img.srcName != img.destName {
if err := client.ImageTag(ctx, img.srcName, img.destName); err != nil {
return errors.Wrapf(err, "failed to tag %s as %s", img.srcName, img.destName)
}
if _, err := client.ImageRemove(ctx, img.srcName, types.ImageRemoveOptions{}); err != nil {
return errors.Wrapf(err, "failed to remove %s", img.srcName)
}
}
}
return nil
}
func imageExists(client client.APIClient, name string) bool {
_, _, err := client.ImageInspectWithRaw(context.Background(), name)
return err == nil
}
func loadFrozenImages(ctx context.Context, client client.APIClient) error {
tar, err := exec.LookPath("tar")
if err != nil {
return errors.Wrap(err, "could not find tar binary")
}
tarCmd := exec.Command(tar, "-cC", frozenImgDir, ".")
out, err := tarCmd.StdoutPipe()
if err != nil {
return errors.Wrap(err, "error getting stdout pipe for tar command")
}
errBuf := bytes.NewBuffer(nil)
tarCmd.Stderr = errBuf
tarCmd.Start()
defer tarCmd.Wait()
resp, err := client.ImageLoad(ctx, out, true)
if err != nil {
return errors.Wrap(err, "failed to load frozen images")
}
defer resp.Body.Close()
fd, isTerminal := term.GetFdInfo(os.Stdout)
return jsonmessage.DisplayJSONMessagesStream(resp.Body, os.Stdout, fd, isTerminal, nil)
}
func pullImages(ctx context.Context, client client.APIClient, images []string) error {
cwd, err := os.Getwd()
if err != nil {
return errors.Wrap(err, "error getting path to dockerfile")
}
dockerfile := os.Getenv("DOCKERFILE")
if dockerfile == "" {
dockerfile = "Dockerfile"
}
dockerfilePath := filepath.Join(filepath.Dir(filepath.Clean(cwd)), dockerfile)
pullRefs, err := readFrozenImageList(dockerfilePath, images)
if err != nil {
return errors.Wrap(err, "error reading frozen image list")
}
var wg sync.WaitGroup
chErr := make(chan error, len(images))
for tag, ref := range pullRefs {
wg.Add(1)
go func(tag, ref string) {
defer wg.Done()
if err := pullTagAndRemove(ctx, client, ref, tag); err != nil {
chErr <- err
return
}
}(tag, ref)
}
wg.Wait()
close(chErr)
return <-chErr
}
func pullTagAndRemove(ctx context.Context, client client.APIClient, ref string, tag string) error {
resp, err := client.ImagePull(ctx, ref, types.ImagePullOptions{})
if err != nil {
return errors.Wrapf(err, "failed to pull %s", ref)
}
defer resp.Close()
fd, isTerminal := term.GetFdInfo(os.Stdout)
if err := jsonmessage.DisplayJSONMessagesStream(resp, os.Stdout, fd, isTerminal, nil); err != nil {
return err
}
if err := client.ImageTag(ctx, ref, tag); err != nil {
return errors.Wrapf(err, "failed to tag %s as %s", ref, tag)
}
_, err = client.ImageRemove(ctx, ref, types.ImageRemoveOptions{})
return errors.Wrapf(err, "failed to remove %s", ref)
}
func readFrozenImageList(dockerfilePath string, images []string) (map[string]string, error) {
f, err := os.Open(dockerfilePath)
if err != nil {
return nil, errors.Wrap(err, "error reading dockerfile")
}
defer f.Close()
ls := make(map[string]string)
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := strings.Fields(scanner.Text())
if len(line) < 3 {
continue
}
if !(line[0] == "RUN" && line[1] == "./contrib/download-frozen-image-v2.sh") {
continue
}
for scanner.Scan() {
img := strings.TrimSpace(scanner.Text())
img = strings.TrimSuffix(img, "\\")
img = strings.TrimSpace(img)
split := strings.Split(img, "@")
if len(split) < 2 {
break
}
for _, i := range images {
if split[0] == i {
ls[i] = img
break
}
}
}
}
return ls, nil
}