Files
virtual-kubelet/vendor/github.com/hyperhq/hypercli/api/client/load.go
2017-12-22 00:30:03 +08:00

324 lines
7.8 KiB
Go

package client
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/hyperhq/hyper-api/types"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/image"
"github.com/hyperhq/hypercli/pkg/archive"
"github.com/hyperhq/hypercli/pkg/jsonmessage"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/progress"
"github.com/hyperhq/hypercli/pkg/streamformatter"
"github.com/hyperhq/hypercli/pkg/symlink"
"golang.org/x/net/context"
)
const (
unsupported = "Unsupported image version"
)
type manifestItem struct {
Config string
RepoTags []string
Layers []string
}
type readCloser struct {
io.Reader
NeedClose io.ReadCloser
}
func (rc readCloser) Close() error {
return rc.NeedClose.Close()
}
func safePath(base, path string) (string, error) {
return symlink.FollowSymlinkInScope(filepath.Join(base, path), base)
}
func removeExistLayers(tmpDir string, existLayers []string, layerPaths []string) error {
keepLayerPaths := make(map[string]bool)
for _, _layerPath := range layerPaths[len(existLayers):] {
layerPath := filepath.Join(tmpDir, _layerPath)
info, err := os.Lstat(layerPath)
if err != nil {
return err
}
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
if _realPath, err := filepath.EvalSymlinks(layerPath); err == nil {
realPath := filepath.Join(filepath.Base(filepath.Dir(_realPath)), "layer.tar")
keepLayerPaths[realPath] = true
}
}
}
for idx := range existLayers {
layerPath := layerPaths[idx]
if _, ok := keepLayerPaths[layerPath]; !ok {
if err := os.Remove(filepath.Join(tmpDir, layerPath)); err != nil {
continue
}
}
}
return nil
}
func (cli *DockerCli) getExistLayers(ctx context.Context, tmpDir string) ([]string, []string, error) {
manifestPath, err := safePath(tmpDir, "manifest.json")
if err != nil {
return nil, nil, err
}
manifestFile, err := os.Open(manifestPath)
if err != nil {
return nil, nil, err
}
defer manifestFile.Close()
var manifest []manifestItem
if err := json.NewDecoder(manifestFile).Decode(&manifest); err != nil {
return nil, nil, err
}
allLayers := make([][]string, 0)
repoTags := make([][]string, 0)
layerPaths := make([]string, 0)
for _, m := range manifest {
configPath, err := safePath(tmpDir, m.Config)
if err != nil {
return nil, nil, err
}
config, err := ioutil.ReadFile(configPath)
if err != nil {
return nil, nil, err
}
img, err := image.NewFromJSON(config)
if err != nil {
return nil, nil, err
}
if expected, actual := len(m.Layers), len(img.RootFS.DiffIDs); expected != actual {
return nil, nil, errors.New(unsupported)
}
layerPaths = append(layerPaths, m.Layers...)
layers := make([]string, 0)
for _, diffID := range img.RootFS.DiffIDs {
layers = append(layers, string(diffID))
}
allLayers = append(allLayers, layers)
repoTags = append(repoTags, m.RepoTags)
}
diffRet, err := cli.client.ImageDiff(ctx, allLayers, repoTags)
if err != nil {
return nil, nil, err
}
return diffRet.ExistLayers, layerPaths, nil
}
func (cli *DockerCli) ImageLoadFromTar(ctx context.Context, tr io.Reader, quiet bool) (*types.ImageLoadResponse, error) {
tmpDir, err := ioutil.TempDir("", "hyper-pull-local-")
if err != nil {
return nil, err
}
defer os.RemoveAll(tmpDir)
if err := archive.Untar(tr, tmpDir, &archive.TarOptions{NoLchown: true}); err != nil {
return nil, err
}
if !quiet {
fmt.Fprintln(cli.out, "Diffing local image with remote image...")
}
existLayers, layerPaths, err := cli.getExistLayers(ctx, tmpDir)
if err != nil {
return nil, err
}
if err := removeExistLayers(tmpDir, existLayers, layerPaths); err != nil {
return nil, err
}
fs, err := archive.Tar(tmpDir, archive.Gzip)
if err != nil {
return nil, err
}
defer fs.Close()
hasNewLayers := len(existLayers) != len(layerPaths)
if hasNewLayers {
if !quiet {
fmt.Fprintln(cli.out, "Preparing to upload image...")
}
}
tarTmpDir, err := ioutil.TempDir("", "hyper-pull-local-")
if err != nil {
return nil, err
}
defer os.RemoveAll(tarTmpDir)
tarPath := filepath.Join(tarTmpDir, "image.tar")
tf, err := os.Create(tarPath)
if err != nil {
return nil, err
}
defer tf.Close()
_, err = io.Copy(tf, fs)
if err != nil {
return nil, err
}
os.RemoveAll(tmpDir)
info, err := tf.Stat()
if err != nil {
return nil, err
}
tf, err = os.Open(tarPath)
if err != nil {
return nil, err
}
resp, err := cli.client.ImageLoadLocal(ctx, quiet, info.Size())
if err != nil {
return nil, err
}
if !hasNewLayers || quiet {
go func() {
_, err := io.Copy(resp.Conn, tf)
if err != nil {
fmt.Fprintln(cli.out, err.Error())
resp.Conn.Close()
return
}
tf.Close()
}()
return &types.ImageLoadResponse{
Body: resp.Conn,
JSON: true,
}, nil
}
pr, pw := io.Pipe()
progressOutput := streamformatter.NewJSONStreamFormatter().NewProgressOutput(pw, false)
progressReader := progress.NewProgressReader(tf, progressOutput, info.Size(), "", "Uploading image")
go func() {
_, err := io.Copy(resp.Conn, progressReader)
if err != nil {
fmt.Fprintln(cli.out, err.Error())
resp.Conn.Close()
return
}
pw.CloseWithError(io.EOF)
}()
return &types.ImageLoadResponse{
Body: readCloser{io.MultiReader(pr, resp.Conn), resp.Conn},
JSON: true,
}, nil
}
// ImageDiff diff an image layers with local and imaged
func (cli *DockerCli) ImageLoadFromDaemon(ctx context.Context, name string, quiet bool) (*types.ImageLoadResponse, error) {
if !quiet {
fmt.Fprintln(cli.out, "Loading image from local docker daemon...")
}
tr, err := cli.client.ImageSaveTarFromDaemon(ctx, []string{name})
if err != nil {
return nil, err
}
defer tr.Close()
return cli.ImageLoadFromTar(ctx, tr, quiet)
}
// CmdLoad load a local image or a tar file
//
// The tar archive is read from STDIN by default, or from a tar archive file.
//
// Usage: docker load [OPTIONS]
func (cli *DockerCli) CmdLoad(args ...string) error {
cmd := Cli.Subcmd("load", nil, "Load a local image or a tar file", true)
local := cmd.String([]string{"l", "-local"}, "", "Read from a local image")
infile := cmd.String([]string{"i", "-input"}, "", "Read from a local or remote archive file compressed with gzip, bzip, or xz, instead of STDIN")
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Do not show load process")
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
*infile = strings.TrimSpace(*infile)
*local = strings.TrimSpace(*local)
var stdin io.Reader = cli.in
if *infile == "" && *local == "" && stdin == nil {
return errors.New("source image must be specified via --input, --local or STDIN")
}
var response *types.ImageLoadResponse
var err error
if *local != "" {
// Load from local docker daemon
response, err = cli.ImageLoadFromDaemon(context.Background(), *local, *quiet)
} else if *infile != "" {
if strings.HasPrefix(*infile, "http://") ||
strings.HasPrefix(*infile, "https://") ||
strings.HasPrefix(*infile, "ftp://") {
var input struct {
FromSrc string `json:"fromSrc"`
Quiet bool `json:"quiet"`
}
input.FromSrc = *infile
input.Quiet = *quiet
// Load from remote URL
response, err = cli.client.ImageLoad(context.Background(), input)
} else {
// Load from local tar
var af *os.File
af, err = os.Open(*infile)
if err != nil {
return err
}
defer af.Close()
response, err = cli.ImageLoadFromTar(context.Background(), af, *quiet)
}
} else if stdin != nil {
// Load from STDIN
response, err = cli.ImageLoadFromTar(context.Background(), stdin, *quiet)
}
if err != nil {
return err
}
if response == nil {
return nil
}
defer response.Body.Close()
if response.JSON {
return jsonmessage.DisplayJSONMessagesStream(response.Body, cli.out, cli.outFd, cli.isTerminalOut, nil)
}
_, err = io.Copy(cli.out, response.Body)
return err
}