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

412 lines
10 KiB
Go

package client
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"github.com/hyperhq/hypercli/api/client/formatter"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/cheggaaa/pb"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hyper-api/types/filters"
"golang.org/x/net/context"
)
// CmdVolume is the parent subcommand for all volume commands
//
// Usage: docker volume <COMMAND> <OPTS>
func (cli *DockerCli) CmdVolume(args ...string) error {
description := Cli.DockerCommands["volume"].Description + "\n\nCommands:\n"
commands := [][]string{
{"create", "Create a volume"},
{"inspect", "Return low-level information on a volume"},
{"ls", "List volumes"},
{"init", "Initialize volumes"},
{"rm", "Remove a volume"},
}
for _, cmd := range commands {
description += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
}
description += "\nRun 'hyper volume COMMAND --help' for more information on a command"
cmd := Cli.Subcmd("volume", []string{"[COMMAND]"}, description, false)
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdVolumeLs outputs a list of Docker volumes.
//
// Usage: docker volume ls [OPTIONS]
func (cli *DockerCli) CmdVolumeLs(args ...string) error {
cmd := Cli.Subcmd("volume ls", nil, "List volumes", true)
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display volume names")
format := cmd.String([]string{"-format"}, "", "Pretty-print containers using a Go template")
flFilter := opts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'dangling=true')")
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
volFilterArgs := filters.NewArgs()
for _, f := range flFilter.GetAll() {
var err error
volFilterArgs, err = filters.ParseFlag(f, volFilterArgs)
if err != nil {
return err
}
}
volumes, err := cli.client.VolumeList(context.Background(), volFilterArgs)
if err != nil {
return err
}
/*
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet {
for _, warn := range volumes.Warnings {
fmt.Fprintln(cli.err, warn)
}
fmt.Fprintf(w, "DRIVER \tVOLUME NAME\tSIZE\tCONTAINER")
fmt.Fprintf(w, "\n")
}
for _, vol := range volumes.Volumes {
if *quiet {
fmt.Fprintln(w, vol.Name)
continue
}
var size, container string
if vol.Labels != nil {
size = vol.Labels["size"]
container = vol.Labels["container"]
if container != "" {
container = stringid.TruncateID(container)
}
}
fmt.Fprintf(w, "%s\t%s\t%s GB\t%s\n", vol.Driver, vol.Name, size, container)
}
w.Flush()
*/
f := *format
if len(f) == 0 {
if len(cli.VolumesFormat()) > 0 && !*quiet {
f = cli.VolumesFormat()
} else {
f = "table"
}
}
volCtx := formatter.VolumeContext{
Context: formatter.Context{
Output: cli.out,
Format: f,
Quiet: *quiet,
},
Volumes: volumes.Volumes,
}
volCtx.Write()
return nil
}
// CmdVolumeInspect displays low-level information on one or more volumes.
//
// Usage: docker volume inspect [OPTIONS] VOLUME [VOLUME...]
func (cli *DockerCli) CmdVolumeInspect(args ...string) error {
cmd := Cli.Subcmd("volume inspect", []string{"VOLUME [VOLUME...]"}, "Return low-level information on a volume", true)
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if err := cmd.Parse(args); err != nil {
return nil
}
ctx := context.Background()
inspectSearcher := func(name string) (interface{}, []byte, error) {
i, err := cli.client.VolumeInspect(ctx, name)
return i, nil, err
}
return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
}
// CmdVolumeCreate creates a new volume.
//
// Usage: docker volume create [OPTIONS]
func (cli *DockerCli) CmdVolumeCreate(args ...string) error {
cmd := Cli.Subcmd("volume create", nil, "Create a volume", true)
flDriver := cmd.String([]string{}, "hyper", "Specify volume driver name")
flName := cmd.String([]string{"-name"}, "", "Specify volume name")
flSnapshot := cmd.String([]string{"-snapshot"}, "", "Specify snapshot to create volume")
flSize := cmd.Int([]string{"-size"}, 10, "Specify volume size")
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
volReq := types.VolumeCreateRequest{
Driver: *flDriver,
DriverOpts: make(map[string]string),
Name: *flName,
}
volReq.DriverOpts["size"] = fmt.Sprintf("%d", *flSize)
if *flSnapshot != "" {
volReq.DriverOpts["snapshot"] = *flSnapshot
if *flSize == 10 {
volReq.DriverOpts["size"] = ""
}
}
vol, err := cli.client.VolumeCreate(context.Background(), volReq)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", vol.Name)
return nil
}
// CmdVolumeRm removes one or more volumes.
//
// Usage: docker volume rm VOLUME [VOLUME...]
func (cli *DockerCli) CmdVolumeRm(args ...string) error {
cmd := Cli.Subcmd("volume rm", []string{"VOLUME [VOLUME...]"}, "Remove a volume", true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var status = 0
ctx := context.Background()
for _, name := range cmd.Args() {
if err := cli.client.VolumeRemove(ctx, name); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
fmt.Fprintf(cli.out, "%s\n", name)
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
func validateVolumeSource(source string) error {
switch {
case strings.HasPrefix(source, "git://"):
fallthrough
case strings.HasPrefix(source, "http://"):
fallthrough
case strings.HasPrefix(source, "https://"):
break
case filepath.VolumeName(source) != "":
fallthrough
case strings.HasPrefix(source, "/"):
info, err := os.Stat(source)
if err != nil {
return err
}
if !info.Mode().IsDir() && !info.Mode().IsRegular() {
return fmt.Errorf("Unsupported local volume source(%s): %s", source, info.Mode().String())
}
break
default:
return fmt.Errorf("%s is not supported volume source", source)
}
return nil
}
func validateVolumeInitArgs(args []string, req *types.VolumesInitializeRequest) ([]int, error) {
var sourceType []int
for _, desc := range args {
idx := strings.LastIndexByte(desc, ':')
if idx == -1 || idx >= len(desc)-1 {
return nil, fmt.Errorf("%s does not match format SOURCE:VOLUME", desc)
}
source := desc[:idx]
name := desc[idx+1:]
if err := validateVolumeSource(source); err != nil {
return nil, err
}
pathType, source := convertToUnixPath(source)
req.Volume = append(req.Volume, types.VolumeInitDesc{
Name: name,
Source: source,
})
sourceType = append(sourceType, pathType)
}
return sourceType, nil
}
// CmdVolumeInit Initializes one or more volumes.
//
// Usage: docker volume init SOURCE:VOLUME [SOURCE:VOLUME...]
func (cli *DockerCli) CmdVolumeInit(args ...string) error {
cmd := Cli.Subcmd("volume init", []string{"SOURCE:VOLUME [SOURCE:VOLUME...]"}, "Initialize a volume", true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
return cli.initVolumes(cmd.Args(), false)
}
func (cli *DockerCli) initVolumes(vols []string, reload bool) error {
var req types.VolumesInitializeRequest
pathType, err := validateVolumeInitArgs(vols, &req)
if err != nil {
return err
}
ctx := context.Background()
req.Reload = reload
resp, err := cli.client.VolumeInitialize(ctx, req)
if err != nil {
return err
}
if len(resp.Session) == 0 {
return nil
}
// Upload local volumes
var wg sync.WaitGroup
var results []error
pool, err := pb.StartPool()
if err != nil {
// Ignore progress bar failures
fmt.Fprintf(cli.err, "Warning: do not show upload progress: %s\n", err.Error())
pool = nil
err = nil
}
for idx, desc := range req.Volume {
if url, ok := resp.Uploaders[desc.Name]; ok {
source := recoverPath(pathType[idx], desc.Source)
wg.Add(1)
go uploadLocalVolume(source, url, resp.Cookie, &results, &wg, pool)
}
}
wg.Wait()
if pool != nil {
pool.Stop()
}
for _, err = range results {
fmt.Fprintf(cli.err, "Upload local volume failed: %s\n", err.Error())
}
finishErr := cli.client.VolumeUploadFinish(ctx, resp.Session)
if err == nil {
err = finishErr
}
return err
}
func uploadLocalVolume(source, url, cookie string, results *[]error, wg *sync.WaitGroup, pool *pb.Pool) {
var (
resp io.ReadCloser
tar *TarFile
fullPath string
err error
)
defer func() {
if err != nil {
*results = append(*results, err)
}
wg.Done()
}()
fullPath, err = filepath.Abs(source)
if err != nil {
return
}
tar = NewTarFile(source, 512)
walkFunc := func(path string, info os.FileInfo, err error) error {
var relPath, linkName string
if err != nil {
return err
}
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
linkName, err = os.Readlink(path)
if err != nil {
return err
}
}
if path == fullPath {
if info.IsDir() {
// "." as indicator that it is a dir volume
relPath = "."
} else {
relPath = filepath.Base(path)
}
} else {
relPath, err = filepath.Rel(fullPath, path)
if err != nil {
return err
}
}
tar.AddFile(info, relPath, linkName, path)
return nil
}
err = filepath.Walk(fullPath, walkFunc)
if err != nil {
return
}
if pool != nil {
tar.AllocBar(pool)
}
resp, err = sendTarball(url, cookie, tar)
if err != nil {
return
}
defer resp.Close()
}
func sendTarball(uri, cookie string, input io.ReadCloser) (io.ReadCloser, error) {
req, err := http.NewRequest("POST", uri+"?cookie="+cookie, input)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-tar")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
defer resp.Body.Close()
buf := new(bytes.Buffer)
buf.ReadFrom(resp.Body)
if buf.Len() > 0 {
err = fmt.Errorf("%s: %s", http.StatusText(resp.StatusCode), buf.String())
} else {
err = fmt.Errorf("%s", http.StatusText(resp.StatusCode))
}
return nil, err
}
return resp.Body, nil
}