Initial commit

This commit is contained in:
Ria Bhatia
2017-12-04 13:32:57 -06:00
committed by Erik St. Martin
commit 0075e5b0f3
9056 changed files with 2523100 additions and 0 deletions

101
vendor/github.com/hyperhq/hypercli/api/client/attach.go generated vendored Normal file
View File

@@ -0,0 +1,101 @@
package client
import (
"fmt"
"io"
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
"github.com/docker/engine-api/types"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/signal"
)
// CmdAttach attaches to a running container.
//
// Usage: docker attach [OPTIONS] CONTAINER
func (cli *DockerCli) CmdAttach(args ...string) error {
cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, Cli.DockerCommands["attach"].Description, true)
noStdin := cmd.Bool([]string{"-no-stdin"}, false, "Do not attach STDIN")
proxy := cmd.Bool([]string{}, true, "Proxy all received signals to the process")
detachKeys := cmd.String([]string{}, "", "Override the key sequence for detaching a container")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
containerID := cmd.Arg(0)
c, err := cli.client.ContainerInspect(ctx, containerID)
if err != nil {
return err
}
if !c.State.Running {
return fmt.Errorf("You cannot attach to a stopped container, start it first")
}
if c.State.Paused {
return fmt.Errorf("You cannot attach to a paused container, unpause it first")
}
if err := cli.CheckTtyInput(!*noStdin, c.Config.Tty); err != nil {
return err
}
if c.Config.Tty && cli.isTerminalOut {
if err := cli.monitorTtySize(ctx, cmd.Arg(0), false); err != nil {
logrus.Debugf("Error monitoring TTY size: %s", err)
}
}
if *detachKeys != "" {
cli.configFile.DetachKeys = *detachKeys
}
options := types.ContainerAttachOptions{
Stream: true,
Stdin: !*noStdin && c.Config.OpenStdin,
Stdout: true,
Stderr: true,
DetachKeys: cli.configFile.DetachKeys,
}
var in io.ReadCloser
if options.Stdin {
in = cli.in
}
if *proxy && !c.Config.Tty {
sigc := cli.forwardAllSignals(ctx, containerID)
defer signal.StopCatch(sigc)
}
resp, err := cli.client.ContainerAttach(ctx, containerID, options)
if err != nil {
return err
}
defer resp.Close()
if in != nil && c.Config.Tty {
if err := cli.setRawTerminal(); err != nil {
return err
}
defer cli.restoreTerminal(in)
}
if err := cli.holdHijackedConnection(c.Config.Tty, in, cli.out, cli.err, resp); err != nil {
return err
}
_, status, err := getExitCode(ctx, cli, containerID)
if err != nil {
return err
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}

651
vendor/github.com/hyperhq/hypercli/api/client/build.go generated vendored Normal file
View File

@@ -0,0 +1,651 @@
package client
import (
"archive/tar"
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"github.com/docker/engine-api/types"
"github.com/docker/go-units"
"github.com/hyperhq/hypercli/api"
"github.com/hyperhq/hypercli/builder/dockerignore"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
"github.com/hyperhq/hypercli/pkg/archive"
"github.com/hyperhq/hypercli/pkg/fileutils"
"github.com/hyperhq/hypercli/pkg/gitutils"
"github.com/hyperhq/hypercli/pkg/httputils"
"github.com/hyperhq/hypercli/pkg/ioutils"
"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/urlutil"
"github.com/hyperhq/hypercli/reference"
runconfigopts "github.com/hyperhq/hypercli/runconfig/opts"
"golang.org/x/net/context"
)
type translatorFunc func(context.Context, reference.NamedTagged) (reference.Canonical, error)
// CmdBuild builds a new image from the source code at a given path.
//
// If '-' is provided instead of a path or URL, Docker will build an image from either a Dockerfile or tar archive read from STDIN.
//
// Usage: docker build [OPTIONS] PATH | URL | -
func (cli *DockerCli) Build(args ...string) error {
cmd := Cli.Subcmd("build", []string{"PATH | URL | -"}, Cli.DockerCommands["build"].Description, true)
flTags := opts.NewListOpts(validateTag)
cmd.Var(&flTags, []string{"t", "-tag"}, "Name and optionally a tag in the 'name:tag' format")
suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the build output and print image ID on success")
noCache := cmd.Bool([]string{"-no-cache"}, false, "Do not use cache when building the image")
rm := cmd.Bool([]string{"-rm"}, true, "Remove intermediate containers after a successful build")
forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers")
pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image")
dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
flShmSize := cmd.String([]string{"-shm-size"}, "", "Size of /dev/shm, default value is 64MB")
flCPUShares := cmd.Int64([]string{"#c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
flCPUPeriod := cmd.Int64([]string{"-cpu-period"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
flCPUQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
flBuildArg := opts.NewListOpts(runconfigopts.ValidateEnv)
cmd.Var(&flBuildArg, []string{"-build-arg"}, "Set build-time variables")
ulimits := make(map[string]*units.Ulimit)
flUlimits := runconfigopts.NewUlimitOpt(&ulimits)
cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
cmd.Require(flag.Exact, 1)
// For trusted pull on "FROM <image>" instruction.
addTrustedFlags(cmd, true)
cmd.ParseFlags(args, true)
var (
buildCtx io.ReadCloser
err error
)
specifiedContext := cmd.Arg(0)
var (
contextDir string
tempDir string
relDockerfile string
progBuff io.Writer
buildBuff io.Writer
)
progBuff = cli.out
buildBuff = cli.out
if *suppressOutput {
progBuff = bytes.NewBuffer(nil)
buildBuff = bytes.NewBuffer(nil)
}
switch {
case specifiedContext == "-":
buildCtx, relDockerfile, err = getContextFromReader(cli.in, *dockerfileName)
case urlutil.IsGitURL(specifiedContext):
tempDir, relDockerfile, err = getContextFromGitURL(specifiedContext, *dockerfileName)
case urlutil.IsURL(specifiedContext):
buildCtx, relDockerfile, err = getContextFromURL(progBuff, specifiedContext, *dockerfileName)
default:
contextDir, relDockerfile, err = getContextFromLocalDir(specifiedContext, *dockerfileName)
}
if err != nil {
if *suppressOutput && urlutil.IsURL(specifiedContext) {
fmt.Fprintln(cli.err, progBuff)
}
return fmt.Errorf("unable to prepare context: %s", err)
}
if tempDir != "" {
defer os.RemoveAll(tempDir)
contextDir = tempDir
}
if buildCtx == nil {
// And canonicalize dockerfile name to a platform-independent one
relDockerfile, err = archive.CanonicalTarNameForPath(relDockerfile)
if err != nil {
return fmt.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err)
}
f, err := os.Open(filepath.Join(contextDir, ".dockerignore"))
if err != nil && !os.IsNotExist(err) {
return err
}
var excludes []string
if err == nil {
excludes, err = dockerignore.ReadAll(f)
if err != nil {
return err
}
}
if err := validateContextDirectory(contextDir, excludes); err != nil {
return fmt.Errorf("Error checking context: '%s'.", err)
}
// If .dockerignore mentions .dockerignore or the Dockerfile
// then make sure we send both files over to the daemon
// because Dockerfile is, obviously, needed no matter what, and
// .dockerignore is needed to know if either one needs to be
// removed. The daemon will remove them for us, if needed, after it
// parses the Dockerfile. Ignore errors here, as they will have been
// caught by validateContextDirectory above.
var includes = []string{"."}
keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
keepThem2, _ := fileutils.Matches(relDockerfile, excludes)
if keepThem1 || keepThem2 {
includes = append(includes, ".dockerignore", relDockerfile)
}
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
Compression: archive.Uncompressed,
ExcludePatterns: excludes,
IncludeFiles: includes,
})
if err != nil {
return err
}
}
ctx := context.Background()
var resolvedTags []*resolvedTag
if isTrusted() {
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
// Dockerfile which uses trusted pulls.
buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, cli.trustedReference, &resolvedTags)
}
// Setup an upload progress bar
progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true)
var body io.Reader = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
var memory int64
if *flMemoryString != "" {
parsedMemory, err := units.RAMInBytes(*flMemoryString)
if err != nil {
return err
}
memory = parsedMemory
}
var memorySwap int64
if *flMemorySwap != "" {
if *flMemorySwap == "-1" {
memorySwap = -1
} else {
parsedMemorySwap, err := units.RAMInBytes(*flMemorySwap)
if err != nil {
return err
}
memorySwap = parsedMemorySwap
}
}
var shmSize int64
if *flShmSize != "" {
shmSize, err = units.RAMInBytes(*flShmSize)
if err != nil {
return err
}
}
options := types.ImageBuildOptions{
Memory: memory,
MemorySwap: memorySwap,
Tags: flTags.GetAll(),
SuppressOutput: *suppressOutput,
NoCache: *noCache,
Remove: *rm,
ForceRemove: *forceRm,
PullParent: *pull,
CPUSetCPUs: *flCPUSetCpus,
CPUSetMems: *flCPUSetMems,
CPUShares: *flCPUShares,
CPUQuota: *flCPUQuota,
CPUPeriod: *flCPUPeriod,
CgroupParent: *flCgroupParent,
Dockerfile: relDockerfile,
ShmSize: shmSize,
Ulimits: flUlimits.GetList(),
BuildArgs: runconfigopts.ConvertKVStringsToMap(flBuildArg.GetAll()),
AuthConfigs: cli.configFile.AuthConfigs,
}
response, err := cli.client.ImageBuild(ctx, body, options)
if err != nil {
return err
}
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, cli.outFd, cli.isTerminalOut, nil)
if err != nil {
if jerr, ok := err.(*jsonmessage.JSONError); ok {
// If no error code is set, default to 1
if jerr.Code == 0 {
jerr.Code = 1
}
if *suppressOutput {
fmt.Fprintf(cli.err, "%s%s", progBuff, buildBuff)
}
return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
}
}
// Windows: show error message about modified file permissions if the
// daemon isn't running Windows.
if response.OSType != "windows" && runtime.GOOS == "windows" {
fmt.Fprintln(cli.err, `SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`)
}
// Everything worked so if -q was provided the output from the daemon
// should be just the image ID and we'll print that to stdout.
if *suppressOutput {
fmt.Fprintf(cli.out, "%s", buildBuff)
}
if isTrusted() {
// Since the build was successful, now we must tag any of the resolved
// images from the above Dockerfile rewrite.
for _, resolved := range resolvedTags {
if err := cli.tagTrusted(ctx, resolved.digestRef, resolved.tagRef); err != nil {
return err
}
}
}
return nil
}
// validateContextDirectory checks if all the contents of the directory
// can be read and returns an error if some files can't be read
// symlinks which point to non-existing files don't trigger an error
func validateContextDirectory(srcPath string, excludes []string) error {
contextRoot, err := getContextRoot(srcPath)
if err != nil {
return err
}
return filepath.Walk(contextRoot, func(filePath string, f os.FileInfo, err error) error {
// skip this directory/file if it's not in the path, it won't get added to the context
if relFilePath, err := filepath.Rel(contextRoot, filePath); err != nil {
return err
} else if skip, err := fileutils.Matches(relFilePath, excludes); err != nil {
return err
} else if skip {
if f.IsDir() {
return filepath.SkipDir
}
return nil
}
if err != nil {
if os.IsPermission(err) {
return fmt.Errorf("can't stat '%s'", filePath)
}
if os.IsNotExist(err) {
return nil
}
return err
}
// skip checking if symlinks point to non-existing files, such symlinks can be useful
// also skip named pipes, because they hanging on open
if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 {
return nil
}
if !f.IsDir() {
currentFile, err := os.Open(filePath)
if err != nil && os.IsPermission(err) {
return fmt.Errorf("no permission to read from '%s'", filePath)
}
currentFile.Close()
}
return nil
})
}
// validateTag checks if the given image name can be resolved.
func validateTag(rawRepo string) (string, error) {
_, err := reference.ParseNamed(rawRepo)
if err != nil {
return "", err
}
return rawRepo, nil
}
// isUNC returns true if the path is UNC (one starting \\). It always returns
// false on Linux.
func isUNC(path string) bool {
return runtime.GOOS == "windows" && strings.HasPrefix(path, `\\`)
}
// getDockerfileRelPath uses the given context directory for a `docker build`
// and returns the absolute path to the context directory, the relative path of
// the dockerfile in that context directory, and a non-nil error on success.
func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDir, relDockerfile string, err error) {
if absContextDir, err = filepath.Abs(givenContextDir); err != nil {
return "", "", fmt.Errorf("unable to get absolute context directory: %v", err)
}
// The context dir might be a symbolic link, so follow it to the actual
// target directory.
//
// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
// paths (those starting with \\). This hack means that when using links
// on UNC paths, they will not be followed.
if !isUNC(absContextDir) {
absContextDir, err = filepath.EvalSymlinks(absContextDir)
if err != nil {
return "", "", fmt.Errorf("unable to evaluate symlinks in context path: %v", err)
}
}
stat, err := os.Lstat(absContextDir)
if err != nil {
return "", "", fmt.Errorf("unable to stat context directory %q: %v", absContextDir, err)
}
if !stat.IsDir() {
return "", "", fmt.Errorf("context must be a directory: %s", absContextDir)
}
absDockerfile := givenDockerfile
if absDockerfile == "" {
// No -f/--file was specified so use the default relative to the
// context directory.
absDockerfile = filepath.Join(absContextDir, api.DefaultDockerfileName)
// Just to be nice ;-) look for 'dockerfile' too but only
// use it if we found it, otherwise ignore this check
if _, err = os.Lstat(absDockerfile); os.IsNotExist(err) {
altPath := filepath.Join(absContextDir, strings.ToLower(api.DefaultDockerfileName))
if _, err = os.Lstat(altPath); err == nil {
absDockerfile = altPath
}
}
}
// If not already an absolute path, the Dockerfile path should be joined to
// the base directory.
if !filepath.IsAbs(absDockerfile) {
absDockerfile = filepath.Join(absContextDir, absDockerfile)
}
// Evaluate symlinks in the path to the Dockerfile too.
//
// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
// paths (those starting with \\). This hack means that when using links
// on UNC paths, they will not be followed.
if !isUNC(absDockerfile) {
absDockerfile, err = filepath.EvalSymlinks(absDockerfile)
if err != nil {
return "", "", fmt.Errorf("unable to evaluate symlinks in Dockerfile path: %v", err)
}
}
if _, err := os.Lstat(absDockerfile); err != nil {
if os.IsNotExist(err) {
return "", "", fmt.Errorf("Cannot locate Dockerfile: %q", absDockerfile)
}
return "", "", fmt.Errorf("unable to stat Dockerfile: %v", err)
}
if relDockerfile, err = filepath.Rel(absContextDir, absDockerfile); err != nil {
return "", "", fmt.Errorf("unable to get relative Dockerfile path: %v", err)
}
if strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
return "", "", fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", givenDockerfile, givenContextDir)
}
return absContextDir, relDockerfile, nil
}
// writeToFile copies from the given reader and writes it to a file with the
// given filename.
func writeToFile(r io.Reader, filename string) error {
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
if err != nil {
return fmt.Errorf("unable to create file: %v", err)
}
defer file.Close()
if _, err := io.Copy(file, r); err != nil {
return fmt.Errorf("unable to write file: %v", err)
}
return nil
}
// getContextFromReader will read the contents of the given reader as either a
// Dockerfile or tar archive. Returns a tar archive used as a context and a
// path to the Dockerfile inside the tar.
func getContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
buf := bufio.NewReader(r)
magic, err := buf.Peek(archive.HeaderSize)
if err != nil && err != io.EOF {
return nil, "", fmt.Errorf("failed to peek context header from STDIN: %v", err)
}
if archive.IsArchive(magic) {
return ioutils.NewReadCloserWrapper(buf, func() error { return r.Close() }), dockerfileName, nil
}
// Input should be read as a Dockerfile.
tmpDir, err := ioutil.TempDir("", "docker-build-context-")
if err != nil {
return nil, "", fmt.Errorf("unbale to create temporary context directory: %v", err)
}
f, err := os.Create(filepath.Join(tmpDir, api.DefaultDockerfileName))
if err != nil {
return nil, "", err
}
_, err = io.Copy(f, buf)
if err != nil {
f.Close()
return nil, "", err
}
if err := f.Close(); err != nil {
return nil, "", err
}
if err := r.Close(); err != nil {
return nil, "", err
}
tar, err := archive.Tar(tmpDir, archive.Uncompressed)
if err != nil {
return nil, "", err
}
return ioutils.NewReadCloserWrapper(tar, func() error {
err := tar.Close()
os.RemoveAll(tmpDir)
return err
}), api.DefaultDockerfileName, nil
}
// getContextFromGitURL uses a Git URL as context for a `docker build`. The
// git repo is cloned into a temporary directory used as the context directory.
// Returns the absolute path to the temporary context directory, the relative
// path of the dockerfile in that context directory, and a non-nil error on
// success.
func getContextFromGitURL(gitURL, dockerfileName string) (absContextDir, relDockerfile string, err error) {
if _, err := exec.LookPath("git"); err != nil {
return "", "", fmt.Errorf("unable to find 'git': %v", err)
}
if absContextDir, err = gitutils.Clone(gitURL); err != nil {
return "", "", fmt.Errorf("unable to 'git clone' to temporary context directory: %v", err)
}
return getDockerfileRelPath(absContextDir, dockerfileName)
}
// getContextFromURL uses a remote URL as context for a `docker build`. The
// remote resource is downloaded as either a Dockerfile or a tar archive.
// Returns the tar archive used for the context and a path of the
// dockerfile inside the tar.
func getContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.ReadCloser, string, error) {
response, err := httputils.Download(remoteURL)
if err != nil {
return nil, "", fmt.Errorf("unable to download remote context %s: %v", remoteURL, err)
}
progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(out, true)
// Pass the response body through a progress reader.
progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", fmt.Sprintf("Downloading build context from remote url: %s", remoteURL))
return getContextFromReader(ioutils.NewReadCloserWrapper(progReader, func() error { return response.Body.Close() }), dockerfileName)
}
// getContextFromLocalDir uses the given local directory as context for a
// `docker build`. Returns the absolute path to the local context directory,
// the relative path of the dockerfile in that context directory, and a non-nil
// error on success.
func getContextFromLocalDir(localDir, dockerfileName string) (absContextDir, relDockerfile string, err error) {
// When using a local context directory, when the Dockerfile is specified
// with the `-f/--file` option then it is considered relative to the
// current directory and not the context directory.
if dockerfileName != "" {
if dockerfileName, err = filepath.Abs(dockerfileName); err != nil {
return "", "", fmt.Errorf("unable to get absolute path to Dockerfile: %v", err)
}
}
return getDockerfileRelPath(localDir, dockerfileName)
}
var dockerfileFromLinePattern = regexp.MustCompile(`(?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+)`)
// resolvedTag records the repository, tag, and resolved digest reference
// from a Dockerfile rewrite.
type resolvedTag struct {
digestRef reference.Canonical
tagRef reference.NamedTagged
}
// rewriteDockerfileFrom rewrites the given Dockerfile by resolving images in
// "FROM <image>" instructions to a digest reference. `translator` is a
// function that takes a repository name and tag reference and returns a
// trusted digest reference.
func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator translatorFunc) (newDockerfile []byte, resolvedTags []*resolvedTag, err error) {
scanner := bufio.NewScanner(dockerfile)
buf := bytes.NewBuffer(nil)
// Scan the lines of the Dockerfile, looking for a "FROM" line.
for scanner.Scan() {
line := scanner.Text()
matches := dockerfileFromLinePattern.FindStringSubmatch(line)
if matches != nil && matches[1] != api.NoBaseImageSpecifier {
// Replace the line with a resolved "FROM repo@digest"
ref, err := reference.ParseNamed(matches[1])
if err != nil {
return nil, nil, err
}
ref = reference.WithDefaultTag(ref)
if ref, ok := ref.(reference.NamedTagged); ok && isTrusted() {
trustedRef, err := translator(ctx, ref)
if err != nil {
return nil, nil, err
}
line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", trustedRef.String()))
resolvedTags = append(resolvedTags, &resolvedTag{
digestRef: trustedRef,
tagRef: ref,
})
}
}
_, err := fmt.Fprintln(buf, line)
if err != nil {
return nil, nil, err
}
}
return buf.Bytes(), resolvedTags, scanner.Err()
}
// replaceDockerfileTarWrapper wraps the given input tar archive stream and
// replaces the entry with the given Dockerfile name with the contents of the
// new Dockerfile. Returns a new tar archive stream with the replaced
// Dockerfile.
func replaceDockerfileTarWrapper(ctx context.Context, inputTarStream io.ReadCloser, dockerfileName string, translator translatorFunc, resolvedTags *[]*resolvedTag) io.ReadCloser {
pipeReader, pipeWriter := io.Pipe()
go func() {
tarReader := tar.NewReader(inputTarStream)
tarWriter := tar.NewWriter(pipeWriter)
defer inputTarStream.Close()
for {
hdr, err := tarReader.Next()
if err == io.EOF {
// Signals end of archive.
tarWriter.Close()
pipeWriter.Close()
return
}
if err != nil {
pipeWriter.CloseWithError(err)
return
}
var content io.Reader = tarReader
if hdr.Name == dockerfileName {
// This entry is the Dockerfile. Since the tar archive was
// generated from a directory on the local filesystem, the
// Dockerfile will only appear once in the archive.
var newDockerfile []byte
newDockerfile, *resolvedTags, err = rewriteDockerfileFrom(ctx, content, translator)
if err != nil {
pipeWriter.CloseWithError(err)
return
}
hdr.Size = int64(len(newDockerfile))
content = bytes.NewBuffer(newDockerfile)
}
if err := tarWriter.WriteHeader(hdr); err != nil {
pipeWriter.CloseWithError(err)
return
}
if _, err := io.Copy(tarWriter, content); err != nil {
pipeWriter.CloseWithError(err)
return
}
}
}()
return pipeReader
}

247
vendor/github.com/hyperhq/hypercli/api/client/cli.go generated vendored Normal file
View File

@@ -0,0 +1,247 @@
package client
import (
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"runtime"
"github.com/docker/engine-api/client"
"github.com/docker/go-connections/sockets"
"github.com/docker/go-connections/tlsconfig"
"github.com/hyperhq/hypercli/api"
"github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/cliconfig"
"github.com/hyperhq/hypercli/dockerversion"
"github.com/hyperhq/hypercli/opts"
"github.com/hyperhq/hypercli/pkg/term"
)
// DockerCli represents the docker command line client.
// Instances of the client can be returned from NewDockerCli.
type DockerCli struct {
// initializing closure
init func() error
// configFile has the client configuration file
configFile *cliconfig.ConfigFile
// in holds the input stream and closer (io.ReadCloser) for the client.
in io.ReadCloser
// out holds the output stream (io.Writer) for the client.
out io.Writer
// err holds the error stream (io.Writer) for the client.
err io.Writer
// keyFile holds the key file as a string.
keyFile string
// inFd holds the file descriptor of the client's STDIN (if valid).
inFd uintptr
// outFd holds file descriptor of the client's STDOUT (if valid).
outFd uintptr
// isTerminalIn indicates whether the client's STDIN is a TTY
isTerminalIn bool
// isTerminalOut indicates whether the client's STDOUT is a TTY
isTerminalOut bool
// client is the http client that performs all API operations
client client.APIClient
// state holds the terminal state
state *term.State
region string
host string
}
// Initialize calls the init function that will setup the configuration for the client
// such as the TLS, tcp and other parameters used to run the client.
func (cli *DockerCli) Initialize() error {
if cli.init == nil {
return nil
}
return cli.init()
}
// CheckTtyInput checks if we are trying to attach to a container tty
// from a non-tty client input stream, and if so, returns an error.
func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
// In order to attach to a container tty, input stream for the client must
// be a tty itself: redirecting or piping the client standard input is
// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
if ttyMode && attachStdin && !cli.isTerminalIn {
return errors.New("cannot enable tty mode on non tty input")
}
return nil
}
// PsFormat returns the format string specified in the configuration.
// String contains columns and format specification, for example {{ID}}\t{{Name}}.
func (cli *DockerCli) PsFormat() string {
return cli.configFile.PsFormat
}
// ImagesFormat returns the format string specified in the configuration.
// String contains columns and format specification, for example {{ID}}\t{{Name}}.
func (cli *DockerCli) ImagesFormat() string {
return cli.configFile.ImagesFormat
}
// VolumesFormat returns the format string specified in the configuration.
// String contains columns and format specification, for example {{ID}}\t{{Name}}.
func (cli *DockerCli) VolumesFormat() string {
return cli.configFile.VolumesFormat
}
func (cli *DockerCli) setRawTerminal() error {
if cli.isTerminalIn && os.Getenv("NORAW") == "" {
state, err := term.SetRawTerminal(cli.inFd)
if err != nil {
return err
}
cli.state = state
}
return nil
}
func (cli *DockerCli) restoreTerminal(in io.Closer) error {
if cli.state != nil {
term.RestoreTerminal(cli.inFd, cli.state)
}
// WARNING: DO NOT REMOVE THE OS CHECK !!!
// For some reason this Close call blocks on darwin..
// As the client exists right after, simply discard the close
// until we find a better solution.
if in != nil && runtime.GOOS != "darwin" {
return in.Close()
}
return nil
}
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
// The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config
// is set the client scheme will be set to https.
// The client will be given a 32-second timeout (see https://github.com/hyperhq/hypercli/pull/8035).
func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientFlags) *DockerCli {
cli := &DockerCli{
in: in,
out: out,
err: err,
keyFile: clientFlags.Common.TrustKey,
}
cli.init = func() error {
clientFlags.PostParse()
configFile, e := cliconfig.Load(cliconfig.ConfigDir())
if e != nil {
fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e)
}
cli.configFile = configFile
host, dft, err := cli.getServerHost(clientFlags.Common.Region, clientFlags.Common.TLSOptions)
if err != nil {
return err
}
customHeaders := cli.configFile.HTTPHeaders
if customHeaders == nil {
customHeaders = map[string]string{}
}
customHeaders["User-Agent"] = "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")"
verStr := api.DefaultVersion.String()
if tmpStr := os.Getenv("HYPER_API_VERSION"); tmpStr != "" {
verStr = tmpStr
}
httpClient, err := newHTTPClient(host, clientFlags.Common.TLSOptions)
if err != nil {
return err
}
var cloudConfig cliconfig.CloudConfig
cc, ok := configFile.CloudConfig[cliconfig.DefaultHyperFormat]
if ok && dft {
cloudConfig.AccessKey = cc.AccessKey
cloudConfig.SecretKey = cc.SecretKey
} else {
cc, ok = configFile.CloudConfig[host]
if ok {
cloudConfig.AccessKey = cc.AccessKey
cloudConfig.SecretKey = cc.SecretKey
} else {
cloudConfig.AccessKey = os.Getenv("HYPER_ACCESS")
cloudConfig.SecretKey = os.Getenv("HYPER_SECRET")
}
}
if cloudConfig.AccessKey == "" || cloudConfig.SecretKey == "" {
fmt.Fprintf(cli.err, "WARNING: null cloud config\n")
}
cli.region = clientFlags.Common.Region
if cli.region == "" {
if cli.region = cc.Region; cli.region == "" {
cli.region = cli.getDefaultRegion()
}
}
if !dft {
if cli.region = cc.Region; cli.region == "" {
cli.region = cliconfig.DefaultHyperRegion
}
}
client, err := client.NewClient(host, verStr, httpClient, customHeaders, cloudConfig.AccessKey, cloudConfig.SecretKey, cli.region)
if err != nil {
return err
}
cli.client = client
cli.host = host
if cli.in != nil {
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
}
if cli.out != nil {
cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
}
return nil
}
return cli
}
func (cli *DockerCli) getServerHost(region string, tlsOptions *tlsconfig.Options) (host string, dft bool, err error) {
dft = false
host = region
if host == "" {
host = os.Getenv("HYPER_DEFAULT_REGION")
region = cli.getDefaultRegion()
}
if _, err := url.ParseRequestURI(host); err != nil {
host = "tcp://" + region + "." + cliconfig.DefaultHyperEndpoint
dft = true
}
host, err = opts.ParseHost(tlsOptions != nil, host)
return
}
func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, error) {
if tlsOptions == nil {
// let the api client configure the default transport.
return nil, nil
}
config, err := tlsconfig.Client(*tlsOptions)
if err != nil {
return nil, err
}
tr := &http.Transport{
TLSClientConfig: config,
}
proto, addr, _, err := client.ParseHost(host)
if err != nil {
return nil, err
}
sockets.ConfigureTransport(tr, proto, addr)
return &http.Client{
Transport: tr,
}, nil
}

View File

@@ -0,0 +1,5 @@
// Package client provides a command-line interface for Docker.
//
// Run "docker help SUBCOMMAND" or "docker SUBCOMMAND --help" to see more information on any Docker subcommand, including the full list of options supported for the subcommand.
// See https://docs.docker.com/installation/ for instructions on installing Docker.
package client

View File

@@ -0,0 +1,61 @@
package client
import (
"encoding/json"
"fmt"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"golang.org/x/net/context"
)
// CmdCommit creates a new image from a container's changes.
//
// Usage: hyper commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
func (cli *DockerCli) CmdCommit(args ...string) error {
cmd := Cli.Subcmd("commit", []string{"CONTAINER [REPOSITORY[:TAG]]"}, Cli.DockerCommands["commit"].Description, true)
flPause := cmd.Bool([]string{}, true, "Pause container during commit")
flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
flAuthor := cmd.String([]string{"a", "-author"}, "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
flChanges := opts.NewListOpts(nil)
cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
// FIXME: --run is deprecated, it will be replaced with inline Dockerfile commands.
flConfig := cmd.String([]string{}, "", "This option is deprecated and will be removed in a future version in favor of inline Dockerfile-compatible commands")
cmd.Require(flag.Max, 2)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var (
name = cmd.Arg(0)
reference = cmd.Arg(1)
)
var config *container.Config
if *flConfig != "" {
config = &container.Config{}
if err := json.Unmarshal([]byte(*flConfig), config); err != nil {
return err
}
}
options := types.ContainerCommitOptions{
Reference: reference,
Comment: *flComment,
Author: *flAuthor,
Changes: flChanges.GetAll(),
Pause: *flPause,
Config: config,
}
response, err := cli.client.ContainerCommit(context.Background(), name, options)
if err != nil {
return err
}
fmt.Fprintln(cli.out, response.ID)
return nil
}

View File

@@ -0,0 +1,519 @@
package client
import (
"fmt"
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/docker/engine-api/client"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/pkg/jsonmessage"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/libcompose/docker"
"github.com/hyperhq/libcompose/logger"
"github.com/hyperhq/libcompose/project"
"github.com/hyperhq/libcompose/project/options"
"golang.org/x/net/context"
)
const ComposeFipAuto = "auto"
// CmdCompose is the parent subcommand for all compose commands
//
// Usage: hyper compose <COMMAND> [OPTIONS]
func (cli *DockerCli) CmdCompose(args ...string) error {
cmd := Cli.Subcmd("compose", []string{"<COMMAND>"}, composeUsage(), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdComposeRun
//
// Usage: hyper compose run [OPTIONS] SERVICE [COMMAND] [ARGS...]
func (cli *DockerCli) CmdComposeRun(args ...string) error {
cmd := Cli.Subcmd("compose run", []string{"SERVICE [COMMAND] [ARGS...]"}, "Run a one-off command on a service", false)
composeFile := cmd.String([]string{"f", "-file"}, "docker-compose.yml", "Specify an alternate compose file")
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
rm := cmd.Bool([]string{"-rm"}, false, "Remove container after run, ignored in detached mode")
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
if *projectName == "" {
*projectName = getBaseDir()
}
project, err := docker.NewProject(&docker.Context{
Context: project.Context{
ComposeFiles: []string{*composeFile},
ProjectName: *projectName,
Autoremove: *rm,
},
ClientFactory: cli,
})
if err != nil {
return err
}
service := cmd.Args()[0]
status, err := project.Run(context.Background(), service, cmd.Args()[1:])
if err != nil {
return err
}
if *rm {
opts := options.Delete{RemoveVolume: true}
if err = project.Delete(opts, service); err != nil {
return err
}
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
// CmdComposeDown
//
// Usage: hyper compose down [OPTIONS]
func (cli *DockerCli) CmdComposeDown(args ...string) error {
cmd := Cli.Subcmd("compose down", []string{}, "Stop and remove containers, images, and volumes\ncreated by `up`. Only containers and networks are removed by default.", false)
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
rmi := cmd.String([]string{"-rmi"}, "", "Remove images, type may be one of: 'all' to remove\nall images, or 'local' to remove only images that\ndon't have an custom name set by the `image` field")
vol := cmd.Bool([]string{"v", "-volumes"}, false, "Remove data volumes")
rmorphans := cmd.Bool([]string{"-remove-orphans"}, false, "Remove containers for services not defined in the Compose file")
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
imageType := options.ImageType(*rmi)
if !imageType.Valid() {
return fmt.Errorf("rmi with %s is not valid", *rmi)
}
if *projectName == "" {
*projectName = getBaseDir()
}
body, err := cli.client.ComposeDown(*projectName, cmd.Args(), *rmi, *vol, *rmorphans)
if err != nil {
return err
}
defer body.Close()
return jsonmessage.DisplayJSONMessagesStream(body, cli.out, cli.outFd, cli.isTerminalOut, nil)
}
// CmdComposeUp
//
// Usage: hyper compose up [OPTIONS]
func (cli *DockerCli) CmdComposeUp(args ...string) error {
cmd := Cli.Subcmd("compose up", []string{"[SERVICE...]"}, "Builds, (re)creates, starts, and attaches to containers for a service.\n\nUnless they are already running, this command also starts any linked services.\n\n"+
"The `hyper compose up` command aggregates the output of each container. When\n"+
"the command exits, all containers are stopped. Running `hyper compose up -d`\n"+
"starts the containers in the background and leaves them running.\n\n"+
"If there are existing containers for a service, and the service's configuration\n"+
"or image was changed after the container's creation, `hyper compose up` picks\n"+
"up the changes by stopping and recreating the containers (preserving mounted\n"+
"volumes). To prevent Compose from picking up changes, use the `--no-recreate`\n"+
"flag.\n\n"+
"If you want to force Compose to stop and recreate all containers, use the\n"+
"`--force-recreate` flag.", false)
composeFile := cmd.String([]string{"f", "-file"}, "docker-compose.yml", "Specify an alternate compose file")
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
detach := cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: Run containers in the background,\nprint new container names.\nIncompatible with --abort-on-container-exit.")
forcerecreate := cmd.Bool([]string{"-force-recreate"}, false, "Recreate containers even if their configuration\nand image haven't changed.\nIncompatible with --no-recreate.")
norecreate := cmd.Bool([]string{"-no-recreate"}, false, "If containers already exist, don't recreate them.\nIncompatible with --force-recreate.")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
project, err := docker.NewProject(&docker.Context{
Context: project.Context{
ComposeFiles: []string{*composeFile},
ProjectName: *projectName,
LoggerFactory: logger.NewColorLoggerFactory(),
},
ClientFactory: cli,
})
if err != nil {
return err
}
services := cmd.Args()
c, vc, nc := project.GetConfig()
if *projectName == "" {
*projectName = getBaseDir()
}
var fips = []string{}
var newFipNum = 0
for _, svconfig := range c.M {
if svconfig.Fip == ComposeFipAuto {
newFipNum++
}
}
fipFilterArgs, _ := filters.FromParam("dangling=true")
options := types.NetworkListOptions{
Filters: fipFilterArgs,
}
fipList, err := cli.client.FipList(context.Background(), options)
if err == nil {
for _, fip := range fipList {
if fip["container"] == "" && fip["service"] == "" {
fips = append(fips, fip["fip"])
}
}
}
if newFipNum > len(fips) {
if askForConfirmation(warnMessage) == true {
newFips, err := cli.client.FipAllocate(context.Background(), fmt.Sprintf("%d", newFipNum-len(fips)))
if err != nil {
return err
}
fips = append(fips, newFips...)
}
}
i := 0
for _, svconfig := range c.M {
if svconfig.Fip == ComposeFipAuto {
if i >= newFipNum {
svconfig.Fip = ""
} else {
svconfig.Fip = fips[i]
i++
}
}
}
body, err := cli.client.ComposeUp(*projectName, services, c, vc, nc, cli.configFile.AuthConfigs, *forcerecreate, *norecreate)
if err != nil {
return err
}
defer body.Close()
err = jsonmessage.DisplayJSONMessagesStream(body, cli.out, cli.outFd, cli.isTerminalOut, nil)
if err != nil {
return err
}
if !*detach {
signalChan := make(chan os.Signal, 1)
cleanupDone := make(chan bool)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
errChan := make(chan error)
go func() {
errChan <- project.Log(true, services...)
}()
go func() {
select {
case <-signalChan:
fmt.Printf("\nGracefully stopping...\n")
project.Stop(0, services...)
cleanupDone <- true
case err := <-errChan:
if err != nil {
logrus.Fatal(err)
}
cleanupDone <- true
}
}()
<-cleanupDone
return nil
}
return nil
}
// CmdComposeStart
//
// Usage: hyper compose start [OPTIONS] [SERVICE]
func (cli *DockerCli) CmdComposeStart(args ...string) error {
cmd := Cli.Subcmd("compose start", []string{"[SERVICE...]"}, "Start existing containers.", false)
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
services := cmd.Args()
if *projectName == "" {
*projectName = getBaseDir()
}
body, err := cli.client.ComposeStart(*projectName, services)
if err != nil {
return err
}
defer body.Close()
return jsonmessage.DisplayJSONMessagesStream(body, cli.out, cli.outFd, cli.isTerminalOut, nil)
}
// CmdComposeStop
//
// Usage: hyper compose stop [OPTIONS]
func (cli *DockerCli) CmdComposeStop(args ...string) error {
cmd := Cli.Subcmd("compose stop", []string{"[SERVICE...]"}, "Stop running containers without removing them.\n\nThey can be started again with `hyper compose start`.", false)
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
nSeconds := cmd.Int([]string{"t", "-timeout"}, 10, "Specify a shutdown timeout in seconds.")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
services := cmd.Args()
if *projectName == "" {
*projectName = getBaseDir()
}
body, err := cli.client.ComposeStop(*projectName, services, *nSeconds)
if err != nil {
return err
}
defer body.Close()
return jsonmessage.DisplayJSONMessagesStream(body, cli.out, cli.outFd, cli.isTerminalOut, nil)
}
// CmdComposeCreate
//
// Usage: hyper compose create [OPTIONS]
func (cli *DockerCli) CmdComposeCreate(args ...string) error {
cmd := Cli.Subcmd("compose create", []string{"[SERVICE...]"}, "Creates containers for a service.", false)
composeFile := cmd.String([]string{"f", "-file"}, "docker-compose.yml", "Specify an alternate compose file")
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
forcerecreate := cmd.Bool([]string{"-force-recreate"}, false, "Recreate containers even if their configuration\nand image haven't changed.\nIncompatible with --no-recreate.")
norecreate := cmd.Bool([]string{"-no-recreate"}, false, "If containers already exist, don't recreate them.\nIncompatible with --force-recreate.")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
project, err := docker.NewProject(&docker.Context{
Context: project.Context{
ComposeFiles: []string{*composeFile},
ProjectName: *projectName,
LoggerFactory: logger.NewColorLoggerFactory(),
},
ClientFactory: cli,
})
if err != nil {
return err
}
services := cmd.Args()
c, vc, nc := project.GetConfig()
if *projectName == "" {
*projectName = getBaseDir()
}
body, err := cli.client.ComposeCreate(*projectName, services, c, vc, nc, cli.configFile.AuthConfigs, *forcerecreate, *norecreate)
if err != nil {
return err
}
defer body.Close()
return jsonmessage.DisplayJSONMessagesStream(body, cli.out, cli.outFd, cli.isTerminalOut, nil)
}
// CmdComposePs
//
// Usage: hyper compose ps [OPTIONS]
func (cli *DockerCli) CmdComposePs(args ...string) error {
cmd := Cli.Subcmd("compose ps", []string{"[SERVICE...]"}, "List containers.", false)
composeFile := cmd.String([]string{"f", "-file"}, "docker-compose.yml", "Specify an alternate compose file")
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display IDs")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
if *projectName == "" {
*projectName = getBaseDir()
}
project, err := docker.NewProject(&docker.Context{
Context: project.Context{
ComposeFiles: []string{*composeFile},
ProjectName: *projectName,
},
ClientFactory: cli,
})
if err != nil {
return err
}
ps, err := project.Ps(*quiet, cmd.Args()...)
if err != nil {
return err
}
fmt.Printf(ps.String(true))
return nil
}
// CmdComposeKill
//
// Usage: hyper compose kill [OPTIONS]
func (cli *DockerCli) CmdComposeKill(args ...string) error {
cmd := Cli.Subcmd("compose kill", []string{"[SERVICE...]"}, "Force stop service containers.", false)
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
signal := cmd.String([]string{}, "KILL", "Signal to send to the container")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
services := cmd.Args()
if *projectName == "" {
*projectName = getBaseDir()
}
body, err := cli.client.ComposeKill(*projectName, services, *signal)
if err != nil {
return err
}
defer body.Close()
return jsonmessage.DisplayJSONMessagesStream(body, cli.out, cli.outFd, cli.isTerminalOut, nil)
}
// CmdComposeRm
//
// Usage: hyper compose rm [OPTIONS] [SERVICE]
func (cli *DockerCli) CmdComposeRm(args ...string) error {
cmd := Cli.Subcmd("compose rm", []string{"[SERVICE...]"}, "Remove stopped service containers.", false)
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
removeVol := cmd.Bool([]string{"v"}, false, "Remove volumes associated with containers")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
services := cmd.Args()
if *projectName == "" {
*projectName = getBaseDir()
}
body, err := cli.client.ComposeRm(*projectName, services, *removeVol)
if err != nil {
return err
}
defer body.Close()
return jsonmessage.DisplayJSONMessagesStream(body, cli.out, cli.outFd, cli.isTerminalOut, nil)
}
// CmdComposeScale
//
// Usage: hyper compose scale [OPTIONS] [SERVICE=NUM...]
func (cli *DockerCli) CmdComposeScale(args ...string) error {
cmd := Cli.Subcmd("compose scale", []string{"[SERVICE=NUM...]"}, "Set number of containers to run for a service.", false)
composeFile := cmd.String([]string{"f", "-file"}, "docker-compose.yml", "Specify an alternate compose file")
projectName := cmd.String([]string{"p", "-project-name"}, "", "Specify an alternate project name")
timeout := cmd.Int([]string{"t", "-timeout"}, 10, "Specify a shutdown timeout in seconds")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
if *projectName == "" {
*projectName = getBaseDir()
}
project, err := docker.NewProject(&docker.Context{
Context: project.Context{
ComposeFiles: []string{*composeFile},
ProjectName: *projectName,
},
ClientFactory: cli,
})
if err != nil {
return err
}
servicesScale := map[string]int{}
for _, ss := range cmd.Args() {
fields := strings.SplitN(ss, "=", 2)
if len(fields) != 2 {
continue
}
num, err := strconv.Atoi(fields[1])
if err != nil {
return err
}
servicesScale[fields[0]] = num
}
err = project.Scale(*timeout, servicesScale)
if err != nil {
return err
}
return nil
}
// CmdComposePull
//
// Usage: hyper compose pull [OPTIONS]
func (cli *DockerCli) CmdComposePull(args ...string) error {
cmd := Cli.Subcmd("compose pull", []string{"[SERVICE...]"}, "Pull images of services.", false)
composeFile := cmd.String([]string{"f", "-file"}, "docker-compose.yml", "Specify an alternate compose file")
cmd.Require(flag.Min, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
project, err := docker.NewProject(&docker.Context{
Context: project.Context{
ComposeFiles: []string{*composeFile},
},
ClientFactory: cli,
})
if err != nil {
return err
}
err = project.Pull(cmd.Args()...)
if err != nil {
return err
}
return nil
}
func composeUsage() string {
composeCommands := [][]string{
{"create", "Creates containers for a service"},
{"down", "Stop and remove containers, images, and volumes"},
{"kill", "Force stop service containers"},
{"ps", "List containers"},
{"pull", "Pull images of services"},
{"rm", "Remove stopped service containers"},
{"run", "Run a one-off command"},
{"scale", "Set number of containers for a service"},
{"start", "Start services"},
{"stop", "Stop services"},
{"up", "Create and start containers"},
}
help := "Commands:\n"
for _, cmd := range composeCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
}
help += fmt.Sprintf("\nRun 'hyper compose COMMAND --help' for more information on a command.")
return help
}
func (cli *DockerCli) Create(s project.Service) client.APIClient {
return cli.client
}
func getBaseDir() string {
file, err := os.Getwd()
if err != nil {
return ""
}
return filepath.Base(file)
}

123
vendor/github.com/hyperhq/hypercli/api/client/config.go generated vendored Normal file
View File

@@ -0,0 +1,123 @@
package client
import (
"fmt"
"os"
"runtime"
"strings"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/cliconfig"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdConfig
//
// Usage: hyper config
func (cli *DockerCli) CmdConfig(args ...string) error {
cmd := Cli.Subcmd("config", []string{"[REGION]"}, Cli.DockerCommands["config"].Description+".\nIf no region is specified, the default is defined as "+cliconfig.DefaultHyperRegion, true)
cmd.Require(flag.Max, 1)
flAccesskey := cmd.String([]string{"-accesskey"}, "", "Access Key")
flSecretkey := cmd.String([]string{"-secretkey"}, "", "Secret Key")
flDefaultRegion := cmd.String([]string{"-default-region"}, "", "Default Region Endpoint")
cmd.ParseFlags(args, true)
// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
if runtime.GOOS == "windows" {
cli.in = os.Stdin
}
var serverAddress string
if len(cmd.Args()) > 0 {
serverAddress = cmd.Arg(0)
} else {
serverAddress = cliconfig.DefaultHyperFormat
}
_, err := cli.configureCloud(serverAddress, *flDefaultRegion, *flAccesskey, *flSecretkey)
if err != nil {
return err
}
if err := cli.configFile.Save(); err != nil {
return fmt.Errorf("Error saving config file: %v", err)
}
fmt.Fprintf(cli.out, "WARNING: Your login credentials has been saved in %s\n", cli.configFile.Filename())
return nil
}
func (cli *DockerCli) configureCloud(serverAddress, flRegion, flAccesskey, flSecretkey string) (cliconfig.CloudConfig, error) {
cloudConfig := cliconfig.CloudConfig{}
if serverAddress != "" {
if cc, ok := cli.configFile.CloudConfig[serverAddress]; ok {
cloudConfig = cc
} else {
// for legacy format
defaultHost := "tcp://" + cliconfig.DefaultHyperRegion + "." + cliconfig.DefaultHyperEndpoint
cloudConfig, ok = cli.configFile.CloudConfig[defaultHost]
if ok {
delete(cli.configFile.CloudConfig, defaultHost)
}
}
}
defaultRegion := cli.getDefaultRegion()
if cloudConfig.Region != "" {
defaultRegion = cloudConfig.Region
}
if flAccesskey = strings.TrimSpace(flAccesskey); flAccesskey == "" {
cli.promptWithDefault("Enter Access Key", cloudConfig.AccessKey)
flAccesskey = readInput(cli.in, cli.out)
flAccesskey = strings.TrimSpace(flAccesskey)
if flAccesskey == "" {
flAccesskey = cloudConfig.AccessKey
}
}
if flSecretkey = strings.TrimSpace(flSecretkey); flSecretkey == "" {
cli.promptWithDefault("Enter Secret Key", cloudConfig.SecretKey)
flSecretkey = readInput(cli.in, cli.out)
flSecretkey = strings.TrimSpace(flSecretkey)
if flSecretkey == "" {
flSecretkey = cloudConfig.SecretKey
}
}
if flRegion = strings.TrimSpace(flRegion); flRegion == "" {
cli.promptWithDefault("Enter Default Region", defaultRegion)
flRegion = readInput(cli.in, cli.out)
flRegion = strings.TrimSpace(flRegion)
if flRegion == "" {
flRegion = defaultRegion
}
}
cloudConfig.AccessKey = flAccesskey
cloudConfig.SecretKey = flSecretkey
cloudConfig.Region = flRegion
if serverAddress != "" {
cli.configFile.CloudConfig[serverAddress] = cloudConfig
}
return cloudConfig, nil
}
func (cli *DockerCli) checkCloudConfig() error {
_, ok := cli.configFile.CloudConfig[cli.host]
if !ok {
_, ok = cli.configFile.CloudConfig[cliconfig.DefaultHyperFormat]
if !ok {
return fmt.Errorf("Config info for the host is not found, please run 'hyper config %s' first.", cli.host)
}
}
return nil
}
func (cli *DockerCli) getDefaultRegion() string {
cc, ok := cli.configFile.CloudConfig[cliconfig.DefaultHyperFormat]
if ok && cc.Region != "" {
return cc.Region
}
return cliconfig.DefaultHyperRegion
}

297
vendor/github.com/hyperhq/hypercli/api/client/cp.go generated vendored Normal file
View File

@@ -0,0 +1,297 @@
package client
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/pkg/archive"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/system"
)
type copyDirection int
const (
fromContainer copyDirection = (1 << iota)
toContainer
acrossContainers = fromContainer | toContainer
)
type cpConfig struct {
followLink bool
}
// CmdCp copies files/folders to or from a path in a container.
//
// When copying from a container, if DEST_PATH is '-' the data is written as a
// tar archive file to STDOUT.
//
// When copying to a container, if SRC_PATH is '-' the data is read as a tar
// archive file from STDIN, and the destination CONTAINER:DEST_PATH, must specify
// a directory.
//
// Usage:
// docker cp CONTAINER:SRC_PATH DEST_PATH|-
// docker cp SRC_PATH|- CONTAINER:DEST_PATH
func (cli *DockerCli) Cp(args ...string) error {
cmd := Cli.Subcmd(
"cp",
[]string{"CONTAINER:SRC_PATH DEST_PATH|-", "SRC_PATH|- CONTAINER:DEST_PATH"},
strings.Join([]string{
Cli.DockerCommands["cp"].Description,
"\nUse '-' as the source to read a tar archive from stdin\n",
"and extract it to a directory destination in a container.\n",
"Use '-' as the destination to stream a tar archive of a\n",
"container source to stdout.",
}, ""),
true,
)
followLink := cmd.Bool([]string{"L", "-follow-link"}, false, "Always follow symbol link in SRC_PATH")
cmd.Require(flag.Exact, 2)
cmd.ParseFlags(args, true)
if cmd.Arg(0) == "" {
return fmt.Errorf("source can not be empty")
}
if cmd.Arg(1) == "" {
return fmt.Errorf("destination can not be empty")
}
srcContainer, srcPath := splitCpArg(cmd.Arg(0))
dstContainer, dstPath := splitCpArg(cmd.Arg(1))
var direction copyDirection
if srcContainer != "" {
direction |= fromContainer
}
if dstContainer != "" {
direction |= toContainer
}
cpParam := &cpConfig{
followLink: *followLink,
}
ctx := context.Background()
switch direction {
case fromContainer:
return cli.copyFromContainer(ctx, srcContainer, srcPath, dstPath, cpParam)
case toContainer:
return cli.copyToContainer(ctx, srcPath, dstContainer, dstPath, cpParam)
case acrossContainers:
// Copying between containers isn't supported.
return fmt.Errorf("copying between containers is not supported")
default:
// User didn't specify any container.
return fmt.Errorf("must specify at least one container source")
}
}
// We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be
// in a valid LOCALPATH, like `file:name.txt`. We can resolve this ambiguity by
// requiring a LOCALPATH with a `:` to be made explicit with a relative or
// absolute path:
// `/path/to/file:name.txt` or `./file:name.txt`
//
// This is apparently how `scp` handles this as well:
// http://www.cyberciti.biz/faq/rsync-scp-file-name-with-colon-punctuation-in-it/
//
// We can't simply check for a filepath separator because container names may
// have a separator, e.g., "host0/cname1" if container is in a Docker cluster,
// so we have to check for a `/` or `.` prefix. Also, in the case of a Windows
// client, a `:` could be part of an absolute Windows path, in which case it
// is immediately proceeded by a backslash.
func splitCpArg(arg string) (container, path string) {
if system.IsAbs(arg) {
// Explicit local absolute path, e.g., `C:\foo` or `/foo`.
return "", arg
}
parts := strings.SplitN(arg, ":", 2)
if len(parts) == 1 || strings.HasPrefix(parts[0], ".") {
// Either there's no `:` in the arg
// OR it's an explicit local relative path like `./file:name.txt`.
return "", arg
}
return parts[0], parts[1]
}
func (cli *DockerCli) statContainerPath(ctx context.Context, containerName, path string) (types.ContainerPathStat, error) {
return cli.client.ContainerStatPath(ctx, containerName, path)
}
func resolveLocalPath(localPath string) (absPath string, err error) {
if absPath, err = filepath.Abs(localPath); err != nil {
return
}
return archive.PreserveTrailingDotOrSeparator(absPath, localPath), nil
}
func (cli *DockerCli) copyFromContainer(ctx context.Context, srcContainer, srcPath, dstPath string, cpParam *cpConfig) (err error) {
if dstPath != "-" {
// Get an absolute destination path.
dstPath, err = resolveLocalPath(dstPath)
if err != nil {
return err
}
}
// if client requests to follow symbol link, then must decide target file to be copied
var rebaseName string
if cpParam.followLink {
srcStat, err := cli.statContainerPath(ctx, srcContainer, srcPath)
// If the destination is a symbolic link, we should follow it.
if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
linkTarget := srcStat.LinkTarget
if !system.IsAbs(linkTarget) {
// Join with the parent directory.
srcParent, _ := archive.SplitPathDirEntry(srcPath)
linkTarget = filepath.Join(srcParent, linkTarget)
}
linkTarget, rebaseName = archive.GetRebaseName(srcPath, linkTarget)
srcPath = linkTarget
}
}
content, stat, err := cli.client.CopyFromContainer(ctx, srcContainer, srcPath)
if err != nil {
return err
}
defer content.Close()
if dstPath == "-" {
// Send the response to STDOUT.
_, err = io.Copy(os.Stdout, content)
return err
}
// Prepare source copy info.
srcInfo := archive.CopyInfo{
Path: srcPath,
Exists: true,
IsDir: stat.Mode.IsDir(),
RebaseName: rebaseName,
}
preArchive := content
if len(srcInfo.RebaseName) != 0 {
_, srcBase := archive.SplitPathDirEntry(srcInfo.Path)
preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName)
}
// See comments in the implementation of `archive.CopyTo` for exactly what
// goes into deciding how and whether the source archive needs to be
// altered for the correct copy behavior.
return archive.CopyTo(preArchive, srcInfo, dstPath)
}
func (cli *DockerCli) copyToContainer(ctx context.Context, srcPath, dstContainer, dstPath string, cpParam *cpConfig) (err error) {
if srcPath != "-" {
// Get an absolute source path.
srcPath, err = resolveLocalPath(srcPath)
if err != nil {
return err
}
}
// In order to get the copy behavior right, we need to know information
// about both the source and destination. The API is a simple tar
// archive/extract API but we can use the stat info header about the
// destination to be more informed about exactly what the destination is.
// Prepare destination copy info by stat-ing the container path.
dstInfo := archive.CopyInfo{Path: dstPath}
dstStat, err := cli.statContainerPath(ctx, dstContainer, dstPath)
// If the destination is a symbolic link, we should evaluate it.
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
linkTarget := dstStat.LinkTarget
if !system.IsAbs(linkTarget) {
// Join with the parent directory.
dstParent, _ := archive.SplitPathDirEntry(dstPath)
linkTarget = filepath.Join(dstParent, linkTarget)
}
dstInfo.Path = linkTarget
dstStat, err = cli.statContainerPath(ctx, dstContainer, linkTarget)
}
// Ignore any error and assume that the parent directory of the destination
// path exists, in which case the copy may still succeed. If there is any
// type of conflict (e.g., non-directory overwriting an existing directory
// or vice versa) the extraction will fail. If the destination simply did
// not exist, but the parent directory does, the extraction will still
// succeed.
if err == nil {
dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir()
}
var (
content io.Reader
resolvedDstPath string
)
if srcPath == "-" {
// Use STDIN.
content = os.Stdin
resolvedDstPath = dstInfo.Path
if !dstInfo.IsDir {
return fmt.Errorf("destination %q must be a directory", fmt.Sprintf("%s:%s", dstContainer, dstPath))
}
} else {
// Prepare source copy info.
srcInfo, err := archive.CopyInfoSourcePath(srcPath, cpParam.followLink)
if err != nil {
return err
}
srcArchive, err := archive.TarResource(srcInfo)
if err != nil {
return err
}
defer srcArchive.Close()
// With the stat info about the local source as well as the
// destination, we have enough information to know whether we need to
// alter the archive that we upload so that when the server extracts
// it to the specified directory in the container we get the desired
// copy behavior.
// See comments in the implementation of `archive.PrepareArchiveCopy`
// for exactly what goes into deciding how and whether the source
// archive needs to be altered for the correct copy behavior when it is
// extracted. This function also infers from the source and destination
// info which directory to extract to, which may be the parent of the
// destination that the user specified.
dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo)
if err != nil {
return err
}
defer preparedArchive.Close()
resolvedDstPath = dstDir
content = preparedArchive
}
options := types.CopyToContainerOptions{
AllowOverwriteDirWithFile: false,
}
return cli.client.CopyToContainer(ctx, dstContainer, resolvedDstPath, content, options)
}

233
vendor/github.com/hyperhq/hypercli/api/client/create.go generated vendored Normal file
View File

@@ -0,0 +1,233 @@
package client
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"golang.org/x/net/context"
"github.com/docker/engine-api/client"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/pkg/jsonmessage"
"github.com/hyperhq/hypercli/reference"
"github.com/hyperhq/hypercli/registry"
runconfigopts "github.com/hyperhq/hypercli/runconfig/opts"
)
func (cli *DockerCli) pullImage(ctx context.Context, image string) error {
return cli.pullImageCustomOut(ctx, image, cli.out)
}
func (cli *DockerCli) pullImageCustomOut(ctx context.Context, image string, out io.Writer) error {
ref, err := reference.ParseNamed(image)
if err != nil {
return err
}
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
return err
}
authConfig := cli.resolveAuthConfig(ctx, cli.configFile.AuthConfigs, repoInfo.Index)
encodedAuth, err := encodeAuthToBase64(authConfig)
if err != nil {
return err
}
options := types.ImageCreateOptions{
RegistryAuth: encodedAuth,
}
responseBody, err := cli.client.ImageCreate(ctx, image, options)
if err != nil {
return err
}
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream(responseBody, out, cli.outFd, cli.isTerminalOut, nil)
}
type cidFile struct {
path string
file *os.File
written bool
}
func newCIDFile(path string) (*cidFile, error) {
if _, err := os.Stat(path); err == nil {
return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path)
}
f, err := os.Create(path)
if err != nil {
return nil, fmt.Errorf("Failed to create the container ID file: %s", err)
}
return &cidFile{path: path, file: f}, nil
}
func parseProtoAndLocalBind(bind string) (string, string, bool) {
switch {
case strings.HasPrefix(bind, "git://"):
fallthrough
case strings.HasPrefix(bind, "http://"):
fallthrough
case strings.HasPrefix(bind, "https://"):
if strings.Count(bind, ":") < 2 {
return "", "", false
}
case strings.HasPrefix(bind, "/"):
if strings.Count(bind, ":") < 1 {
return "", "", false
}
case filepath.VolumeName(bind) != "":
// Windows local path
default:
return "", "", false
}
pos := strings.LastIndex(bind, ":")
if pos < 0 || pos >= len(bind)-1 {
return "", "", false
}
return bind[:pos], bind[pos+1:], true
}
func (cli *DockerCli) createContainer(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
var containerIDFile *cidFile
var initvols, volumeList []string
if cidfile != "" {
var err error
if containerIDFile, err = newCIDFile(cidfile); err != nil {
return nil, err
}
defer containerIDFile.Close()
}
// Check/create protocol and local volume
defer func() {
for _, vol := range volumeList {
cli.client.VolumeRemove(ctx, vol)
}
}()
for idx, bind := range hostConfig.Binds {
if source, dest, ok := parseProtoAndLocalBind(bind); ok {
volReq := types.VolumeCreateRequest{
Driver: "hyper",
Labels: map[string]string{
"autoremove": "true",
}}
if vol, err := cli.client.VolumeCreate(ctx, volReq); err != nil {
return nil, err
} else {
initvols = append(initvols, source+":"+vol.Name)
volumeList = append(volumeList, vol.Name)
hostConfig.Binds[idx] = vol.Name + ":" + dest
}
}
}
// initialize special volumes
if len(initvols) > 0 {
err := cli.initVolumes(initvols, false)
if err != nil {
return nil, err
}
}
ref, err := reference.ParseNamed(config.Image)
if err != nil {
return nil, err
}
ref = reference.WithDefaultTag(ref)
var trustedRef reference.Canonical
if ref, ok := ref.(reference.NamedTagged); ok && isTrusted() {
var err error
trustedRef, err = cli.trustedReference(ctx, ref)
if err != nil {
return nil, err
}
config.Image = trustedRef.String()
}
//create the container
response, err := cli.client.ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
//if image not found try to pull it
if err != nil {
if client.IsErrImageNotFound(err) {
fmt.Fprintf(cli.err, "Unable to find image '%s' in the current region\n", ref.String())
// we don't want to write to stdout anything apart from container.ID
if err = cli.pullImageCustomOut(ctx, config.Image, cli.err); err != nil {
return nil, err
}
if ref, ok := ref.(reference.NamedTagged); ok && trustedRef != nil {
if err := cli.tagTrusted(ctx, trustedRef, ref); err != nil {
return nil, err
}
}
// Retry
var retryErr error
response, retryErr = cli.client.ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
if retryErr != nil {
return nil, retryErr
}
} else {
return nil, err
}
}
volumeList = nil
for _, warning := range response.Warnings {
fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
}
if containerIDFile != nil {
if err = containerIDFile.Write(response.ID); err != nil {
return nil, err
}
}
return &response, nil
}
// CmdCreate creates a new container from a given image.
//
// Usage: docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
func (cli *DockerCli) CmdCreate(args ...string) error {
cmd := Cli.Subcmd("create", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["create"].Description, true)
addTrustedFlags(cmd, true)
// These are flags not stored in Config/HostConfig
var (
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
)
config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
if err != nil {
cmd.ReportError(err.Error(), true)
os.Exit(1)
}
if config.Image == "" {
cmd.Usage()
return nil
}
response, err := cli.createContainer(context.Background(), config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", response.ID)
return nil
}

398
vendor/github.com/hyperhq/hypercli/api/client/cron.go generated vendored Normal file
View File

@@ -0,0 +1,398 @@
package client
import (
"fmt"
"strconv"
"strings"
"text/tabwriter"
"time"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
"github.com/docker/engine-api/types/filters"
"github.com/docker/engine-api/types/network"
"github.com/docker/engine-api/types/strslice"
"github.com/docker/go-connections/nat"
Cli "github.com/hyperhq/hypercli/cli"
ropts "github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/signal"
"github.com/hyperhq/hypercli/runconfig/opts"
"golang.org/x/net/context"
)
// CmdCron is the parent subcommand for all cron commands
//
// Usage: hyper cron <COMMAND> [OPTIONS]
func (cli *DockerCli) CmdCron(args ...string) error {
cmd := Cli.Subcmd("cron", []string{"COMMAND [OPTIONS]"}, cronUsage(), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdCronCreate creates a new cron with a given name
//
// Usage: hyper cron create [OPTIONS]
func (cli *DockerCli) CmdCronCreate(args ...string) error {
cmd := Cli.Subcmd("cron create", []string{"IMAGE"}, "Create a cron job", false)
var (
flSecurityGroups = ropts.NewListOpts(nil)
flEnv = ropts.NewListOpts(opts.ValidateEnv)
flLabels = ropts.NewListOpts(opts.ValidateEnv)
flEnvFile = ropts.NewListOpts(nil)
flVolumes = ropts.NewListOpts(nil)
flLinks = ropts.NewListOpts(opts.ValidateLink)
flLabelsFile = ropts.NewListOpts(nil)
flPublish = ropts.NewListOpts(nil)
flExpose = ropts.NewListOpts(nil)
flName = cmd.String([]string{"-name"}, "", "Cron name")
flContainerName = cmd.String([]string{"-container-name"}, "", "Cron container name")
flEntrypoint = cmd.String([]string{"-entrypoint"}, "", "Overwrite the default ENTRYPOINT of the image")
flNetMode = cmd.String([]string{}, "bridge", "Connect containers to a network, only bridge is supported now")
flStopSignal = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
flContainerSize = cmd.String([]string{"-size"}, "s4", "The size of cron containers (e.g. s1, s2, s3, s4, m1, m2, m3, l1, l2, l3)")
flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
flHostname = cmd.String([]string{"h", "-hostname"}, "", "Container host name")
flNoAutoVolume = cmd.Bool([]string{"-noauto-volume"}, false, "Do not create volumes specified in image")
flPublishAll = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to random ports")
flRestartPolicy = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
flMailTo = cmd.String([]string{"-mailto"}, "", "Mail to while the cron has something")
flMailPolicy = cmd.String([]string{"-mail"}, "on-failure", "Mail policy to apply when to send email")
flAccessKey = cmd.String([]string{"-access-key"}, "", "Access key to run the cron job")
flSecretKey = cmd.String([]string{"-secret-key"}, "", "Secret key to run the cron job")
flMinute = cmd.String([]string{"-minute"}, "0", "The minutes of cron expression")
flHour = cmd.String([]string{"-hour"}, "0", "The hour of cron expression")
flDom = cmd.String([]string{"-dom"}, "*", "The day of month of cron expression")
flDow = cmd.String([]string{"-week"}, "*", "The day of week of cron expression")
flMonth = cmd.String([]string{"-month"}, "*", "The month of cron expression")
)
cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
cmd.Var(&flSecurityGroups, []string{"-sg"}, "Security group for each container")
cmd.Var(&flVolumes, []string{"v", "--volume"}, "Volume for each container")
cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host")
cmd.Var(&flExpose, []string{"-expose"}, "Expose a port or a range of ports")
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
if (*flAccessKey == "" && *flSecretKey != "") || (*flAccessKey != "" && *flSecretKey == "") {
return fmt.Errorf("You must specify access key and secret key at the same time")
}
var (
parsedArgs = cmd.Args()
runCmd strslice.StrSlice
entrypoint strslice.StrSlice
image = cmd.Arg(0)
)
if len(parsedArgs) > 1 {
runCmd = strslice.StrSlice(parsedArgs[1:])
}
if *flEntrypoint != "" {
entrypoint = strslice.StrSlice{*flEntrypoint}
}
if _, _, err = cli.client.ImageInspectWithRaw(context.Background(), image, false); err != nil && strings.Contains(err.Error(), "No such image") {
if err := cli.pullImage(context.Background(), image); err != nil {
return err
}
}
// collect all the environment variables for the container
envVariables, err := opts.ReadKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
if err != nil {
return err
}
// collect all the labels for the container
labels, err := opts.ReadKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
if err != nil {
return err
}
labels = append(labels, fmt.Sprintf("sh_hyper_instancetype=%s", *flContainerSize))
for _, sg := range flSecurityGroups.GetAll() {
if sg == "" {
continue
}
labels = append(labels, fmt.Sprintf("sh_hyper_sg_%s=yes", sg))
}
if *flNoAutoVolume {
labels = append(labels, "sh_hyper_noauto_volume=true")
}
var (
domainname string
hostname = *flHostname
parts = strings.SplitN(hostname, ".", 2)
)
if len(parts) > 1 {
hostname = parts[0]
domainname = parts[1]
}
ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
if err != nil {
return err
}
// Merge in exposed ports to the map of published ports
for _, e := range flExpose.GetAll() {
if strings.Contains(e, ":") {
return fmt.Errorf("Invalid port format for --expose: %s", e)
}
//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
proto, port := nat.SplitProtoPort(e)
//parse the start and end port and create a sequence of ports to expose
//if expose a port, the start and end port are the same
start, end, err := nat.ParsePortRange(port)
if err != nil {
return fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
}
for i := start; i <= end; i++ {
p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
if err != nil {
return err
}
if _, exists := ports[p]; !exists {
ports[p] = struct{}{}
}
}
}
var binds []string
// add any bind targets to the list of container volumes
for bind := range flVolumes.GetMap() {
if arr := opts.VolumeSplitN(bind, 2); len(arr) > 1 {
// after creating the bind mount we want to delete it from the flVolumes values because
// we do not want bind mounts being committed to image configs
binds = append(binds, bind)
flVolumes.Delete(bind)
}
}
restartPolicy, err := opts.ParseRestartPolicy(*flRestartPolicy)
if err != nil {
return err
}
config := &container.Config{
Hostname: hostname,
Domainname: domainname,
Tty: true,
ExposedPorts: ports,
Env: envVariables,
Cmd: runCmd,
Image: image,
Volumes: flVolumes.GetMap(),
Entrypoint: entrypoint,
WorkingDir: *flWorkingDir,
Labels: opts.ConvertKVStringsToMap(labels),
StopSignal: *flStopSignal,
}
hostConfig := &container.HostConfig{
Binds: binds,
PortBindings: portBindings,
Links: flLinks.GetAll(),
PublishAllPorts: *flPublishAll,
NetworkMode: container.NetworkMode(*flNetMode),
RestartPolicy: restartPolicy,
}
networkingConfig := &network.NetworkingConfig{
EndpointsConfig: make(map[string]*network.EndpointSettings),
}
if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 {
epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
if epConfig == nil {
epConfig = &network.EndpointSettings{}
}
epConfig.Links = make([]string, len(hostConfig.Links))
copy(epConfig.Links, hostConfig.Links)
networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
}
if *flMinute == "0" && *flHour == "0" && *flDom == "*" && *flDow == "*" && *flMonth == "*" {
return fmt.Errorf("must specify at least one schedule")
}
sv := types.Cron{
ContainerName: *flContainerName,
Schedule: *flMinute + " " + *flHour + " " + *flDom + " " + *flMonth + " " + *flDow,
OwnerEmail: *flMailTo,
Config: config,
HostConfig: hostConfig,
NetConfig: networkingConfig,
AccessKey: *flAccessKey,
SecretKey: *flSecretKey,
MailPolicy: *flMailPolicy,
}
_, err = cli.client.CronCreate(context.Background(), *flName, sv)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "Cron %s is created.\n", *flName)
return nil
}
// CmdCronDelete deletes one or more crons
//
// Usage: hyper cron rm cron [cron...]
func (cli *DockerCli) CmdCronRm(args ...string) error {
cmd := Cli.Subcmd("cron rm", []string{"cron [cron...]"}, "Remove one or more cron job", false)
cmd.Require(flag.Min, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
status := 0
for _, sn := range cmd.Args() {
if err := cli.client.CronDelete(context.Background(), sn); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
fmt.Fprintf(cli.out, "%s\n", sn)
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
// CmdCronLs lists all the crons
//
// Usage: hyper cron ls [OPTIONS]
func (cli *DockerCli) CmdCronLs(args ...string) error {
cmd := Cli.Subcmd("cron ls", nil, "Lists all crons", true)
flFilter := ropts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
// Consolidate all filter flags, and sanity check them early.
// They'll get process after get response from server.
cronFilterArgs := filters.NewArgs()
for _, f := range flFilter.GetAll() {
if cronFilterArgs, err = filters.ParseFlag(f, cronFilterArgs); err != nil {
return err
}
}
options := types.CronListOptions{
Filters: cronFilterArgs,
}
crons, err := cli.client.CronList(context.Background(), options)
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "Name\tSchedule\tImage\tCommand\n")
for _, cron := range crons {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", cron.Name, cron.Schedule, cron.Config.Image, strings.Join([]string(cron.Config.Cmd), " "))
}
w.Flush()
return nil
}
// CmdCronInspect
//
// Usage: hyper cron inspect [OPTIONS] CRON [CRON...]
func (cli *DockerCli) CmdCronInspect(args ...string) error {
cmd := Cli.Subcmd("cron inspect", []string{"cron [cron...]"}, "Display detailed information on the given cron", 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 err
}
ctx := context.Background()
inspectSearcher := func(name string) (interface{}, []byte, error) {
i, err := cli.client.CronInspect(ctx, name)
return i, nil, err
}
return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
}
// CmdCronHistory
//
// Usage: hyper cron history [OPTIONS] CRON
func (cli *DockerCli) CmdCronHistory(args ...string) error {
cmd := Cli.Subcmd("cron history", []string{"cron"}, "Show the execution history (last 100) of a cron job", true)
flSince := cmd.String([]string{"-since"}, "", "Show history since timestamp")
flTail := cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the history")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if err := cmd.Parse(args); err != nil {
return err
}
ctx := context.Background()
name := cmd.Args()[0]
cronHistory, err := cli.client.CronHistory(ctx, name, *flSince, *flTail)
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "Container\tStart\tEnd\tStatus\tMessage\n")
for _, h := range cronHistory {
status := h.Status
if status == "success" {
status = "done"
} else if status == "error" {
status = "failed"
} else {
status = "-"
}
if h.FinishedAt == 0 {
fmt.Fprintf(w, "%s\t%v\t-\t%s\t%s\n", h.Container, time.Unix(h.StartedAt, 0).UTC(), status, h.Message)
} else {
fmt.Fprintf(w, "%s\t%v\t%v\t%s\t%s\n", h.Container, time.Unix(h.StartedAt, 0).UTC(), time.Unix(h.FinishedAt, 0).UTC(), status, h.Message)
}
}
w.Flush()
return nil
}
func cronUsage() string {
cronCommands := [][]string{
{"create", "Create a cron job"},
{"inspect", "Display detailed information on the given cron"},
{"ls", "List all crons"},
{"history", "Show execution history of a cron job"},
{"rm", "Remove one or more cron job"},
}
help := "Commands:\n"
for _, cmd := range cronCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
}
help += fmt.Sprintf("\nRun 'hyper cron COMMAND --help' for more information on a command.")
return help
}

49
vendor/github.com/hyperhq/hypercli/api/client/diff.go generated vendored Normal file
View File

@@ -0,0 +1,49 @@
package client
import (
"fmt"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/pkg/archive"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdDiff shows changes on a container's filesystem.
//
// Each changed file is printed on a separate line, prefixed with a single
// character that indicates the status of the file: C (modified), A (added),
// or D (deleted).
//
// Usage: docker diff CONTAINER
func (cli *DockerCli) Diff(args ...string) error {
cmd := Cli.Subcmd("diff", []string{"CONTAINER"}, Cli.DockerCommands["diff"].Description, true)
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
if cmd.Arg(0) == "" {
return fmt.Errorf("Container name cannot be empty")
}
changes, err := cli.client.ContainerDiff(context.Background(), cmd.Arg(0))
if err != nil {
return err
}
for _, change := range changes {
var kind string
switch change.Kind {
case archive.ChangeModify:
kind = "C"
case archive.ChangeAdd:
kind = "A"
case archive.ChangeDelete:
kind = "D"
}
fmt.Fprintf(cli.out, "%s %s\n", kind, change.Path)
}
return nil
}

102
vendor/github.com/hyperhq/hypercli/api/client/events.go generated vendored Normal file
View File

@@ -0,0 +1,102 @@
package client
import (
"crypto/tls"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"github.com/docker/engine-api/types/events"
"github.com/gorilla/websocket"
signutil "github.com/hyperhq/websocket-client/go/util"
"golang.org/x/net/context"
)
// Events returns a stream of events in the daemon. It's up to the caller to close the stream
// by cancelling the context. Once the stream has been completely read an io.EOF error will
// be sent over the error channel. If an error is sent all processing will be stopped. It's up
// to the caller to reopen the stream in the event of an error by reinvoking this method.
func (cli *DockerCli) Events(ctx context.Context) (<-chan events.Message, <-chan error) {
messages := make(chan events.Message)
errs := make(chan error, 1)
go func() {
hostUrl, err := url.Parse(cli.host)
if err != nil {
errs <- err
return
}
cloudConfig, existed := cli.configFile.CloudConfig[cli.host]
if !existed {
errs <- errors.New("Please specify 'accessKey' and 'secretKey'!")
return
}
// TODO: add filter when we have other type event.
var u = url.URL{Scheme: "wss", Host: hostUrl.Host, Path: "/events/ws"}
// add sign to header
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
errs <- err
return
}
req.URL = &u
req = signutil.Sign4(cloudConfig.AccessKey, cloudConfig.SecretKey, req)
// connect to websocket server
config := &tls.Config{
InsecureSkipVerify: true,
}
dialer := websocket.Dialer{
TLSClientConfig: config,
}
ws, resp, err := dialer.Dial(u.String(), req.Header)
if err != nil {
errs <- err
return
}
defer ws.Close()
if resp.ContentLength > 0 {
defer resp.Body.Close()
ioutil.ReadAll(resp.Body)
}
// process websocket message
for {
_, message, err := ws.ReadMessage()
if err != nil {
errs <- err
return
}
select {
case <-ctx.Done():
errs <- ctx.Err()
return
default:
var event events.Message
err := json.Unmarshal([]byte(message), &event)
if err != nil {
errs <- err
return
}
select {
case messages <- event:
case <-ctx.Done():
errs <- ctx.Err()
return
}
}
}
}()
return messages, errs
}

209
vendor/github.com/hyperhq/hypercli/api/client/exec.go generated vendored Normal file
View File

@@ -0,0 +1,209 @@
package client
import (
"fmt"
"io"
"strings"
"time"
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
"github.com/docker/engine-api/types"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/promise"
)
// CmdExec runs a command in a running container.
//
// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
func (cli *DockerCli) CmdExec(args ...string) error {
cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true)
detachKeys := cmd.String([]string{}, "", "Override the key sequence for detaching a container")
execConfig, err := ParseExec(cmd, args)
container := cmd.Arg(0)
// just in case the ParseExec does not exit
if container == "" || err != nil {
return Cli.StatusError{StatusCode: 1}
}
if *detachKeys != "" {
cli.configFile.DetachKeys = *detachKeys
}
// Send client escape keys
execConfig.DetachKeys = cli.configFile.DetachKeys
ctx := context.Background()
response, err := cli.client.ContainerExecCreate(ctx, container, *execConfig)
if err != nil {
return err
}
execID := response.ID
if execID == "" {
fmt.Fprintf(cli.out, "exec ID empty")
return nil
}
//Temp struct for execStart so that we don't need to transfer all the execConfig
if !execConfig.Detach {
if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
return err
}
} else {
execStartCheck := types.ExecStartCheck{
Detach: execConfig.Detach,
Tty: execConfig.Tty,
}
if err := cli.client.ContainerExecStart(ctx, execID, execStartCheck); err != nil {
return err
}
// For now don't print this - wait for when we support exec wait()
// fmt.Fprintf(cli.out, "%s\n", execID)
return nil
}
// Interactive exec requested.
var (
out, stderr io.Writer
in io.ReadCloser
errCh chan error
)
if execConfig.AttachStdin {
in = cli.in
}
if execConfig.AttachStdout {
out = cli.out
}
if execConfig.AttachStderr {
if execConfig.Tty {
stderr = cli.out
} else {
stderr = cli.err
}
}
resp, err := cli.client.ContainerExecAttach(ctx, execID, *execConfig)
if err != nil {
return err
}
defer resp.Close()
if in != nil && execConfig.Tty {
if err := cli.setRawTerminal(); err != nil {
return err
}
defer cli.restoreTerminal(in)
}
errCh = promise.Go(func() error {
return cli.holdHijackedConnection(execConfig.Tty, in, out, stderr, resp)
})
if execConfig.Tty && cli.isTerminalIn {
if err := cli.monitorTtySize(ctx, execID, true); err != nil {
fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
}
}
if err := <-errCh; err != nil {
logrus.Debugf("Error hijack: %s", err)
return err
}
var status int
if _, status, err = getExecExitCode(ctx, cli, execID); err != nil {
return err
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
// ParseExec parses the specified args for the specified command and generates
// an ExecConfig from it.
// If the minimal number of specified args is not right or if specified args are
// not valid, it will return an error.
func ParseExec(cmd *flag.FlagSet, args []string) (*types.ExecConfig, error) {
var (
flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background")
flUser = cmd.String([]string{}, "", "Username or UID (format: <name|uid>[:<group|gid>])")
flPrivileged = cmd.Bool([]string{}, false, "Give extended privileges to the command")
execCmd []string
)
cmd.Require(flag.Min, 2)
if err := cmd.ParseFlags(args, true); err != nil {
return nil, err
}
parsedArgs := cmd.Args()
execCmd = parsedArgs[1:]
execConfig := &types.ExecConfig{
User: *flUser,
Privileged: *flPrivileged,
Tty: *flTty,
Cmd: execCmd,
Detach: *flDetach,
}
// If -d is not set, attach to everything by default
if !*flDetach {
execConfig.AttachStdout = true
execConfig.AttachStderr = true
if *flStdin {
execConfig.AttachStdin = true
}
}
return execConfig, nil
}
func (cli *DockerCli) ExecCmd(ctx context.Context, user, contID string, cmd []string) (string, error) {
execConfig := &types.ExecConfig{
User: user,
Detach: true,
Cmd: cmd,
}
execCreateResponse, err := cli.client.ContainerExecCreate(ctx, contID, *execConfig)
if err != nil {
return "", err
}
execID := execCreateResponse.ID
if execID == "" {
err = fmt.Errorf("Failed to exec %s: Empty exec ID", strings.Join(cmd, " "))
return "", err
}
execStartCheck := types.ExecStartCheck{Detach: execConfig.Detach}
if err := cli.client.ContainerExecStart(ctx, execID, execStartCheck); err != nil {
return "", err
}
return execID, nil
}
func (cli *DockerCli) WaitExec(ctx context.Context, execID string) error {
for {
running, status, err := getExecExitCode(ctx, cli, execID)
switch {
case err != nil:
return err
case running:
time.Sleep(100 * time.Millisecond)
case status != 0:
err = fmt.Errorf("Failed to exec cmd: %d", status)
return err
case status == 0:
return nil
}
}
}

View File

@@ -0,0 +1,130 @@
package client
import (
"fmt"
"io/ioutil"
"testing"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/docker/engine-api/types"
)
type arguments struct {
args []string
}
func TestParseExec(t *testing.T) {
invalids := map[*arguments]error{
&arguments{[]string{"-unknown"}}: fmt.Errorf("flag provided but not defined: -unknown"),
&arguments{[]string{"-u"}}: fmt.Errorf("flag needs an argument: -u"),
&arguments{[]string{"--user"}}: fmt.Errorf("flag needs an argument: --user"),
}
valids := map[*arguments]*types.ExecConfig{
&arguments{
[]string{"container", "command"},
}: {
Container: "container",
Cmd: []string{"command"},
AttachStdout: true,
AttachStderr: true,
},
&arguments{
[]string{"container", "command1", "command2"},
}: {
Container: "container",
Cmd: []string{"command1", "command2"},
AttachStdout: true,
AttachStderr: true,
},
&arguments{
[]string{"-i", "-t", "-u", "uid", "container", "command"},
}: {
User: "uid",
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
Tty: true,
Container: "container",
Cmd: []string{"command"},
},
&arguments{
[]string{"-d", "container", "command"},
}: {
AttachStdin: false,
AttachStdout: false,
AttachStderr: false,
Detach: true,
Container: "container",
Cmd: []string{"command"},
},
&arguments{
[]string{"-t", "-i", "-d", "container", "command"},
}: {
AttachStdin: false,
AttachStdout: false,
AttachStderr: false,
Detach: true,
Tty: true,
Container: "container",
Cmd: []string{"command"},
},
}
for invalid, expectedError := range invalids {
cmd := flag.NewFlagSet("exec", flag.ContinueOnError)
cmd.ShortUsage = func() {}
cmd.SetOutput(ioutil.Discard)
_, err := ParseExec(cmd, invalid.args)
if err == nil || err.Error() != expectedError.Error() {
t.Fatalf("Expected an error [%v] for %v, got %v", expectedError, invalid, err)
}
}
for valid, expectedExecConfig := range valids {
cmd := flag.NewFlagSet("exec", flag.ContinueOnError)
cmd.ShortUsage = func() {}
cmd.SetOutput(ioutil.Discard)
execConfig, err := ParseExec(cmd, valid.args)
if err != nil {
t.Fatal(err)
}
if !compareExecConfig(expectedExecConfig, execConfig) {
t.Fatalf("Expected [%v] for %v, got [%v]", expectedExecConfig, valid, execConfig)
}
}
}
func compareExecConfig(config1 *types.ExecConfig, config2 *types.ExecConfig) bool {
if config1.AttachStderr != config2.AttachStderr {
return false
}
if config1.AttachStdin != config2.AttachStdin {
return false
}
if config1.AttachStdout != config2.AttachStdout {
return false
}
if config1.Container != config2.Container {
return false
}
if config1.Detach != config2.Detach {
return false
}
if config1.Privileged != config2.Privileged {
return false
}
if config1.Tty != config2.Tty {
return false
}
if config1.User != config2.User {
return false
}
if len(config1.Cmd) != len(config2.Cmd) {
return false
}
for index, value := range config1.Cmd {
if value != config2.Cmd[index] {
return false
}
}
return true
}

View File

@@ -0,0 +1,42 @@
package client
import (
"errors"
"io"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdExport exports a filesystem as a tar archive.
//
// The tar archive is streamed to STDOUT by default or written to a file.
//
// Usage: docker export [OPTIONS] CONTAINER
func (cli *DockerCli) Export(args ...string) error {
cmd := Cli.Subcmd("export", []string{"CONTAINER"}, Cli.DockerCommands["export"].Description, true)
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
if *outfile == "" && cli.isTerminalOut {
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
}
responseBody, err := cli.client.ContainerExport(context.Background(), cmd.Arg(0))
if err != nil {
return err
}
defer responseBody.Close()
if *outfile == "" {
_, err := io.Copy(cli.out, responseBody)
return err
}
return copyToFile(*outfile, responseBody)
}

295
vendor/github.com/hyperhq/hypercli/api/client/fip.go generated vendored Normal file
View File

@@ -0,0 +1,295 @@
package client
import (
"bufio"
"fmt"
"log"
"os"
"strings"
"text/tabwriter"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
var warnMessage = "Please note that Floating IP (FIP) is billed monthly. The billing begins when a new IP is allocated, ends when it is released. Partial month is treated as a entire month. Do you want to continue?"
// CmdFip is the parent subcommand for all fip commands
//
// Usage: docker fip <COMMAND> [OPTIONS]
func (cli *DockerCli) CmdFip(args ...string) error {
cmd := Cli.Subcmd("fip", []string{"COMMAND [OPTIONS]"}, fipUsage(), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdNetworkCreate creates a new fip with a given name
//
// Usage: docker fip create [OPTIONS] COUNT
func (cli *DockerCli) CmdFipAllocate(args ...string) error {
cmd := Cli.Subcmd("fip allocate", []string{"COUNT"}, "Creates some new floating IPs by the user", false)
flAvailable := cmd.Bool([]string{"-pick"}, false, "Pick an available floating IP if have")
flForce := cmd.Bool([]string{"y", "-yes"}, false, "Agree to allocate floating IP, will not show prompt")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
if *flAvailable == true {
fipFilterArgs, _ := filters.FromParam("dangling=true")
options := types.NetworkListOptions{
Filters: fipFilterArgs,
}
fips, err := cli.client.FipList(context.Background(), options)
if err == nil {
for _, fip := range fips {
if fip["container"] == "" && fip["service"] == "" {
fmt.Fprintf(cli.out, "%s\n", fip["fip"])
return nil
}
}
}
}
if *flForce == false {
if askForConfirmation(warnMessage) == false {
return nil
}
}
fips, err := cli.client.FipAllocate(context.Background(), cmd.Arg(0))
if err != nil {
return err
}
for _, ip := range fips {
fmt.Fprintf(cli.out, "%s\n", ip)
}
return nil
}
// CmdFipRelease deletes one or more fips
//
// Usage: docker fip release FIP [FIP...]
func (cli *DockerCli) CmdFipRelease(args ...string) error {
cmd := Cli.Subcmd("fip release", []string{"FIP [FIP...]"}, "Release one or more fips", false)
cmd.Require(flag.Min, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
status := 0
for _, ip := range cmd.Args() {
if err := cli.client.FipRelease(context.Background(), ip); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
// CmdFipAttach connects a container to a floating IP
//
// Usage: docker fip attach [OPTIONS] <FIP> <CONTAINER>
func (cli *DockerCli) CmdFipAttach(args ...string) error {
cmd := Cli.Subcmd("fip attach", []string{"FIP CONTAINER"}, "Connects a container to a floating IP", false)
flForce := cmd.Bool([]string{"f", "-force"}, false, "Deattach that FIP and attach it to this container")
cmd.Require(flag.Min, 2)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
if *flForce {
filter, _ := filters.FromParam("dangling=false")
options := types.NetworkListOptions{
Filters: filter,
}
fips, err := cli.client.FipList(context.Background(), options)
if err != nil {
return err
}
for _, fip := range fips {
if ip := fip["fip"]; ip == cmd.Arg(0) {
if fip["container"] != "" {
cli.client.FipDetach(context.Background(), fip["container"])
} else if fip["service"] != "" {
ip = ""
sv := types.ServiceUpdate{
FIP: &ip,
}
cli.client.ServiceUpdate(context.Background(), fip["service"], sv)
}
break
}
}
}
return cli.client.FipAttach(context.Background(), cmd.Arg(0), cmd.Arg(1))
}
// CmdFipDetach disconnects a container from a floating IP
//
// Usage: docker fip detach <CONTAINER>
func (cli *DockerCli) CmdFipDetach(args ...string) error {
cmd := Cli.Subcmd("fip detach", []string{"CONTAINER"}, "Disconnects container from a floating IP", false)
//force := cmd.Bool([]string{"f", "-force"}, false, "Force the container to disconnect from a floating IP")
cmd.Require(flag.Exact, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
ip, err := cli.client.FipDetach(context.Background(), cmd.Arg(0))
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", ip)
return nil
}
// CmdFipLs lists all the fips
//
// Usage: docker fip ls [OPTIONS]
func (cli *DockerCli) CmdFipLs(args ...string) error {
cmd := Cli.Subcmd("fip ls", nil, "Lists fips", true)
flFilter := opts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
// Consolidate all filter flags, and sanity check them early.
// They'll get process after get response from server.
fipFilterArgs := filters.NewArgs()
for _, f := range flFilter.GetAll() {
if fipFilterArgs, err = filters.ParseFlag(f, fipFilterArgs); err != nil {
return err
}
}
options := types.NetworkListOptions{
Filters: fipFilterArgs,
}
fips, err := cli.client.FipList(context.Background(), options)
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "Floating IP\tName\tContainer\tService\n")
for _, fip := range fips {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", fip["fip"], fip["name"], fip["container"], fip["service"])
}
w.Flush()
return nil
}
func (cli *DockerCli) CmdFipName(args ...string) error {
cmd := Cli.Subcmd("fip name", []string{"FIP [NAME]"}, "Set a name for a floating IP", false)
//force := cmd.Bool([]string{"f", "-force"}, false, "Force the container to disconnect from a floating IP")
cmd.Require(flag.Min, 1)
cmd.Require(flag.Max, 2)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
err := cli.client.FipName(context.Background(), cmd.Arg(0), cmd.Arg(1))
if err != nil {
return err
}
return nil
}
func fipUsage() string {
fipCommands := [][]string{
{"allocate", "Allocate a or some IPs"},
{"attach", "Attach floating IP to container"},
{"detach", "Detach floating IP from container"},
{"ls", "List all floating IPs"},
{"release", "Release a floating IP"},
{"name", "Name a floating IP"},
}
help := "Commands:\n"
for _, cmd := range fipCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
}
help += fmt.Sprintf("\nRun 'hyper fip COMMAND --help' for more information on a command.")
return help
}
// Allocate and attach a fip
func (cli *DockerCli) associateNewFip(ctx context.Context, contID string) (string, error) {
fips, err := cli.client.FipAllocate(ctx, "1")
if err != nil {
return "", err
}
for _, ip := range fips {
err = cli.client.FipAttach(ctx, ip, contID)
if err != nil {
go func() {
cli.client.FipRelease(ctx, ip)
}()
return "", err
}
return ip, nil
}
return "", fmt.Errorf("Server failed to create new fip")
}
// Release a fip
func (cli *DockerCli) releaseFip(ctx context.Context, ip string) error {
return cli.client.FipRelease(ctx, ip)
}
// Detach and release a fip
func (cli *DockerCli) releaseContainerFip(ctx context.Context, contID string) error {
ip, err := cli.client.FipDetach(ctx, contID)
if err != nil {
return err
}
return cli.client.FipRelease(ctx, ip)
}
// askForConfirmation asks the user for confirmation. A user must type in "yes" or "no" and
// then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as
// confirmations. If the input is not recognized, it will ask again. The function does not return
// until it gets a valid response from the user.
func askForConfirmation(s string) bool {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Printf("%s [y/n]: ", s)
response, err := reader.ReadString('\n')
if err != nil {
log.Fatal(err)
}
response = strings.ToLower(strings.TrimSpace(response))
if response == "y" || response == "yes" {
return true
} else if response == "n" || response == "no" {
return false
}
}
}

View File

@@ -0,0 +1,264 @@
package formatter
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/docker/engine-api/types"
"github.com/docker/go-units"
"github.com/hyperhq/hypercli/api"
"github.com/hyperhq/hypercli/pkg/stringid"
"github.com/hyperhq/hypercli/pkg/stringutils"
)
const (
tableKey = "table"
fipLabel = "sh.hyper.fip"
containerIDHeader = "CONTAINER ID"
imageHeader = "IMAGE"
namesHeader = "NAMES"
commandHeader = "COMMAND"
createdSinceHeader = "CREATED"
createdAtHeader = "CREATED AT"
runningForHeader = "CREATED"
statusHeader = "STATUS"
portsHeader = "PORTS"
sizeHeader = "SIZE"
labelsHeader = "LABELS"
imageIDHeader = "IMAGE ID"
repositoryHeader = "REPOSITORY"
tagHeader = "TAG"
digestHeader = "DIGEST"
fipHeader = "PUBLIC IP"
volumeNameHeader = "NAME"
volumeSizeHeader = "SIZE"
volumeDriverHeader = "DRIVER"
volumeContainerHeader = "CONTAINER"
)
type containerContext struct {
baseSubContext
trunc bool
c types.Container
}
func (c *containerContext) ID() string {
c.addHeader(containerIDHeader)
if c.trunc {
return stringid.TruncateID(c.c.ID)
}
return c.c.ID
}
func (c *containerContext) Names() string {
c.addHeader(namesHeader)
names := stripNamePrefix(c.c.Names)
if c.trunc {
for _, name := range names {
if len(strings.Split(name, "/")) == 1 {
names = []string{name}
break
}
}
}
return strings.Join(names, ",")
}
func (c *containerContext) Image() string {
c.addHeader(imageHeader)
if c.c.Image == "" {
return "<no image>"
}
if c.trunc {
if trunc := stringid.TruncateID(c.c.ImageID); trunc == stringid.TruncateID(c.c.Image) {
return trunc
}
}
return c.c.Image
}
func (c *containerContext) Command() string {
c.addHeader(commandHeader)
command := c.c.Command
if c.trunc {
command = stringutils.Truncate(command, 20)
}
return strconv.Quote(command)
}
func (c *containerContext) CreatedAt() string {
c.addHeader(createdAtHeader)
return time.Unix(int64(c.c.Created), 0).String()
}
func (c *containerContext) RunningFor() string {
c.addHeader(runningForHeader)
createdAt := time.Unix(int64(c.c.Created), 0)
return units.HumanDuration(time.Now().UTC().Sub(createdAt))
}
func (c *containerContext) Ports() string {
c.addHeader(portsHeader)
return api.DisplayablePorts(c.c.Ports)
}
func (c *containerContext) Status() string {
c.addHeader(statusHeader)
return c.c.Status
}
func (c *containerContext) Size() string {
c.addHeader(sizeHeader)
srw := units.HumanSize(float64(c.c.SizeRw))
sv := units.HumanSize(float64(c.c.SizeRootFs))
sf := srw
if c.c.SizeRootFs > 0 {
sf = fmt.Sprintf("%s (virtual %s)", srw, sv)
}
return sf
}
func (c *containerContext) Labels() string {
c.addHeader(labelsHeader)
if c.c.Labels == nil {
return ""
}
var joinLabels []string
for k, v := range c.c.Labels {
joinLabels = append(joinLabels, fmt.Sprintf("%s=%s", k, v))
}
return strings.Join(joinLabels, ",")
}
func (c *containerContext) Label(name string) string {
n := strings.Split(name, ".")
r := strings.NewReplacer("-", " ", "_", " ")
h := r.Replace(n[len(n)-1])
c.addHeader(h)
if c.c.Labels == nil {
return ""
}
return c.c.Labels[name]
}
func (c *containerContext) PublicIP() string {
c.addHeader(fipHeader)
if c.c.Labels == nil {
return ""
}
return c.c.Labels[fipLabel]
}
type imageContext struct {
baseSubContext
trunc bool
i types.Image
repo string
tag string
digest string
}
func (c *imageContext) ID() string {
c.addHeader(imageIDHeader)
if c.trunc {
return stringid.TruncateID(c.i.ID)
}
return c.i.ID
}
func (c *imageContext) Repository() string {
c.addHeader(repositoryHeader)
return c.repo
}
func (c *imageContext) Tag() string {
c.addHeader(tagHeader)
return c.tag
}
func (c *imageContext) Digest() string {
c.addHeader(digestHeader)
return c.digest
}
func (c *imageContext) CreatedSince() string {
c.addHeader(createdSinceHeader)
createdAt := time.Unix(int64(c.i.Created), 0)
return units.HumanDuration(time.Now().UTC().Sub(createdAt))
}
func (c *imageContext) CreatedAt() string {
c.addHeader(createdAtHeader)
return time.Unix(int64(c.i.Created), 0).String()
}
func (c *imageContext) Size() string {
c.addHeader(sizeHeader)
return units.HumanSize(float64(c.i.Size))
}
type volumeContext struct {
baseSubContext
i types.Volume
}
func (c *volumeContext) Name() string {
c.addHeader(volumeNameHeader)
return c.i.Name
}
func (c *volumeContext) Size() string {
c.addHeader(volumeSizeHeader)
size := c.i.Labels["size"]
return size + " GB"
}
func (c *volumeContext) Driver() string {
c.addHeader(volumeDriverHeader)
return c.i.Driver
}
func (c *volumeContext) Container() string {
c.addHeader(volumeContainerHeader)
container := c.i.Labels["container"]
return container
}
type subContext interface {
fullHeader() string
addHeader(header string)
}
type baseSubContext struct {
header []string
}
func (c *baseSubContext) fullHeader() string {
if c.header == nil {
return ""
}
return strings.Join(c.header, "\t")
}
func (c *baseSubContext) addHeader(header string) {
if c.header == nil {
c.header = []string{}
}
c.header = append(c.header, strings.ToUpper(header))
}
func stripNamePrefix(ss []string) []string {
for i, s := range ss {
ss[i] = s[1:]
}
return ss
}

View File

@@ -0,0 +1,192 @@
package formatter
import (
"reflect"
"strings"
"testing"
"time"
"github.com/hyperhq/hypercli/pkg/stringid"
"github.com/docker/engine-api/types"
)
func TestContainerPsContext(t *testing.T) {
containerID := stringid.GenerateRandomID()
unix := time.Now().Unix()
var ctx containerContext
cases := []struct {
container types.Container
trunc bool
expValue string
expHeader string
call func() string
}{
{types.Container{ID: containerID}, true, stringid.TruncateID(containerID), containerIDHeader, ctx.ID},
{types.Container{ID: containerID}, false, containerID, containerIDHeader, ctx.ID},
{types.Container{Names: []string{"/foobar_baz"}}, true, "foobar_baz", namesHeader, ctx.Names},
{types.Container{Image: "ubuntu"}, true, "ubuntu", imageHeader, ctx.Image},
{types.Container{Image: "verylongimagename"}, true, "verylongimagename", imageHeader, ctx.Image},
{types.Container{Image: "verylongimagename"}, false, "verylongimagename", imageHeader, ctx.Image},
{types.Container{
Image: "a5a665ff33eced1e0803148700880edab4",
ImageID: "a5a665ff33eced1e0803148700880edab4269067ed77e27737a708d0d293fbf5",
},
true,
"a5a665ff33ec",
imageHeader,
ctx.Image,
},
{types.Container{
Image: "a5a665ff33eced1e0803148700880edab4",
ImageID: "a5a665ff33eced1e0803148700880edab4269067ed77e27737a708d0d293fbf5",
},
false,
"a5a665ff33eced1e0803148700880edab4",
imageHeader,
ctx.Image,
},
{types.Container{Image: ""}, true, "<no image>", imageHeader, ctx.Image},
{types.Container{Command: "sh -c 'ls -la'"}, true, `"sh -c 'ls -la'"`, commandHeader, ctx.Command},
{types.Container{Created: unix}, true, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
{types.Container{Ports: []types.Port{{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}}}, true, "8080/tcp", portsHeader, ctx.Ports},
{types.Container{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status},
{types.Container{SizeRw: 10}, true, "10 B", sizeHeader, ctx.Size},
{types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10 B (virtual 20 B)", sizeHeader, ctx.Size},
{types.Container{}, true, "", labelsHeader, ctx.Labels},
{types.Container{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", labelsHeader, ctx.Labels},
{types.Container{Created: unix}, true, "Less than a second", runningForHeader, ctx.RunningFor},
}
for _, c := range cases {
ctx = containerContext{c: c.container, trunc: c.trunc}
v := c.call()
if strings.Contains(v, ",") {
compareMultipleValues(t, v, c.expValue)
} else if v != c.expValue {
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
}
h := ctx.fullHeader()
if h != c.expHeader {
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
}
}
c1 := types.Container{Labels: map[string]string{"com.docker.swarm.swarm-id": "33", "com.docker.swarm.node_name": "ubuntu"}}
ctx = containerContext{c: c1, trunc: true}
sid := ctx.Label("com.docker.swarm.swarm-id")
node := ctx.Label("com.docker.swarm.node_name")
if sid != "33" {
t.Fatalf("Expected 33, was %s\n", sid)
}
if node != "ubuntu" {
t.Fatalf("Expected ubuntu, was %s\n", node)
}
h := ctx.fullHeader()
if h != "SWARM ID\tNODE NAME" {
t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
}
c2 := types.Container{}
ctx = containerContext{c: c2, trunc: true}
label := ctx.Label("anything.really")
if label != "" {
t.Fatalf("Expected an empty string, was %s", label)
}
ctx = containerContext{c: c2, trunc: true}
fullHeader := ctx.fullHeader()
if fullHeader != "" {
t.Fatalf("Expected fullHeader to be empty, was %s", fullHeader)
}
}
func TestImagesContext(t *testing.T) {
imageID := stringid.GenerateRandomID()
unix := time.Now().Unix()
var ctx imageContext
cases := []struct {
imageCtx imageContext
expValue string
expHeader string
call func() string
}{
{imageContext{
i: types.Image{ID: imageID},
trunc: true,
}, stringid.TruncateID(imageID), imageIDHeader, ctx.ID},
{imageContext{
i: types.Image{ID: imageID},
trunc: false,
}, imageID, imageIDHeader, ctx.ID},
{imageContext{
i: types.Image{Size: 10},
trunc: true,
}, "10 B", sizeHeader, ctx.Size},
{imageContext{
i: types.Image{Created: unix},
trunc: true,
}, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
// FIXME
// {imageContext{
// i: types.Image{Created: unix},
// trunc: true,
// }, units.HumanDuration(time.Unix(unix, 0)), createdSinceHeader, ctx.CreatedSince},
{imageContext{
i: types.Image{},
repo: "busybox",
}, "busybox", repositoryHeader, ctx.Repository},
{imageContext{
i: types.Image{},
tag: "latest",
}, "latest", tagHeader, ctx.Tag},
{imageContext{
i: types.Image{},
digest: "sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a",
}, "sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a", digestHeader, ctx.Digest},
}
for _, c := range cases {
ctx = c.imageCtx
v := c.call()
if strings.Contains(v, ",") {
compareMultipleValues(t, v, c.expValue)
} else if v != c.expValue {
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
}
h := ctx.fullHeader()
if h != c.expHeader {
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
}
}
}
func compareMultipleValues(t *testing.T, value, expected string) {
// comma-separated values means probably a map input, which won't
// be guaranteed to have the same order as our expected value
// We'll create maps and use reflect.DeepEquals to check instead:
entriesMap := make(map[string]string)
expMap := make(map[string]string)
entries := strings.Split(value, ",")
expectedEntries := strings.Split(expected, ",")
for _, entry := range entries {
keyval := strings.Split(entry, "=")
entriesMap[keyval[0]] = keyval[1]
}
for _, expected := range expectedEntries {
keyval := strings.Split(expected, "=")
expMap[keyval[0]] = keyval[1]
}
if !reflect.DeepEqual(expMap, entriesMap) {
t.Fatalf("Expected entries: %v, got: %v", expected, value)
}
}

View File

@@ -0,0 +1,302 @@
package formatter
import (
"bytes"
"fmt"
"io"
"strings"
"text/tabwriter"
"text/template"
"github.com/docker/engine-api/types"
"github.com/hyperhq/hypercli/reference"
)
const (
tableFormatKey = "table"
rawFormatKey = "raw"
defaultContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}} ago\t{{.Status}}\t{{.Ports}}\t{{.Names}}\t{{.PublicIP}}"
defaultImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}"
defaultImageTableFormatWithDigest = "table {{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}"
defaultVolumeTableFormat = "table {{.Driver}}\t{{.Name}}\t{{.Size}}\t{{.Container}}"
defaultQuietFormat = "{{.ID}}"
)
// Context contains information required by the formatter to print the output as desired.
type Context struct {
// Output is the output stream to which the formatted string is written.
Output io.Writer
// Format is used to choose raw, table or custom format for the output.
Format string
// Quiet when set to true will simply print minimal information.
Quiet bool
// Trunc when set to true will truncate the output of certain fields such as Container ID.
Trunc bool
// internal element
table bool
finalFormat string
header string
buffer *bytes.Buffer
}
func (c *Context) preformat() {
c.finalFormat = c.Format
if strings.HasPrefix(c.Format, tableKey) {
c.table = true
c.finalFormat = c.finalFormat[len(tableKey):]
}
c.finalFormat = strings.Trim(c.finalFormat, " ")
r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
c.finalFormat = r.Replace(c.finalFormat)
}
func (c *Context) parseFormat() (*template.Template, error) {
tmpl, err := template.New("").Parse(c.finalFormat)
if err != nil {
c.buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
c.buffer.WriteTo(c.Output)
}
return tmpl, err
}
func (c *Context) postformat(tmpl *template.Template, subContext subContext) {
if c.table {
if len(c.header) == 0 {
// if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template
tmpl.Execute(bytes.NewBufferString(""), subContext)
c.header = subContext.fullHeader()
}
t := tabwriter.NewWriter(c.Output, 20, 1, 3, ' ', 0)
t.Write([]byte(c.header))
t.Write([]byte("\n"))
c.buffer.WriteTo(t)
t.Flush()
} else {
c.buffer.WriteTo(c.Output)
}
}
func (c *Context) contextFormat(tmpl *template.Template, subContext subContext) error {
if err := tmpl.Execute(c.buffer, subContext); err != nil {
c.buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err))
c.buffer.WriteTo(c.Output)
return err
}
if c.table && len(c.header) == 0 {
c.header = subContext.fullHeader()
}
c.buffer.WriteString("\n")
return nil
}
// ContainerContext contains container specific information required by the formater, encapsulate a Context struct.
type ContainerContext struct {
Context
// Size when set to true will display the size of the output.
Size bool
// Containers
Containers []types.Container
}
// ImageContext contains image specific information required by the formater, encapsulate a Context struct.
type ImageContext struct {
Context
Digest bool
// Images
Images []types.Image
}
type VolumeContext struct {
Context
// Volumes
Volumes []*types.Volume
}
func (ctx ContainerContext) Write() {
switch ctx.Format {
case tableFormatKey:
ctx.Format = defaultContainerTableFormat
if ctx.Quiet {
ctx.Format = defaultQuietFormat
}
case rawFormatKey:
if ctx.Quiet {
ctx.Format = `container_id: {{.ID}}`
} else {
ctx.Format = `container_id: {{.ID}}
image: {{.Image}}
command: {{.Command}}
created_at: {{.CreatedAt}}
status: {{.Status}}
names: {{.Names}}
public_ip: {{.PublicIP}}
labels: {{.Labels}}
ports: {{.Ports}}
`
if ctx.Size {
ctx.Format += `size: {{.Size}}
`
}
}
}
ctx.buffer = bytes.NewBufferString("")
ctx.preformat()
if ctx.table && ctx.Size {
ctx.finalFormat += "\t{{.Size}}"
}
tmpl, err := ctx.parseFormat()
if err != nil {
return
}
for _, container := range ctx.Containers {
containerCtx := &containerContext{
trunc: ctx.Trunc,
c: container,
}
err = ctx.contextFormat(tmpl, containerCtx)
if err != nil {
return
}
}
ctx.postformat(tmpl, &containerContext{})
}
func (ctx ImageContext) Write() {
switch ctx.Format {
case tableFormatKey:
ctx.Format = defaultImageTableFormat
if ctx.Digest {
ctx.Format = defaultImageTableFormatWithDigest
}
if ctx.Quiet {
ctx.Format = defaultQuietFormat
}
case rawFormatKey:
if ctx.Quiet {
ctx.Format = `image_id: {{.ID}}`
} else {
if ctx.Digest {
ctx.Format = `repository: {{ .Repository }}
tag: {{.Tag}}
digest: {{.Digest}}
image_id: {{.ID}}
created_at: {{.CreatedAt}}
virtual_size: {{.Size}}
`
} else {
ctx.Format = `repository: {{ .Repository }}
tag: {{.Tag}}
image_id: {{.ID}}
created_at: {{.CreatedAt}}
virtual_size: {{.Size}}
`
}
}
}
ctx.buffer = bytes.NewBufferString("")
ctx.preformat()
if ctx.table && ctx.Digest && !strings.Contains(ctx.Format, "{{.Digest}}") {
ctx.finalFormat += "\t{{.Digest}}"
}
tmpl, err := ctx.parseFormat()
if err != nil {
return
}
for _, image := range ctx.Images {
repoTags := image.RepoTags
repoDigests := image.RepoDigests
if len(repoTags) == 1 && repoTags[0] == "<none>:<none>" && len(repoDigests) == 1 && repoDigests[0] == "<none>@<none>" {
// dangling image - clear out either repoTags or repoDigests so we only show it once below
repoDigests = []string{}
}
// combine the tags and digests lists
tagsAndDigests := append(repoTags, repoDigests...)
for _, repoAndRef := range tagsAndDigests {
repo := "<none>"
tag := "<none>"
digest := "<none>"
if !strings.HasPrefix(repoAndRef, "<none>") {
ref, err := reference.ParseNamed(repoAndRef)
if err != nil {
continue
}
repo = ref.Name()
switch x := ref.(type) {
case reference.Canonical:
digest = x.Digest().String()
case reference.NamedTagged:
tag = x.Tag()
}
}
imageCtx := &imageContext{
trunc: ctx.Trunc,
i: image,
repo: repo,
tag: tag,
digest: digest,
}
err = ctx.contextFormat(tmpl, imageCtx)
if err != nil {
return
}
}
}
ctx.postformat(tmpl, &imageContext{})
}
func (ctx VolumeContext) Write() {
switch ctx.Format {
case tableFormatKey:
ctx.Format = defaultVolumeTableFormat
if ctx.Quiet {
ctx.Format = "{{.Name}}"
}
case rawFormatKey:
if ctx.Quiet {
ctx.Format = `name: {{.Name}}`
} else {
ctx.Format = `name: {{.Name}}
driver: {{.Driver}}
size: {{.Size}}
container: {{.Container}}
`
}
}
ctx.buffer = bytes.NewBufferString("")
ctx.preformat()
tmpl, err := ctx.parseFormat()
if err != nil {
return
}
for _, vol := range ctx.Volumes {
volCtx := &volumeContext{
i: *vol,
}
err = ctx.contextFormat(tmpl, volCtx)
if err != nil {
return
}
}
ctx.postformat(tmpl, &volumeContext{})
}

View File

@@ -0,0 +1,535 @@
package formatter
import (
"bytes"
"fmt"
"testing"
"time"
"github.com/docker/engine-api/types"
)
func TestContainerContextWrite(t *testing.T) {
unixTime := time.Now().AddDate(0, 0, -1).Unix()
expectedTime := time.Unix(unixTime, 0).String()
contexts := []struct {
context ContainerContext
expected string
}{
// Errors
{
ContainerContext{
Context: Context{
Format: "{{InvalidFunction}}",
},
},
`Template parsing error: template: :1: function "InvalidFunction" not defined
`,
},
{
ContainerContext{
Context: Context{
Format: "{{nil}}",
},
},
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
`,
},
// Table Format
{
ContainerContext{
Context: Context{
Format: "table",
},
},
`CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
containerID1 ubuntu "" 24 hours ago foobar_baz
containerID2 ubuntu "" 24 hours ago foobar_bar
`,
},
{
ContainerContext{
Context: Context{
Format: "table {{.Image}}",
},
},
"IMAGE\nubuntu\nubuntu\n",
},
{
ContainerContext{
Context: Context{
Format: "table {{.Image}}",
},
Size: true,
},
"IMAGE SIZE\nubuntu 0 B\nubuntu 0 B\n",
},
{
ContainerContext{
Context: Context{
Format: "table {{.Image}}",
Quiet: true,
},
},
"IMAGE\nubuntu\nubuntu\n",
},
{
ContainerContext{
Context: Context{
Format: "table",
Quiet: true,
},
},
"containerID1\ncontainerID2\n",
},
// Raw Format
{
ContainerContext{
Context: Context{
Format: "raw",
},
},
fmt.Sprintf(`container_id: containerID1
image: ubuntu
command: ""
created_at: %s
status:
names: foobar_baz
labels:
ports:
container_id: containerID2
image: ubuntu
command: ""
created_at: %s
status:
names: foobar_bar
labels:
ports:
`, expectedTime, expectedTime),
},
{
ContainerContext{
Context: Context{
Format: "raw",
},
Size: true,
},
fmt.Sprintf(`container_id: containerID1
image: ubuntu
command: ""
created_at: %s
status:
names: foobar_baz
labels:
ports:
size: 0 B
container_id: containerID2
image: ubuntu
command: ""
created_at: %s
status:
names: foobar_bar
labels:
ports:
size: 0 B
`, expectedTime, expectedTime),
},
{
ContainerContext{
Context: Context{
Format: "raw",
Quiet: true,
},
},
"container_id: containerID1\ncontainer_id: containerID2\n",
},
// Custom Format
{
ContainerContext{
Context: Context{
Format: "{{.Image}}",
},
},
"ubuntu\nubuntu\n",
},
{
ContainerContext{
Context: Context{
Format: "{{.Image}}",
},
Size: true,
},
"ubuntu\nubuntu\n",
},
}
for _, context := range contexts {
containers := []types.Container{
{ID: "containerID1", Names: []string{"/foobar_baz"}, Image: "ubuntu", Created: unixTime},
{ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu", Created: unixTime},
}
out := bytes.NewBufferString("")
context.context.Output = out
context.context.Containers = containers
context.context.Write()
actual := out.String()
if actual != context.expected {
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
}
// Clean buffer
out.Reset()
}
}
func TestContainerContextWriteWithNoContainers(t *testing.T) {
out := bytes.NewBufferString("")
containers := []types.Container{}
contexts := []struct {
context ContainerContext
expected string
}{
{
ContainerContext{
Context: Context{
Format: "{{.Image}}",
Output: out,
},
},
"",
},
{
ContainerContext{
Context: Context{
Format: "table {{.Image}}",
Output: out,
},
},
"IMAGE\n",
},
{
ContainerContext{
Context: Context{
Format: "{{.Image}}",
Output: out,
},
Size: true,
},
"",
},
{
ContainerContext{
Context: Context{
Format: "table {{.Image}}",
Output: out,
},
Size: true,
},
"IMAGE SIZE\n",
},
}
for _, context := range contexts {
context.context.Containers = containers
context.context.Write()
actual := out.String()
if actual != context.expected {
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
}
// Clean buffer
out.Reset()
}
}
func TestImageContextWrite(t *testing.T) {
unixTime := time.Now().AddDate(0, 0, -1).Unix()
expectedTime := time.Unix(unixTime, 0).String()
contexts := []struct {
context ImageContext
expected string
}{
// Errors
{
ImageContext{
Context: Context{
Format: "{{InvalidFunction}}",
},
},
`Template parsing error: template: :1: function "InvalidFunction" not defined
`,
},
{
ImageContext{
Context: Context{
Format: "{{nil}}",
},
},
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
`,
},
// Table Format
{
ImageContext{
Context: Context{
Format: "table",
},
},
`REPOSITORY TAG IMAGE ID CREATED SIZE
image tag1 imageID1 24 hours ago 0 B
image <none> imageID1 24 hours ago 0 B
image tag2 imageID2 24 hours ago 0 B
<none> <none> imageID3 24 hours ago 0 B
`,
},
{
ImageContext{
Context: Context{
Format: "table {{.Repository}}",
},
},
"REPOSITORY\nimage\nimage\nimage\n<none>\n",
},
{
ImageContext{
Context: Context{
Format: "table {{.Repository}}",
},
Digest: true,
},
`REPOSITORY DIGEST
image <none>
image sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
image <none>
<none> <none>
`,
},
{
ImageContext{
Context: Context{
Format: "table {{.Repository}}",
Quiet: true,
},
},
"REPOSITORY\nimage\nimage\nimage\n<none>\n",
},
{
ImageContext{
Context: Context{
Format: "table",
Quiet: true,
},
},
"imageID1\nimageID1\nimageID2\nimageID3\n",
},
{
ImageContext{
Context: Context{
Format: "table",
Quiet: false,
},
Digest: true,
},
`REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
image tag1 <none> imageID1 24 hours ago 0 B
image <none> sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf imageID1 24 hours ago 0 B
image tag2 <none> imageID2 24 hours ago 0 B
<none> <none> <none> imageID3 24 hours ago 0 B
`,
},
{
ImageContext{
Context: Context{
Format: "table",
Quiet: true,
},
Digest: true,
},
"imageID1\nimageID1\nimageID2\nimageID3\n",
},
// Raw Format
{
ImageContext{
Context: Context{
Format: "raw",
},
},
fmt.Sprintf(`repository: image
tag: tag1
image_id: imageID1
created_at: %s
virtual_size: 0 B
repository: image
tag: <none>
image_id: imageID1
created_at: %s
virtual_size: 0 B
repository: image
tag: tag2
image_id: imageID2
created_at: %s
virtual_size: 0 B
repository: <none>
tag: <none>
image_id: imageID3
created_at: %s
virtual_size: 0 B
`, expectedTime, expectedTime, expectedTime, expectedTime),
},
{
ImageContext{
Context: Context{
Format: "raw",
},
Digest: true,
},
fmt.Sprintf(`repository: image
tag: tag1
digest: <none>
image_id: imageID1
created_at: %s
virtual_size: 0 B
repository: image
tag: <none>
digest: sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
image_id: imageID1
created_at: %s
virtual_size: 0 B
repository: image
tag: tag2
digest: <none>
image_id: imageID2
created_at: %s
virtual_size: 0 B
repository: <none>
tag: <none>
digest: <none>
image_id: imageID3
created_at: %s
virtual_size: 0 B
`, expectedTime, expectedTime, expectedTime, expectedTime),
},
{
ImageContext{
Context: Context{
Format: "raw",
Quiet: true,
},
},
`image_id: imageID1
image_id: imageID1
image_id: imageID2
image_id: imageID3
`,
},
// Custom Format
{
ImageContext{
Context: Context{
Format: "{{.Repository}}",
},
},
"image\nimage\nimage\n<none>\n",
},
{
ImageContext{
Context: Context{
Format: "{{.Repository}}",
},
Digest: true,
},
"image\nimage\nimage\n<none>\n",
},
}
for _, context := range contexts {
images := []types.Image{
{ID: "imageID1", RepoTags: []string{"image:tag1"}, RepoDigests: []string{"image@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf"}, Created: unixTime},
{ID: "imageID2", RepoTags: []string{"image:tag2"}, Created: unixTime},
{ID: "imageID3", RepoTags: []string{"<none>:<none>"}, RepoDigests: []string{"<none>@<none>"}, Created: unixTime},
}
out := bytes.NewBufferString("")
context.context.Output = out
context.context.Images = images
context.context.Write()
actual := out.String()
if actual != context.expected {
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
}
// Clean buffer
out.Reset()
}
}
func TestImageContextWriteWithNoImage(t *testing.T) {
out := bytes.NewBufferString("")
images := []types.Image{}
contexts := []struct {
context ImageContext
expected string
}{
{
ImageContext{
Context: Context{
Format: "{{.Repository}}",
Output: out,
},
},
"",
},
{
ImageContext{
Context: Context{
Format: "table {{.Repository}}",
Output: out,
},
},
"REPOSITORY\n",
},
{
ImageContext{
Context: Context{
Format: "{{.Repository}}",
Output: out,
},
Digest: true,
},
"",
},
{
ImageContext{
Context: Context{
Format: "table {{.Repository}}",
Output: out,
},
Digest: true,
},
"REPOSITORY DIGEST\n",
},
}
for _, context := range contexts {
context.context.Images = images
context.context.Write()
actual := out.String()
if actual != context.expected {
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
}
// Clean buffer
out.Reset()
}
}

533
vendor/github.com/hyperhq/hypercli/api/client/func.go generated vendored Normal file
View File

@@ -0,0 +1,533 @@
package client
import (
"bufio"
"encoding/json"
"fmt"
"io"
"os"
"strconv"
"strings"
"text/tabwriter"
"time"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
"github.com/docker/engine-api/types/filters"
"github.com/docker/engine-api/types/network"
"github.com/docker/engine-api/types/strslice"
"github.com/docker/go-connections/nat"
units "github.com/docker/go-units"
Cli "github.com/hyperhq/hypercli/cli"
ropts "github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/signal"
"github.com/hyperhq/hypercli/runconfig/opts"
"golang.org/x/net/context"
)
// CmdFunc is the parent subcommand for all func commands
//
// Usage: docker func <COMMAND> [OPTIONS]
func (cli *DockerCli) CmdFunc(args ...string) error {
cmd := Cli.Subcmd("func", []string{"COMMAND [OPTIONS]"}, funcUsage(), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
func funcUsage() string {
funcCommands := [][]string{
{"create", "Create a new function"},
{"update", "Update a function"},
{"ls", "Lists all functions"},
{"rm", "Remove one or more function"},
{"inspect", "Display detailed information on the given function"},
{"call", "Call a function"},
{"get", "Get the return of a function call"},
{"logs", "Retrieve the logs of a function"},
{"status", "Retrieve the status of a function"},
}
help := "Commands:\n"
for _, cmd := range funcCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
}
help += fmt.Sprintf("\nRun 'hyper func COMMAND --help' for more information on a command.")
return help
}
// CmdFuncCreate creates a new func with a given name
//
// Usage: hyper func create [OPTIONS] IMAGE [COMMAND]
func (cli *DockerCli) CmdFuncCreate(args ...string) error {
cmd := Cli.Subcmd("func create", []string{"IMAGE [COMMAND] [ARG...]"}, "Create a new function", false)
var (
flName = cmd.String([]string{"-name"}, "", "Function name")
flContainerSize = cmd.String([]string{"-size"}, "s4", "The size of function containers to run the funciton (e.g. s1, s2, s3, s4, m1, m2, m3, l1, l2, l3)")
flTimeout = cmd.Int([]string{"-timeout"}, 300, "The maximum execution duration of function call")
flEnv = ropts.NewListOpts(opts.ValidateEnv)
flEnvFile = ropts.NewListOpts(nil)
flLabels = ropts.NewListOpts(opts.ValidateEnv)
flLabelsFile = ropts.NewListOpts(nil)
flVolumesFrom = ropts.NewListOpts(nil)
flNoAutoVolume = cmd.Bool([]string{"-noauto-volume"}, false, "Do not create volumes specified in image")
flPublish = ropts.NewListOpts(nil)
flExpose = ropts.NewListOpts(nil)
flPublishAll = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to random ports")
flEntrypoint = cmd.String([]string{"-entrypoint"}, "", "Overwrite the default ENTRYPOINT of the image")
flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
flLinks = ropts.NewListOpts(opts.ValidateLink)
flSecurityGroups = ropts.NewListOpts(nil)
flStopSignal = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
flNetMode = cmd.String([]string{}, "bridge", "Connect containers to a network, only bridge is supported now")
)
cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
cmd.Var(&flSecurityGroups, []string{"-sg"}, "Security group for each container")
cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host")
cmd.Var(&flExpose, []string{"-expose"}, "Expose a port or a range of ports")
cmd.Var(&flVolumesFrom, []string{"-volumes-from"}, "Mount shared volumes from the specified container(s)")
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
var (
parsedArgs = cmd.Args()
runCmd strslice.StrSlice
entrypoint strslice.StrSlice
image = cmd.Arg(0)
)
if len(parsedArgs) > 1 {
runCmd = strslice.StrSlice(parsedArgs[1:])
}
if *flEntrypoint != "" {
entrypoint = strslice.StrSlice{*flEntrypoint}
}
// collect all the environment variables for the container
envVariables, err := opts.ReadKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
if err != nil {
return err
}
// collect all the labels for the container
labels, err := opts.ReadKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
if err != nil {
return err
}
for _, sg := range flSecurityGroups.GetAll() {
if sg == "" {
continue
}
labels = append(labels, fmt.Sprintf("sh_hyper_sg_%s=yes", sg))
}
if *flNoAutoVolume {
labels = append(labels, "sh_hyper_noauto_volume=true")
}
ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
if err != nil {
return err
}
// Merge in exposed ports to the map of published ports
for _, e := range flExpose.GetAll() {
if strings.Contains(e, ":") {
return fmt.Errorf("Invalid port format for --expose: %s", e)
}
//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
proto, port := nat.SplitProtoPort(e)
//parse the start and end port and create a sequence of ports to expose
//if expose a port, the start and end port are the same
start, end, err := nat.ParsePortRange(port)
if err != nil {
return fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
}
for i := start; i <= end; i++ {
p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
if err != nil {
return err
}
if _, exists := ports[p]; !exists {
ports[p] = struct{}{}
}
}
}
config := types.FuncConfig{
Tty: *flTty,
ExposedPorts: ports,
Env: &envVariables,
Cmd: runCmd,
Image: image,
Entrypoint: entrypoint,
WorkingDir: *flWorkingDir,
Labels: opts.ConvertKVStringsToMap(labels),
StopSignal: *flStopSignal,
}
hostConfig := types.FuncHostConfig{
VolumesFrom: flVolumesFrom.GetAll(),
PortBindings: portBindings,
Links: flLinks.GetAll(),
PublishAllPorts: *flPublishAll,
NetworkMode: container.NetworkMode(*flNetMode),
}
networkingConfig := network.NetworkingConfig{
EndpointsConfig: make(map[string]*network.EndpointSettings),
}
if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 {
epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
if epConfig == nil {
epConfig = &network.EndpointSettings{}
}
epConfig.Links = make([]string, len(hostConfig.Links))
copy(epConfig.Links, hostConfig.Links)
networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
}
fnOpts := types.Func{
Name: *flName,
ContainerSize: *flContainerSize,
Timeout: *flTimeout,
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
}
fn, err := cli.client.FuncCreate(context.Background(), fnOpts)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s is created with the address of https://%s.hyperfunc.io/call/%s/%s\n", fn.Name, cli.region, fn.Name, fn.UUID)
return nil
}
// CmdFuncUpdate updates a func with a given name
//
// Usage: hyper func update [OPTIONS] NAME
func (cli *DockerCli) CmdFuncUpdate(args ...string) error {
cmd := Cli.Subcmd("func update", []string{"NAME"}, "Update a function", false)
var (
flContainerSize = cmd.String([]string{"-size"}, "", "The size of function containers to run the funciton (e.g. s1, s2, s3, s4, m1, m2, m3, l1, l2, l3)")
flEnv = ropts.NewListOpts(opts.ValidateEnv)
flEnvFile = ropts.NewListOpts(nil)
flRefresh = cmd.Bool([]string{"-refresh"}, false, "Whether to regenerate the uuid of function")
flTimeout = cmd.String([]string{"-timeout"}, "", "The maximum execution duration of function call")
)
cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
name := cmd.Arg(0)
name = strings.Replace(name, "/", "", -1)
// collect all the environment variables for the container
envVariables, err := opts.ReadKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
if err != nil {
return err
}
for _, env := range envVariables {
if env == "" {
envVariables = []string{}
break
}
}
env := &envVariables
if !cmd.IsSet("-env") && !cmd.IsSet("e") && !cmd.IsSet("-env-file") {
env = nil
}
timeout, _ := strconv.Atoi(*flTimeout)
fnOpts := types.Func{
Name: name,
ContainerSize: *flContainerSize,
Refresh: *flRefresh,
Timeout: timeout,
Config: types.FuncConfig{
Env: env,
},
}
fn, err := cli.client.FuncUpdate(context.Background(), name, fnOpts)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", fn.Name)
return nil
}
// CmdFuncDelete deletes one or more funcs
//
// Usage: hyper func rm NAME [NAME...]
func (cli *DockerCli) CmdFuncRm(args ...string) error {
cmd := Cli.Subcmd("func rm", []string{"NAME [NAME...]"}, "Remove one or more function", false)
cmd.Require(flag.Min, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
status := 0
for _, name := range cmd.Args() {
name = strings.Replace(name, "/", "", -1)
if err := cli.client.FuncDelete(context.Background(), 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
}
// CmdFuncLs lists all the funcs
//
// Usage: hyper func ls [OPTIONS]
func (cli *DockerCli) CmdFuncLs(args ...string) error {
cmd := Cli.Subcmd("func ls", nil, "Lists all functions", true)
flFilter := ropts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
// Consolidate all filter flags, and sanity check them early.
// They'll get process after get response from server.
funcFilterArgs := filters.NewArgs()
for _, f := range flFilter.GetAll() {
if funcFilterArgs, err = filters.ParseFlag(f, funcFilterArgs); err != nil {
return err
}
}
options := types.FuncListOptions{
Filters: funcFilterArgs,
}
fns, err := cli.client.FuncList(context.Background(), options)
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tSIZE\tIMAGE\tCOMMAND\tCREATED\tUUID\n")
for _, fn := range fns {
created := units.HumanDuration(time.Now().UTC().Sub(fn.Created)) + " ago"
command := strings.Join([]string(fn.Config.Cmd), " ")
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", fn.Name, fn.ContainerSize, fn.Config.Image, command, created, fn.UUID)
}
w.Flush()
return nil
}
// CmdFuncInspect
//
// Usage: docker func inspect [OPTIONS] NAME [NAME...]
func (cli *DockerCli) CmdFuncInspect(args ...string) error {
cmd := Cli.Subcmd("func inspect", []string{"NAME [NAME...]"}, "Display detailed information on the given function", 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) {
name = strings.Replace(name, "/", "", -1)
i, err := cli.client.FuncInspect(ctx, name)
return i, nil, err
}
return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
}
// CmdFuncCall call a func
//
// Usage: hyper func call NAME
func (cli *DockerCli) CmdFuncCall(args ...string) error {
cmd := Cli.Subcmd("func call", []string{"NAME"}, "Call a function", false)
sync := cmd.Bool([]string{"-sync"}, false, "Block until the call is completed")
cmd.Require(flag.Exact, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
name := cmd.Arg(0)
name = strings.Replace(name, "/", "", -1)
var stdin io.Reader
if fi, err := os.Stdin.Stat(); err == nil {
if fi.Mode()&os.ModeNamedPipe != 0 {
stdin = bufio.NewReader(os.Stdin)
}
}
body, err := cli.client.FuncCall(context.Background(), cli.region, name, stdin, *sync)
if err != nil {
return err
}
defer body.Close()
if *sync {
_, err = io.Copy(cli.out, body)
return err
}
var ret types.FuncCallResponse
err = json.NewDecoder(body).Decode(&ret)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "CallId: %s\n", ret.CallId)
return nil
}
// CmdFuncGet Get the return of a func call
//
// Usage: hyper func get [OPTIONS] CALL_ID
func (cli *DockerCli) CmdFuncGet(args ...string) error {
cmd := Cli.Subcmd("func get", []string{"CALL_ID"}, "Get the return of a function call", false)
wait := cmd.Bool([]string{"-wait"}, false, "Block until the call is completed")
cmd.Require(flag.Exact, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
callId := cmd.Arg(0)
body, err := cli.client.FuncGet(context.Background(), cli.region, callId, *wait)
if err != nil {
return err
}
defer body.Close()
_, err = io.Copy(cli.out, body)
return err
}
// CmdFuncLogs Get the return of a func call
//
// Usage: hyper func get [OPTIONS] NAME
func (cli *DockerCli) CmdFuncLogs(args ...string) error {
cmd := Cli.Subcmd("func logs", []string{"NAME"}, "Retrieve the logs of a function", false)
follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
tail := cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
callId := cmd.String([]string{"-callid"}, "", "Only retrieve specific logs of CallId")
cmd.Require(flag.Exact, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
name := cmd.Arg(0)
name = strings.Replace(name, "/", "", -1)
reader, err := cli.client.FuncLogs(context.Background(), cli.region, name, *callId, *follow, *tail)
if err != nil {
return err
}
defer reader.Close()
dec := json.NewDecoder(reader)
for {
var log types.FuncLogsResponse
err := dec.Decode(&log)
if err != nil {
if err == io.EOF {
return nil
}
return err
}
if log.Event != "" {
logTime := log.Time.Local().Format("2006-01-02T15:04:05Z")
if log.Event == "CALL" {
fmt.Fprintf(
cli.out, "%s [%s] CallId: %s, ShortStdin: %s\n",
logTime, log.Event, log.CallId, log.ShortStdin,
)
} else if log.Event == "FINISHED" {
fmt.Fprintf(
cli.out, "%s [%s] CallId: %s, ShortStdout: %s, ShortStderr: %s\n",
logTime, log.Event, log.CallId, log.ShortStdout, log.ShortStderr,
)
} else if log.Event == "FAILED" {
fmt.Fprintf(
cli.out, "%s [%s] CallId: %s, Message: %s\n",
logTime, log.Event, log.CallId, log.Message,
)
} else {
fmt.Fprintf(
cli.out, "%s [%s] CallId: %s\n",
logTime, log.Event, log.CallId,
)
}
}
}
}
// CmdFuncStatus Status the return of a func call
//
// Usage: hyper func status [OPTIONS] NAME
func (cli *DockerCli) CmdFuncStatus(args ...string) error {
cmd := Cli.Subcmd("func status", []string{"NAME"}, "Retrieve the status of a function", false)
cmd.Require(flag.Exact, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
name := cmd.Arg(0)
name = strings.Replace(name, "/", "", -1)
status, err := cli.client.FuncStatus(context.Background(), cli.region, name)
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "TOTAL\tPENDING\tRUNNING\tFINISHED\tFAILED\n")
fmt.Fprintf(w, "%d\t%d\t%d\t%d\t%d\n", status.Total, status.Pending, status.Running, status.Finished, status.Failed)
w.Flush()
return nil
}

View File

@@ -0,0 +1,56 @@
package client
import (
"io"
"github.com/Sirupsen/logrus"
"github.com/hyperhq/hypercli/pkg/stdcopy"
"github.com/docker/engine-api/types"
)
func (cli *DockerCli) holdHijackedConnection(tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
var err error
receiveStdout := make(chan error, 1)
if outputStream != nil || errorStream != nil {
go func() {
// When TTY is ON, use regular copy
if tty && outputStream != nil {
_, err = io.Copy(outputStream, resp.Reader)
} else {
_, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader)
}
logrus.Debugf("[hijack] End of stdout")
receiveStdout <- err
}()
}
stdinDone := make(chan struct{})
go func() {
if inputStream != nil {
io.Copy(resp.Conn, inputStream)
logrus.Debugf("[hijack] End of stdin")
}
if err := resp.CloseWrite(); err != nil {
logrus.Debugf("Couldn't send EOF: %s", err)
}
close(stdinDone)
}()
select {
case err := <-receiveStdout:
if err != nil {
logrus.Debugf("Error receiveStdout: %s", err)
return err
}
case <-stdinDone:
if outputStream != nil || errorStream != nil {
if err := <-receiveStdout; err != nil {
logrus.Debugf("Error receiveStdout: %s", err)
return err
}
}
}
return nil
}

View File

@@ -0,0 +1,76 @@
package client
import (
"fmt"
"strconv"
"strings"
"text/tabwriter"
"time"
"golang.org/x/net/context"
"github.com/docker/go-units"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/stringid"
"github.com/hyperhq/hypercli/pkg/stringutils"
)
// CmdHistory shows the history of an image.
//
// Usage: docker history [OPTIONS] IMAGE
func (cli *DockerCli) CmdHistory(args ...string) error {
cmd := Cli.Subcmd("history", []string{"IMAGE"}, Cli.DockerCommands["history"].Description, true)
human := cmd.Bool([]string{"H", "-human"}, true, "Print sizes and dates in human readable format")
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Don't truncate output")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
history, err := cli.client.ImageHistory(context.Background(), cmd.Arg(0))
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if *quiet {
for _, entry := range history {
if *noTrunc {
fmt.Fprintf(w, "%s\n", entry.ID)
} else {
fmt.Fprintf(w, "%s\n", stringid.TruncateID(entry.ID))
}
}
w.Flush()
return nil
}
var imageID string
var createdBy string
var created string
var size string
fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE\tCOMMENT")
for _, entry := range history {
imageID = entry.ID
createdBy = strings.Replace(entry.CreatedBy, "\t", " ", -1)
if *noTrunc == false {
createdBy = stringutils.Truncate(createdBy, 45)
imageID = stringid.TruncateID(entry.ID)
}
if *human {
created = units.HumanDuration(time.Now().UTC().Sub(time.Unix(entry.Created, 0))) + " ago"
size = units.HumanSize(float64(entry.Size))
} else {
created = time.Unix(entry.Created, 0).Format(time.RFC3339)
size = strconv.FormatInt(entry.Size, 10)
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", imageID, created, createdBy, size, entry.Comment)
}
w.Flush()
return nil
}

View File

@@ -0,0 +1,80 @@
package client
import (
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
"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"
"golang.org/x/net/context"
)
// CmdImages lists the images in a specified repository, or all top-level images if no repository is specified.
//
// Usage: docker images [OPTIONS] [REPOSITORY]
func (cli *DockerCli) CmdImages(args ...string) error {
cmd := Cli.Subcmd("images", []string{"[REPOSITORY[:TAG]]"}, Cli.DockerCommands["images"].Description, true)
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)")
noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Don't truncate output")
showDigests := cmd.Bool([]string{"-digests"}, false, "Show digests")
format := cmd.String([]string{"-format"}, "", "Pretty-print images using a Go template")
flFilter := opts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.Require(flag.Max, 1)
cmd.ParseFlags(args, true)
// Consolidate all filter flags, and sanity check them early.
// They'll get process in the daemon/server.
imageFilterArgs := filters.NewArgs()
for _, f := range flFilter.GetAll() {
var err error
imageFilterArgs, err = filters.ParseFlag(f, imageFilterArgs)
if err != nil {
return err
}
}
var matchName string
if cmd.NArg() == 1 {
matchName = cmd.Arg(0)
}
options := types.ImageListOptions{
MatchName: matchName,
All: *all,
Filters: imageFilterArgs,
}
images, err := cli.client.ImageList(context.Background(), options)
if err != nil {
return err
}
f := *format
if len(f) == 0 {
if len(cli.ImagesFormat()) > 0 && !*quiet {
f = cli.ImagesFormat()
} else {
f = "table"
}
}
imagesCtx := formatter.ImageContext{
Context: formatter.Context{
Output: cli.out,
Format: f,
Quiet: *quiet,
Trunc: !*noTrunc,
},
Digest: *showDigests,
Images: images,
}
imagesCtx.Write()
return nil
}

View File

@@ -0,0 +1,75 @@
package client
import (
"fmt"
"io"
"os"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
"github.com/hyperhq/hypercli/pkg/jsonmessage"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/urlutil"
)
// CmdImport creates an empty filesystem image, imports the contents of the tarball into the image, and optionally tags the image.
//
// The URL argument is the address of a tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) file or a path to local file relative to docker client. If the URL is '-', then the tar file is read from STDIN.
//
// Usage: docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]
func (cli *DockerCli) Import(args ...string) error {
cmd := Cli.Subcmd("import", []string{"file|URL|- [REPOSITORY[:TAG]]"}, Cli.DockerCommands["import"].Description, true)
flChanges := opts.NewListOpts(nil)
cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
message := cmd.String([]string{"m", "-message"}, "", "Set commit message for imported image")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var (
in io.Reader
tag string
src = cmd.Arg(0)
srcName = src
ref = cmd.Arg(1)
changes = flChanges.GetAll()
)
if cmd.NArg() == 3 {
fmt.Fprintf(cli.err, "[DEPRECATED] The format 'file|URL|- [REPOSITORY [TAG]]' has been deprecated. Please use file|URL|- [REPOSITORY[:TAG]]\n")
tag = cmd.Arg(2)
}
if src == "-" {
in = cli.in
} else if !urlutil.IsURL(src) {
srcName = "-"
file, err := os.Open(src)
if err != nil {
return err
}
defer file.Close()
in = file
}
source := types.ImageImportSource{
Source: in,
SourceName: srcName,
}
options := types.ImageImportOptions{
Message: *message,
Tag: tag,
Changes: changes,
}
responseBody, err := cli.client.ImageImport(context.Background(), source, ref, options)
if err != nil {
return err
}
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil)
}

153
vendor/github.com/hyperhq/hypercli/api/client/info.go generated vendored Normal file
View File

@@ -0,0 +1,153 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
"github.com/docker/go-units"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/pkg/ioutils"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/utils"
)
// CmdInfo displays system-wide information.
//
// Usage: docker info
func (cli *DockerCli) CmdInfo(args ...string) error {
cmd := Cli.Subcmd("info", nil, Cli.DockerCommands["info"].Description, true)
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
info, err := cli.client.Info(context.Background())
if err != nil {
return err
}
fmt.Fprintf(cli.out, "Containers: %d\n", info.Containers)
fmt.Fprintf(cli.out, " Running: %d\n", info.ContainersRunning)
fmt.Fprintf(cli.out, " Paused: %d\n", info.ContainersPaused)
fmt.Fprintf(cli.out, " Stopped: %d\n", info.ContainersStopped)
fmt.Fprintf(cli.out, "Images: %d\n", info.Images)
ioutils.FprintfIfNotEmpty(cli.out, "Server Version: %s\n", info.ServerVersion)
ioutils.FprintfIfNotEmpty(cli.out, "Storage Driver: %s\n", info.Driver)
if info.DriverStatus != nil {
for _, pair := range info.DriverStatus {
fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1])
// print a warning if devicemapper is using a loopback file
if pair[0] == "Data loop file" {
fmt.Fprintln(cli.err, " WARNING: Usage of loopback devices is strongly discouraged for production use. Either use `--storage-opt dm.thinpooldev` or use `--storage-opt dm.no_warn_on_loop_devices=true` to suppress this warning.")
}
}
}
if info.SystemStatus != nil {
for _, pair := range info.SystemStatus {
fmt.Fprintf(cli.out, "%s: %s\n", pair[0], pair[1])
}
}
ioutils.FprintfIfNotEmpty(cli.out, "Execution Driver: %s\n", info.ExecutionDriver)
ioutils.FprintfIfNotEmpty(cli.out, "Logging Driver: %s\n", info.LoggingDriver)
fmt.Fprintf(cli.out, "Plugins: \n")
fmt.Fprintf(cli.out, " Volume:")
fmt.Fprintf(cli.out, " %s", strings.Join(info.Plugins.Volume, " "))
fmt.Fprintf(cli.out, "\n")
fmt.Fprintf(cli.out, " Network:")
fmt.Fprintf(cli.out, " %s", strings.Join(info.Plugins.Network, " "))
fmt.Fprintf(cli.out, "\n")
if len(info.Plugins.Authorization) != 0 {
fmt.Fprintf(cli.out, " Authorization:")
fmt.Fprintf(cli.out, " %s", strings.Join(info.Plugins.Authorization, " "))
fmt.Fprintf(cli.out, "\n")
}
ioutils.FprintfIfNotEmpty(cli.out, "Kernel Version: %s\n", info.KernelVersion)
ioutils.FprintfIfNotEmpty(cli.out, "Operating System: %s\n", info.OperatingSystem)
ioutils.FprintfIfNotEmpty(cli.out, "OSType: %s\n", info.OSType)
ioutils.FprintfIfNotEmpty(cli.out, "Architecture: %s\n", info.Architecture)
fmt.Fprintf(cli.out, "CPUs: %d\n", info.NCPU)
fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(info.MemTotal)))
ioutils.FprintfIfNotEmpty(cli.out, "Name: %s\n", info.Name)
ioutils.FprintfIfNotEmpty(cli.out, "ID: %s\n", info.ID)
fmt.Fprintf(cli.out, "Debug mode (client): %v\n", utils.IsDebugEnabled())
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", info.Debug)
if info.Debug {
fmt.Fprintf(cli.out, " File Descriptors: %d\n", info.NFd)
fmt.Fprintf(cli.out, " Goroutines: %d\n", info.NGoroutines)
fmt.Fprintf(cli.out, " System Time: %s\n", info.SystemTime)
fmt.Fprintf(cli.out, " EventsListeners: %d\n", info.NEventsListener)
fmt.Fprintf(cli.out, " Docker Root Dir: %s\n", info.DockerRootDir)
}
ioutils.FprintfIfNotEmpty(cli.out, "Http Proxy: %s\n", info.HTTPProxy)
ioutils.FprintfIfNotEmpty(cli.out, "Https Proxy: %s\n", info.HTTPSProxy)
ioutils.FprintfIfNotEmpty(cli.out, "No Proxy: %s\n", info.NoProxy)
if info.IndexServerAddress != "" {
u := cli.configFile.AuthConfigs[info.IndexServerAddress].Username
if len(u) > 0 {
fmt.Fprintf(cli.out, "Username: %v\n", u)
fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress)
}
}
// Only output these warnings if the server does not support these features
// and the client is in debug mode
if utils.IsDebugEnabled() && info.OSType != "windows" {
if !info.MemoryLimit {
fmt.Fprintln(cli.err, "WARNING: No memory limit support")
}
if !info.SwapLimit {
fmt.Fprintln(cli.err, "WARNING: No swap limit support")
}
if !info.OomKillDisable {
fmt.Fprintln(cli.err, "WARNING: No oom kill disable support")
}
if !info.CPUCfsQuota {
fmt.Fprintln(cli.err, "WARNING: No cpu cfs quota support")
}
if !info.CPUCfsPeriod {
fmt.Fprintln(cli.err, "WARNING: No cpu cfs period support")
}
if !info.CPUShares {
fmt.Fprintln(cli.err, "WARNING: No cpu shares support")
}
if !info.CPUSet {
fmt.Fprintln(cli.err, "WARNING: No cpuset support")
}
if !info.IPv4Forwarding {
fmt.Fprintln(cli.err, "WARNING: IPv4 forwarding is disabled")
}
if !info.BridgeNfIptables {
fmt.Fprintln(cli.err, "WARNING: bridge-nf-call-iptables is disabled")
}
if !info.BridgeNfIP6tables {
fmt.Fprintln(cli.err, "WARNING: bridge-nf-call-ip6tables is disabled")
}
}
if info.Labels != nil {
fmt.Fprintln(cli.out, "Labels:")
for _, attribute := range info.Labels {
fmt.Fprintf(cli.out, " %s\n", attribute)
}
}
ioutils.FprintfIfTrue(cli.out, "Experimental: %v\n", info.ExperimentalBuild)
if info.ClusterStore != "" {
fmt.Fprintf(cli.out, "Cluster store: %s\n", info.ClusterStore)
}
if info.ClusterAdvertise != "" {
fmt.Fprintf(cli.out, "Cluster advertise: %s\n", info.ClusterAdvertise)
}
return nil
}

View File

@@ -0,0 +1,137 @@
package client
import (
"encoding/json"
"fmt"
"text/template"
"golang.org/x/net/context"
"github.com/docker/engine-api/client"
"github.com/hyperhq/hypercli/api/client/inspect"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
var funcMap = template.FuncMap{
"json": func(v interface{}) string {
a, _ := json.Marshal(v)
return string(a)
},
}
// CmdInspect displays low-level information on one or more containers or images.
//
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
func (cli *DockerCli) CmdInspect(args ...string) error {
cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE [CONTAINER|IMAGE...]"}, Cli.DockerCommands["inspect"].Description, true)
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image or container)")
size := cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes if the type is container")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if *inspectType != "" && *inspectType != "container" && *inspectType != "image" {
return fmt.Errorf("%q is not a valid value for --type", *inspectType)
}
ctx := context.Background()
var elementSearcher inspectSearcher
switch *inspectType {
case "container":
elementSearcher = cli.inspectContainers(ctx, *size)
case "image":
elementSearcher = cli.inspectImages(ctx, *size)
default:
elementSearcher = cli.inspectAll(ctx, *size)
}
return cli.inspectElements(*tmplStr, cmd.Args(), elementSearcher)
}
func (cli *DockerCli) inspectContainers(ctx context.Context, getSize bool) inspectSearcher {
return func(ref string) (interface{}, []byte, error) {
return cli.client.ContainerInspectWithRaw(ctx, ref, getSize)
}
}
func (cli *DockerCli) inspectImages(ctx context.Context, getSize bool) inspectSearcher {
return func(ref string) (interface{}, []byte, error) {
return cli.client.ImageInspectWithRaw(ctx, ref, getSize)
}
}
func (cli *DockerCli) inspectAll(ctx context.Context, getSize bool) inspectSearcher {
return func(ref string) (interface{}, []byte, error) {
c, rawContainer, err := cli.client.ContainerInspectWithRaw(ctx, ref, getSize)
if err != nil {
// Search for image with that id if a container doesn't exist.
if client.IsErrContainerNotFound(err) {
i, rawImage, err := cli.client.ImageInspectWithRaw(ctx, ref, getSize)
if err != nil {
if client.IsErrImageNotFound(err) {
return nil, nil, fmt.Errorf("Error: No such image or container: %s", ref)
}
return nil, nil, err
}
return i, rawImage, err
}
return nil, nil, err
}
return c, rawContainer, err
}
}
type inspectSearcher func(ref string) (interface{}, []byte, error)
func (cli *DockerCli) inspectElements(tmplStr string, references []string, searchByReference inspectSearcher) error {
elementInspector, err := cli.newInspectorWithTemplate(tmplStr)
if err != nil {
return Cli.StatusError{StatusCode: 64, Status: err.Error()}
}
var inspectErr error
for _, ref := range references {
element, raw, err := searchByReference(ref)
if err != nil {
inspectErr = err
break
}
if err := elementInspector.Inspect(element, raw); err != nil {
inspectErr = err
break
}
}
if err := elementInspector.Flush(); err != nil {
cli.inspectErrorStatus(err)
}
if status := cli.inspectErrorStatus(inspectErr); status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
func (cli *DockerCli) inspectErrorStatus(err error) (status int) {
if err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
}
return
}
func (cli *DockerCli) newInspectorWithTemplate(tmplStr string) (inspect.Inspector, error) {
elementInspector := inspect.NewIndentedInspector(cli.out)
if tmplStr != "" {
tmpl, err := template.New("").Funcs(funcMap).Parse(tmplStr)
if err != nil {
return nil, fmt.Errorf("Template parsing error: %s", err)
}
elementInspector = inspect.NewTemplateInspector(cli.out, tmpl)
}
return elementInspector, nil
}

View File

@@ -0,0 +1,119 @@
package inspect
import (
"bytes"
"encoding/json"
"fmt"
"io"
"text/template"
)
// Inspector defines an interface to implement to process elements
type Inspector interface {
Inspect(typedElement interface{}, rawElement []byte) error
Flush() error
}
// TemplateInspector uses a text template to inspect elements.
type TemplateInspector struct {
outputStream io.Writer
buffer *bytes.Buffer
tmpl *template.Template
}
// NewTemplateInspector creates a new inspector with a template.
func NewTemplateInspector(outputStream io.Writer, tmpl *template.Template) Inspector {
return &TemplateInspector{
outputStream: outputStream,
buffer: new(bytes.Buffer),
tmpl: tmpl,
}
}
// Inspect executes the inspect template.
// It decodes the raw element into a map if the initial execution fails.
// This allows docker cli to parse inspect structs injected with Swarm fields.
func (i *TemplateInspector) Inspect(typedElement interface{}, rawElement []byte) error {
buffer := new(bytes.Buffer)
if err := i.tmpl.Execute(buffer, typedElement); err != nil {
if rawElement == nil {
return fmt.Errorf("Template parsing error: %v", err)
}
return i.tryRawInspectFallback(rawElement, err)
}
i.buffer.Write(buffer.Bytes())
i.buffer.WriteByte('\n')
return nil
}
// Flush write the result of inspecting all elements into the output stream.
func (i *TemplateInspector) Flush() error {
if i.buffer.Len() == 0 {
_, err := io.WriteString(i.outputStream, "\n")
return err
}
_, err := io.Copy(i.outputStream, i.buffer)
return err
}
// IndentedInspector uses a buffer to stop the indented representation of an element.
type IndentedInspector struct {
outputStream io.Writer
elements []interface{}
rawElements [][]byte
}
// NewIndentedInspector generates a new IndentedInspector.
func NewIndentedInspector(outputStream io.Writer) Inspector {
return &IndentedInspector{
outputStream: outputStream,
}
}
// Inspect writes the raw element with an indented json format.
func (i *IndentedInspector) Inspect(typedElement interface{}, rawElement []byte) error {
if rawElement != nil {
i.rawElements = append(i.rawElements, rawElement)
} else {
i.elements = append(i.elements, typedElement)
}
return nil
}
// Flush write the result of inspecting all elements into the output stream.
func (i *IndentedInspector) Flush() error {
if len(i.elements) == 0 && len(i.rawElements) == 0 {
_, err := io.WriteString(i.outputStream, "[]\n")
return err
}
var buffer io.Reader
if len(i.rawElements) > 0 {
bytesBuffer := new(bytes.Buffer)
bytesBuffer.WriteString("[")
for idx, r := range i.rawElements {
bytesBuffer.Write(r)
if idx < len(i.rawElements)-1 {
bytesBuffer.WriteString(",")
}
}
bytesBuffer.WriteString("]")
indented := new(bytes.Buffer)
if err := json.Indent(indented, bytesBuffer.Bytes(), "", " "); err != nil {
return err
}
buffer = indented
} else {
b, err := json.MarshalIndent(i.elements, "", " ")
if err != nil {
return err
}
buffer = bytes.NewReader(b)
}
if _, err := io.Copy(i.outputStream, buffer); err != nil {
return err
}
_, err := io.WriteString(i.outputStream, "\n")
return err
}

View File

@@ -0,0 +1,40 @@
// +build !go1.5
package inspect
import (
"bytes"
"encoding/json"
"fmt"
"strings"
)
// tryeRawInspectFallback executes the inspect template with a raw interface.
// This allows docker cli to parse inspect structs injected with Swarm fields.
// Unfortunately, go 1.4 doesn't fail executing invalid templates when the input is an interface.
// It doesn't allow to modify this behavior either, sending <no value> messages to the output.
// We assume that the template is invalid when there is a <no value>, if the template was valid
// we'd get <nil> or "" values. In that case we fail with the original error raised executing the
// template with the typed input.
func (i *TemplateInspector) tryRawInspectFallback(rawElement []byte, originalErr error) error {
var raw interface{}
buffer := new(bytes.Buffer)
rdr := bytes.NewReader(rawElement)
dec := json.NewDecoder(rdr)
if rawErr := dec.Decode(&raw); rawErr != nil {
return fmt.Errorf("unable to read inspect data: %v", rawErr)
}
if rawErr := i.tmpl.Execute(buffer, raw); rawErr != nil {
return fmt.Errorf("Template parsing error: %v", rawErr)
}
if strings.Contains(buffer.String(), "<no value>") {
return fmt.Errorf("Template parsing error: %v", originalErr)
}
i.buffer.Write(buffer.Bytes())
i.buffer.WriteByte('\n')
return nil
}

View File

@@ -0,0 +1,29 @@
// +build go1.5
package inspect
import (
"bytes"
"encoding/json"
"fmt"
)
func (i *TemplateInspector) tryRawInspectFallback(rawElement []byte, _ error) error {
var raw interface{}
buffer := new(bytes.Buffer)
rdr := bytes.NewReader(rawElement)
dec := json.NewDecoder(rdr)
if rawErr := dec.Decode(&raw); rawErr != nil {
return fmt.Errorf("unable to read inspect data: %v", rawErr)
}
tmplMissingKey := i.tmpl.Option("missingkey=error")
if rawErr := tmplMissingKey.Execute(buffer, raw); rawErr != nil {
return fmt.Errorf("Template parsing error: %v", rawErr)
}
i.buffer.Write(buffer.Bytes())
i.buffer.WriteByte('\n')
return nil
}

View File

@@ -0,0 +1,220 @@
package inspect
import (
"bytes"
"strings"
"testing"
"text/template"
)
type testElement struct {
DNS string `json:"Dns"`
}
func TestTemplateInspectorDefault(t *testing.T) {
b := new(bytes.Buffer)
tmpl, err := template.New("test").Parse("{{.DNS}}")
if err != nil {
t.Fatal(err)
}
i := NewTemplateInspector(b, tmpl)
if err := i.Inspect(testElement{"0.0.0.0"}, nil); err != nil {
t.Fatal(err)
}
if err := i.Flush(); err != nil {
t.Fatal(err)
}
if b.String() != "0.0.0.0\n" {
t.Fatalf("Expected `0.0.0.0\\n`, got `%s`", b.String())
}
}
func TestTemplateInspectorEmpty(t *testing.T) {
b := new(bytes.Buffer)
tmpl, err := template.New("test").Parse("{{.DNS}}")
if err != nil {
t.Fatal(err)
}
i := NewTemplateInspector(b, tmpl)
if err := i.Flush(); err != nil {
t.Fatal(err)
}
if b.String() != "\n" {
t.Fatalf("Expected `\\n`, got `%s`", b.String())
}
}
func TestTemplateInspectorTemplateError(t *testing.T) {
b := new(bytes.Buffer)
tmpl, err := template.New("test").Parse("{{.Foo}}")
if err != nil {
t.Fatal(err)
}
i := NewTemplateInspector(b, tmpl)
err = i.Inspect(testElement{"0.0.0.0"}, nil)
if err == nil {
t.Fatal("Expected error got nil")
}
if !strings.HasPrefix(err.Error(), "Template parsing error") {
t.Fatalf("Expected template error, got %v", err)
}
}
func TestTemplateInspectorRawFallback(t *testing.T) {
b := new(bytes.Buffer)
tmpl, err := template.New("test").Parse("{{.Dns}}")
if err != nil {
t.Fatal(err)
}
i := NewTemplateInspector(b, tmpl)
if err := i.Inspect(testElement{"0.0.0.0"}, []byte(`{"Dns": "0.0.0.0"}`)); err != nil {
t.Fatal(err)
}
if err := i.Flush(); err != nil {
t.Fatal(err)
}
if b.String() != "0.0.0.0\n" {
t.Fatalf("Expected `0.0.0.0\\n`, got `%s`", b.String())
}
}
func TestTemplateInspectorRawFallbackError(t *testing.T) {
b := new(bytes.Buffer)
tmpl, err := template.New("test").Parse("{{.Dns}}")
if err != nil {
t.Fatal(err)
}
i := NewTemplateInspector(b, tmpl)
err = i.Inspect(testElement{"0.0.0.0"}, []byte(`{"Foo": "0.0.0.0"}`))
if err == nil {
t.Fatal("Expected error got nil")
}
if !strings.HasPrefix(err.Error(), "Template parsing error") {
t.Fatalf("Expected template error, got %v", err)
}
}
func TestTemplateInspectorMultiple(t *testing.T) {
b := new(bytes.Buffer)
tmpl, err := template.New("test").Parse("{{.DNS}}")
if err != nil {
t.Fatal(err)
}
i := NewTemplateInspector(b, tmpl)
if err := i.Inspect(testElement{"0.0.0.0"}, nil); err != nil {
t.Fatal(err)
}
if err := i.Inspect(testElement{"1.1.1.1"}, nil); err != nil {
t.Fatal(err)
}
if err := i.Flush(); err != nil {
t.Fatal(err)
}
if b.String() != "0.0.0.0\n1.1.1.1\n" {
t.Fatalf("Expected `0.0.0.0\\n1.1.1.1\\n`, got `%s`", b.String())
}
}
func TestIndentedInspectorDefault(t *testing.T) {
b := new(bytes.Buffer)
i := NewIndentedInspector(b)
if err := i.Inspect(testElement{"0.0.0.0"}, nil); err != nil {
t.Fatal(err)
}
if err := i.Flush(); err != nil {
t.Fatal(err)
}
expected := `[
{
"Dns": "0.0.0.0"
}
]
`
if b.String() != expected {
t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
}
}
func TestIndentedInspectorMultiple(t *testing.T) {
b := new(bytes.Buffer)
i := NewIndentedInspector(b)
if err := i.Inspect(testElement{"0.0.0.0"}, nil); err != nil {
t.Fatal(err)
}
if err := i.Inspect(testElement{"1.1.1.1"}, nil); err != nil {
t.Fatal(err)
}
if err := i.Flush(); err != nil {
t.Fatal(err)
}
expected := `[
{
"Dns": "0.0.0.0"
},
{
"Dns": "1.1.1.1"
}
]
`
if b.String() != expected {
t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
}
}
func TestIndentedInspectorEmpty(t *testing.T) {
b := new(bytes.Buffer)
i := NewIndentedInspector(b)
if err := i.Flush(); err != nil {
t.Fatal(err)
}
expected := "[]\n"
if b.String() != expected {
t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
}
}
func TestIndentedInspectorRawElements(t *testing.T) {
b := new(bytes.Buffer)
i := NewIndentedInspector(b)
if err := i.Inspect(testElement{"0.0.0.0"}, []byte(`{"Dns": "0.0.0.0", "Node": "0"}`)); err != nil {
t.Fatal(err)
}
if err := i.Inspect(testElement{"1.1.1.1"}, []byte(`{"Dns": "1.1.1.1", "Node": "1"}`)); err != nil {
t.Fatal(err)
}
if err := i.Flush(); err != nil {
t.Fatal(err)
}
expected := `[
{
"Dns": "0.0.0.0",
"Node": "0"
},
{
"Dns": "1.1.1.1",
"Node": "1"
}
]
`
if b.String() != expected {
t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
}
}

35
vendor/github.com/hyperhq/hypercli/api/client/kill.go generated vendored Normal file
View File

@@ -0,0 +1,35 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdKill kills one or more running container using SIGKILL or a specified signal.
//
// Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdKill(args ...string) error {
cmd := Cli.Subcmd("kill", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["kill"].Description, true)
signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var errs []string
for _, name := range cmd.Args() {
if err := cli.client.ContainerKill(context.Background(), name, *signal); err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

323
vendor/github.com/hyperhq/hypercli/api/client/load.go generated vendored Normal file
View File

@@ -0,0 +1,323 @@
package client
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/docker/engine-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
}

156
vendor/github.com/hyperhq/hypercli/api/client/login.go generated vendored Normal file
View File

@@ -0,0 +1,156 @@
package client
import (
"bufio"
"fmt"
"io"
"os"
"runtime"
"strings"
"golang.org/x/net/context"
"github.com/docker/engine-api/client"
"github.com/docker/engine-api/types"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/term"
)
// CmdLogin logs in or registers a user to a Docker registry service.
//
// If no server is specified, the user will be logged into or registered to the registry's index server.
//
// Usage: docker login SERVER
func (cli *DockerCli) CmdLogin(args ...string) error {
cmd := Cli.Subcmd("login", []string{"[SERVER]"}, Cli.DockerCommands["login"].Description+".\nIf no server is specified, the default is defined by the daemon.", true)
cmd.Require(flag.Max, 1)
flUser := cmd.String([]string{"u", "-username"}, "", "Username")
flPassword := cmd.String([]string{"p", "-password"}, "", "Password")
flEmail := cmd.String([]string{"e", "-email"}, "", "Email")
cmd.ParseFlags(args, true)
// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
if runtime.GOOS == "windows" {
cli.in = os.Stdin
}
ctx := context.Background()
var serverAddress string
if len(cmd.Args()) > 0 {
serverAddress = cmd.Arg(0)
} else {
serverAddress = cli.electAuthServer(ctx)
}
authConfig, err := cli.configureAuth(*flUser, *flPassword, *flEmail, serverAddress)
if err != nil {
return err
}
response, err := cli.client.RegistryLogin(ctx, authConfig)
if err != nil {
if client.IsErrUnauthorized(err) {
delete(cli.configFile.AuthConfigs, serverAddress)
if err2 := cli.configFile.Save(); err2 != nil {
fmt.Fprintf(cli.out, "WARNING: could not save config file: %v\n", err2)
}
}
return err
}
if err := cli.configFile.Save(); err != nil {
return fmt.Errorf("Error saving config file: %v", err)
}
fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s\n", cli.configFile.Filename())
if response.Status != "" {
fmt.Fprintf(cli.out, "%s\n", response.Status)
}
return nil
}
func (cli *DockerCli) promptWithDefault(prompt string, configDefault string) {
if configDefault == "" {
fmt.Fprintf(cli.out, "%s: ", prompt)
} else {
fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault)
}
}
func (cli *DockerCli) configureAuth(flUser, flPassword, flEmail, serverAddress string) (types.AuthConfig, error) {
authconfig, ok := cli.configFile.AuthConfigs[serverAddress]
if !ok {
authconfig = types.AuthConfig{}
}
authconfig.Username = strings.TrimSpace(authconfig.Username)
if flUser = strings.TrimSpace(flUser); flUser == "" {
cli.promptWithDefault("Username", authconfig.Username)
flUser = readInput(cli.in, cli.out)
flUser = strings.TrimSpace(flUser)
if flUser == "" {
flUser = authconfig.Username
}
}
if flUser == "" {
return authconfig, fmt.Errorf("Error: Non-null Username Required")
}
if flPassword == "" {
oldState, err := term.SaveState(cli.inFd)
if err != nil {
return authconfig, err
}
fmt.Fprintf(cli.out, "Password: ")
term.DisableEcho(cli.inFd, oldState)
flPassword = readInput(cli.in, cli.out)
fmt.Fprint(cli.out, "\n")
term.RestoreTerminal(cli.inFd, oldState)
if flPassword == "" {
return authconfig, fmt.Errorf("Error: Password Required")
}
}
// Assume that a different username means they may not want to use
// the email from the config file, so prompt it
if flUser != authconfig.Username {
if flEmail == "" {
cli.promptWithDefault("Email", authconfig.Email)
flEmail = readInput(cli.in, cli.out)
if flEmail == "" {
flEmail = authconfig.Email
}
}
} else {
// However, if they don't override the username use the
// email from the cmd line if specified. IOW, allow
// then to change/override them. And if not specified, just
// use what's in the config file
if flEmail == "" {
flEmail = authconfig.Email
}
}
authconfig.Username = flUser
authconfig.Password = flPassword
authconfig.Email = flEmail
authconfig.ServerAddress = serverAddress
cli.configFile.AuthConfigs[serverAddress] = authconfig
return authconfig, nil
}
func readInput(in io.Reader, out io.Writer) string {
reader := bufio.NewReader(in)
line, _, err := reader.ReadLine()
if err != nil {
fmt.Fprintln(out, err.Error())
os.Exit(1)
}
return string(line)
}

View File

@@ -0,0 +1,42 @@
package client
import (
"fmt"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdLogout logs a user out from a Docker registry.
//
// If no server is specified, the user will be logged out from the registry's index server.
//
// Usage: docker logout [SERVER]
func (cli *DockerCli) CmdLogout(args ...string) error {
cmd := Cli.Subcmd("logout", []string{"[SERVER]"}, Cli.DockerCommands["logout"].Description+".\nIf no server is specified, the default is defined by the daemon.", true)
cmd.Require(flag.Max, 1)
cmd.ParseFlags(args, true)
var serverAddress string
if len(cmd.Args()) > 0 {
serverAddress = cmd.Arg(0)
} else {
serverAddress = cli.electAuthServer(context.Background())
}
if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok {
fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress)
return nil
}
fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress)
delete(cli.configFile.AuthConfigs, serverAddress)
if err := cli.configFile.Save(); err != nil {
return fmt.Errorf("Failed to save docker config: %v", err)
}
return nil
}

65
vendor/github.com/hyperhq/hypercli/api/client/logs.go generated vendored Normal file
View File

@@ -0,0 +1,65 @@
package client
import (
"fmt"
"io"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/stdcopy"
)
var validDrivers = map[string]bool{
"json-file": true,
"journald": true,
}
// CmdLogs fetches the logs of a given container.
//
// docker logs [OPTIONS] CONTAINER
func (cli *DockerCli) CmdLogs(args ...string) error {
cmd := Cli.Subcmd("logs", []string{"CONTAINER"}, Cli.DockerCommands["logs"].Description, true)
follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
since := cmd.String([]string{"-since"}, "", "Show logs since timestamp")
times := cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
tail := cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
name := cmd.Arg(0)
ctx := context.Background()
c, err := cli.client.ContainerInspect(ctx, name)
if err != nil {
return err
}
if !validDrivers[c.HostConfig.LogConfig.Type] {
return fmt.Errorf("\"logs\" command is supported only for \"json-file\" and \"journald\" logging drivers (got: %s)", c.HostConfig.LogConfig.Type)
}
options := types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Since: *since,
Timestamps: *times,
Follow: *follow,
Tail: *tail,
}
responseBody, err := cli.client.ContainerLogs(ctx, name, options)
if err != nil {
return err
}
defer responseBody.Close()
if c.Config.Tty {
_, err = io.Copy(cli.out, responseBody)
} else {
_, err = stdcopy.StdCopy(cli.out, cli.err, responseBody)
}
return err
}

View File

@@ -0,0 +1,379 @@
package client
import (
"fmt"
"net"
"strings"
"text/tabwriter"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
"github.com/docker/engine-api/types/network"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/stringid"
runconfigopts "github.com/hyperhq/hypercli/runconfig/opts"
)
// CmdNetwork is the parent subcommand for all network commands
//
// Usage: docker network <COMMAND> [OPTIONS]
func (cli *DockerCli) Network(args ...string) error {
cmd := Cli.Subcmd("network", []string{"COMMAND [OPTIONS]"}, networkUsage(), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdNetworkCreate creates a new network with a given name
//
// Usage: docker network create [OPTIONS] <NETWORK-NAME>
func (cli *DockerCli) NetworkCreate(args ...string) error {
cmd := Cli.Subcmd("network create", []string{"NETWORK-NAME"}, "Creates a new network with a name specified by the user", false)
flDriver := cmd.String([]string{"d", "-driver"}, "bridge", "Driver to manage the Network")
flOpts := opts.NewMapOpts(nil, nil)
flIpamDriver := cmd.String([]string{"-ipam-driver"}, "default", "IP Address Management Driver")
flIpamSubnet := opts.NewListOpts(nil)
flIpamIPRange := opts.NewListOpts(nil)
flIpamGateway := opts.NewListOpts(nil)
flIpamAux := opts.NewMapOpts(nil, nil)
flIpamOpt := opts.NewMapOpts(nil, nil)
cmd.Var(&flIpamSubnet, []string{"-subnet"}, "subnet in CIDR format that represents a network segment")
cmd.Var(&flIpamIPRange, []string{"-ip-range"}, "allocate container ip from a sub-range")
cmd.Var(&flIpamGateway, []string{"-gateway"}, "ipv4 or ipv6 Gateway for the master subnet")
cmd.Var(flIpamAux, []string{"-aux-address"}, "auxiliary ipv4 or ipv6 addresses used by Network driver")
cmd.Var(flOpts, []string{"o", "-opt"}, "set driver specific options")
cmd.Var(flIpamOpt, []string{"-ipam-opt"}, "set IPAM driver specific options")
flInternal := cmd.Bool([]string{"-internal"}, false, "restricts external access to the network")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
// Set the default driver to "" if the user didn't set the value.
// That way we can know whether it was user input or not.
driver := *flDriver
if !cmd.IsSet("-driver") && !cmd.IsSet("d") {
driver = ""
}
ipamCfg, err := consolidateIpam(flIpamSubnet.GetAll(), flIpamIPRange.GetAll(), flIpamGateway.GetAll(), flIpamAux.GetAll())
if err != nil {
return err
}
// Construct network create request body
nc := types.NetworkCreate{
Driver: driver,
IPAM: network.IPAM{Driver: *flIpamDriver, Config: ipamCfg, Options: flIpamOpt.GetAll()},
Options: flOpts.GetAll(),
CheckDuplicate: true,
Internal: *flInternal,
}
resp, err := cli.client.NetworkCreate(context.Background(), cmd.Arg(0), nc)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", resp.ID)
return nil
}
// CmdNetworkRm deletes one or more networks
//
// Usage: docker network rm NETWORK-NAME|NETWORK-ID [NETWORK-NAME|NETWORK-ID...]
func (cli *DockerCli) NetworkRm(args ...string) error {
cmd := Cli.Subcmd("network rm", []string{"NETWORK [NETWORK...]"}, "Deletes one or more networks", false)
cmd.Require(flag.Min, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
status := 0
for _, net := range cmd.Args() {
if err := cli.client.NetworkRemove(context.Background(), net); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
// CmdNetworkConnect connects a container to a network
//
// Usage: docker network connect [OPTIONS] <NETWORK> <CONTAINER>
func (cli *DockerCli) NetworkConnect(args ...string) error {
cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
flIPAddress := cmd.String([]string{"-ip"}, "", "IP Address")
flIPv6Address := cmd.String([]string{"-ip6"}, "", "IPv6 Address")
flLinks := opts.NewListOpts(runconfigopts.ValidateLink)
cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
flAliases := opts.NewListOpts(nil)
cmd.Var(&flAliases, []string{"-alias"}, "Add network-scoped alias for the container")
cmd.Require(flag.Min, 2)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
epConfig := &network.EndpointSettings{
IPAMConfig: &network.EndpointIPAMConfig{
IPv4Address: *flIPAddress,
IPv6Address: *flIPv6Address,
},
Links: flLinks.GetAll(),
Aliases: flAliases.GetAll(),
}
return cli.client.NetworkConnect(context.Background(), cmd.Arg(0), cmd.Arg(1), epConfig)
}
// CmdNetworkDisconnect disconnects a container from a network
//
// Usage: docker network disconnect <NETWORK> <CONTAINER>
func (cli *DockerCli) NetworkDisconnect(args ...string) error {
cmd := Cli.Subcmd("network disconnect", []string{"NETWORK CONTAINER"}, "Disconnects container from a network", false)
force := cmd.Bool([]string{"f", "-force"}, false, "Force the container to disconnect from a network")
cmd.Require(flag.Exact, 2)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
return cli.client.NetworkDisconnect(context.Background(), cmd.Arg(0), cmd.Arg(1), *force)
}
// CmdNetworkLs lists all the networks managed by docker daemon
//
// Usage: docker network ls [OPTIONS]
func (cli *DockerCli) NetworkLs(args ...string) error {
cmd := Cli.Subcmd("network ls", nil, "Lists networks", true)
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Do not truncate the output")
flFilter := opts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
// Consolidate all filter flags, and sanity check them early.
// They'll get process after get response from server.
netFilterArgs := filters.NewArgs()
for _, f := range flFilter.GetAll() {
if netFilterArgs, err = filters.ParseFlag(f, netFilterArgs); err != nil {
return err
}
}
options := types.NetworkListOptions{
Filters: netFilterArgs,
}
networkResources, err := cli.client.NetworkList(context.Background(), options)
if err != nil {
return err
}
wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
// unless quiet (-q) is specified, print field titles
if !*quiet {
fmt.Fprintln(wr, "NETWORK ID\tNAME\tDRIVER")
}
for _, networkResource := range networkResources {
ID := networkResource.ID
netName := networkResource.Name
if !*noTrunc {
ID = stringid.TruncateID(ID)
}
if *quiet {
fmt.Fprintln(wr, ID)
continue
}
driver := networkResource.Driver
fmt.Fprintf(wr, "%s\t%s\t%s\t",
ID,
netName,
driver)
fmt.Fprint(wr, "\n")
}
wr.Flush()
return nil
}
// CmdNetworkInspect inspects the network object for more details
//
// Usage: docker network inspect [OPTIONS] <NETWORK> [NETWORK...]
func (cli *DockerCli) NetworkInspect(args ...string) error {
cmd := Cli.Subcmd("network inspect", []string{"NETWORK [NETWORK...]"}, "Displays detailed information on one or more networks", false)
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
cmd.Require(flag.Min, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
inspectSearcher := func(name string) (interface{}, []byte, error) {
i, err := cli.client.NetworkInspect(context.Background(), name)
return i, nil, err
}
return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
}
// Consolidates the ipam configuration as a group from different related configurations
// user can configure network with multiple non-overlapping subnets and hence it is
// possible to correlate the various related parameters and consolidate them.
// consoidateIpam consolidates subnets, ip-ranges, gateways and auxiliary addresses into
// structured ipam data.
func consolidateIpam(subnets, ranges, gateways []string, auxaddrs map[string]string) ([]network.IPAMConfig, error) {
if len(subnets) < len(ranges) || len(subnets) < len(gateways) {
return nil, fmt.Errorf("every ip-range or gateway must have a corresponding subnet")
}
iData := map[string]*network.IPAMConfig{}
// Populate non-overlapping subnets into consolidation map
for _, s := range subnets {
for k := range iData {
ok1, err := subnetMatches(s, k)
if err != nil {
return nil, err
}
ok2, err := subnetMatches(k, s)
if err != nil {
return nil, err
}
if ok1 || ok2 {
return nil, fmt.Errorf("multiple overlapping subnet configuration is not supported")
}
}
iData[s] = &network.IPAMConfig{Subnet: s, AuxAddress: map[string]string{}}
}
// Validate and add valid ip ranges
for _, r := range ranges {
match := false
for _, s := range subnets {
ok, err := subnetMatches(s, r)
if err != nil {
return nil, err
}
if !ok {
continue
}
if iData[s].IPRange != "" {
return nil, fmt.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s)
}
d := iData[s]
d.IPRange = r
match = true
}
if !match {
return nil, fmt.Errorf("no matching subnet for range %s", r)
}
}
// Validate and add valid gateways
for _, g := range gateways {
match := false
for _, s := range subnets {
ok, err := subnetMatches(s, g)
if err != nil {
return nil, err
}
if !ok {
continue
}
if iData[s].Gateway != "" {
return nil, fmt.Errorf("cannot configure multiple gateways (%s, %s) for the same subnet (%s)", g, iData[s].Gateway, s)
}
d := iData[s]
d.Gateway = g
match = true
}
if !match {
return nil, fmt.Errorf("no matching subnet for gateway %s", g)
}
}
// Validate and add aux-addresses
for key, aa := range auxaddrs {
match := false
for _, s := range subnets {
ok, err := subnetMatches(s, aa)
if err != nil {
return nil, err
}
if !ok {
continue
}
iData[s].AuxAddress[key] = aa
match = true
}
if !match {
return nil, fmt.Errorf("no matching subnet for aux-address %s", aa)
}
}
idl := []network.IPAMConfig{}
for _, v := range iData {
idl = append(idl, *v)
}
return idl, nil
}
func subnetMatches(subnet, data string) (bool, error) {
var (
ip net.IP
)
_, s, err := net.ParseCIDR(subnet)
if err != nil {
return false, fmt.Errorf("Invalid subnet %s : %v", s, err)
}
if strings.Contains(data, "/") {
ip, _, err = net.ParseCIDR(data)
if err != nil {
return false, fmt.Errorf("Invalid cidr %s : %v", data, err)
}
} else {
ip = net.ParseIP(data)
}
return s.Contains(ip), nil
}
func networkUsage() string {
networkCommands := map[string]string{
"create": "Create a network",
"connect": "Connect container to a network",
"disconnect": "Disconnect container from a network",
"inspect": "Display detailed network information",
"ls": "List all networks",
"rm": "Remove a network",
}
help := "Commands:\n"
for cmd, description := range networkCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd, description)
}
help += fmt.Sprintf("\nRun 'hyper network COMMAND --help' for more information on a command.")
return help
}

36
vendor/github.com/hyperhq/hypercli/api/client/pause.go generated vendored Normal file
View File

@@ -0,0 +1,36 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdPause pauses all processes within one or more containers.
//
// Usage: docker pause CONTAINER [CONTAINER...]
func (cli *DockerCli) Pause(args ...string) error {
cmd := Cli.Subcmd("pause", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["pause"].Description, true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
var errs []string
for _, name := range cmd.Args() {
if err := cli.client.ContainerPause(ctx, name); err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

63
vendor/github.com/hyperhq/hypercli/api/client/port.go generated vendored Normal file
View File

@@ -0,0 +1,63 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
"github.com/docker/go-connections/nat"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdPort lists port mappings for a container.
// If a private port is specified, it also shows the public-facing port that is NATed to the private port.
//
// Usage: docker port CONTAINER [PRIVATE_PORT[/PROTO]]
func (cli *DockerCli) CmdPort(args ...string) error {
cmd := Cli.Subcmd("port", []string{"CONTAINER [PRIVATE_PORT[/PROTO]]"}, Cli.DockerCommands["port"].Description, true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
c, err := cli.client.ContainerInspect(ctx, cmd.Arg(0))
if err != nil {
return err
}
if cmd.NArg() == 2 {
var (
port = cmd.Arg(1)
proto = "tcp"
parts = strings.SplitN(port, "/", 2)
)
if len(parts) == 2 && len(parts[1]) != 0 {
port = parts[0]
proto = parts[1]
}
natPort := port + "/" + proto
newP, err := nat.NewPort(proto, port)
if err != nil {
return err
}
if frontends, exists := c.NetworkSettings.Ports[newP]; exists && frontends != nil {
for _, frontend := range frontends {
fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIP, frontend.HostPort)
}
return nil
}
return fmt.Errorf("Error: No public port '%s' published for %s", natPort, cmd.Arg(0))
}
for from, frontends := range c.NetworkSettings.Ports {
for _, frontend := range frontends {
fmt.Fprintf(cli.out, "%s -> %s:%s\n", from, frontend.HostIP, frontend.HostPort)
}
}
return nil
}

88
vendor/github.com/hyperhq/hypercli/api/client/ps.go generated vendored Normal file
View File

@@ -0,0 +1,88 @@
package client
import (
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
"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"
"golang.org/x/net/context"
)
// CmdPs outputs a list of Docker containers.
//
// Usage: docker ps [OPTIONS]
func (cli *DockerCli) CmdPs(args ...string) error {
var (
err error
psFilterArgs = filters.NewArgs()
cmd = Cli.Subcmd("ps", nil, Cli.DockerCommands["ps"].Description, true)
quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes")
all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
noTrunc = cmd.Bool([]string{"-no-trunc"}, false, "Don't truncate output")
nLatest = cmd.Bool([]string{"l", "-latest"}, false, "Show the latest created container (includes all states)")
since = cmd.String([]string{"#-since"}, "", "Show containers created since Id or Name (includes all states)")
before = cmd.String([]string{"#-before"}, "", "Only show containers created before Id or Name")
last = cmd.Int([]string{"n"}, -1, "Show n last created containers (includes all states)")
format = cmd.String([]string{"-format"}, "", "Pretty-print containers using a Go template")
flFilter = opts.NewListOpts(nil)
)
cmd.Require(flag.Exact, 0)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.ParseFlags(args, true)
if *last == -1 && *nLatest {
*last = 1
}
// Consolidate all filter flags, and sanity check them.
// They'll get processed in the daemon/server.
for _, f := range flFilter.GetAll() {
if psFilterArgs, err = filters.ParseFlag(f, psFilterArgs); err != nil {
return err
}
}
options := types.ContainerListOptions{
All: *all,
Limit: *last,
Since: *since,
Before: *before,
Size: *size,
Filter: psFilterArgs,
}
containers, err := cli.client.ContainerList(context.Background(), options)
if err != nil {
return err
}
f := *format
if len(f) == 0 {
if len(cli.PsFormat()) > 0 && !*quiet {
f = cli.PsFormat()
} else {
f = "table"
}
}
psCtx := formatter.ContainerContext{
Context: formatter.Context{
Output: cli.out,
Format: f,
Quiet: *quiet,
Trunc: !*noTrunc,
},
Size: *size,
Containers: containers,
}
psCtx.Write()
return nil
}

94
vendor/github.com/hyperhq/hypercli/api/client/pull.go generated vendored Normal file
View File

@@ -0,0 +1,94 @@
package client
import (
"errors"
"fmt"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/pkg/jsonmessage"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/reference"
"github.com/hyperhq/hypercli/registry"
)
// CmdPull pulls an image or a repository from the registry.
//
// Usage: docker pull [OPTIONS] IMAGENAME[:TAG|@DIGEST]
func (cli *DockerCli) CmdPull(args ...string) error {
cmd := Cli.Subcmd("pull", []string{"NAME[:TAG|@DIGEST]"}, Cli.DockerCommands["pull"].Description, true)
allTags := cmd.Bool([]string{}, false, "Download all tagged images in the repository")
addTrustedFlags(cmd, true)
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
remote := cmd.Arg(0)
distributionRef, err := reference.ParseNamed(remote)
if err != nil {
return err
}
if *allTags && !reference.IsNameOnly(distributionRef) {
return errors.New("tag can't be used with --all-tags/-a")
}
if err = cli.checkCloudConfig(); err != nil {
return err
}
if !*allTags && reference.IsNameOnly(distributionRef) {
distributionRef = reference.WithDefaultTag(distributionRef)
fmt.Fprintf(cli.out, "Using default tag: %s\n", reference.DefaultTag)
}
var tag string
switch x := distributionRef.(type) {
case reference.Canonical:
tag = x.Digest().String()
case reference.NamedTagged:
tag = x.Tag()
}
ref := registry.ParseReference(tag)
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ParseRepositoryInfo(distributionRef)
if err != nil {
return err
}
ctx := context.Background()
authConfig := cli.resolveAuthConfig(ctx, cli.configFile.AuthConfigs, repoInfo.Index)
requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "pull")
if isTrusted() && !ref.HasDigest() {
// Check if tag is digest
return cli.trustedPull(ctx, repoInfo, ref, authConfig, requestPrivilege)
}
return cli.imagePullPrivileged(ctx, authConfig, distributionRef.String(), requestPrivilege, *allTags)
}
func (cli *DockerCli) imagePullPrivileged(ctx context.Context, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc, all bool) error {
encodedAuth, err := encodeAuthToBase64(authConfig)
if err != nil {
return err
}
options := types.ImagePullOptions{
PrivilegeFunc: requestPrivilege,
RegistryAuth: encodedAuth,
All: all,
}
responseBody, err := cli.client.ImagePull(context.Background(), ref, options)
if err != nil {
return err
}
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil)
}

66
vendor/github.com/hyperhq/hypercli/api/client/push.go generated vendored Normal file
View File

@@ -0,0 +1,66 @@
package client
import (
"io"
"github.com/docker/engine-api/types"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/pkg/jsonmessage"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/reference"
"github.com/hyperhq/hypercli/registry"
"golang.org/x/net/context"
)
// CmdPush pushes an image or repository to the registry.
//
// Usage: hyper push NAME[:TAG]
func (cli *DockerCli) CmdPush(args ...string) error {
cmd := Cli.Subcmd("push", []string{"NAME[:TAG]"}, Cli.DockerCommands["push"].Description, true)
addTrustedFlags(cmd, false)
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
ref, err := reference.ParseNamed(cmd.Arg(0))
if err != nil {
return err
}
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
return err
}
ctx := context.Background()
// Resolve the Auth config relevant for this server
authConfig := cli.resolveAuthConfig(ctx, cli.configFile.AuthConfigs, repoInfo.Index)
requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "push")
if isTrusted() {
return cli.trustedPush(ctx, repoInfo, ref, authConfig, requestPrivilege)
}
responseBody, err := cli.imagePushPrivileged(ctx, authConfig, ref.String(), requestPrivilege)
if err != nil {
return err
}
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil)
}
func (cli *DockerCli) imagePushPrivileged(ctx context.Context, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc) (io.ReadCloser, error) {
encodedAuth, err := encodeAuthToBase64(authConfig)
if err != nil {
return nil, err
}
options := types.ImagePushOptions{
RegistryAuth: encodedAuth,
PrivilegeFunc: requestPrivilege,
}
return cli.client.ImagePush(ctx, ref, options)
}

View File

@@ -0,0 +1,34 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdRename renames a container.
//
// Usage: docker rename OLD_NAME NEW_NAME
func (cli *DockerCli) CmdRename(args ...string) error {
cmd := Cli.Subcmd("rename", []string{"OLD_NAME NEW_NAME"}, Cli.DockerCommands["rename"].Description, true)
cmd.Require(flag.Exact, 2)
cmd.ParseFlags(args, true)
oldName := strings.TrimSpace(cmd.Arg(0))
newName := strings.TrimSpace(cmd.Arg(1))
if oldName == "" || newName == "" {
return fmt.Errorf("Error: Neither old nor new names may be empty")
}
if err := cli.client.ContainerRename(context.Background(), oldName, newName); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
return fmt.Errorf("Error: failed to rename container named %s", oldName)
}
return nil
}

View File

@@ -0,0 +1,37 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdRestart restarts one or more containers.
//
// Usage: docker restart [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdRestart(args ...string) error {
cmd := Cli.Subcmd("restart", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["restart"].Description, true)
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
var errs []string
for _, name := range cmd.Args() {
if err := cli.client.ContainerRestart(ctx, name, *nSeconds); err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

61
vendor/github.com/hyperhq/hypercli/api/client/rm.go generated vendored Normal file
View File

@@ -0,0 +1,61 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdRm removes one or more containers.
//
// Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdRm(args ...string) error {
cmd := Cli.Subcmd("rm", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["rm"].Description, true)
v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container")
link := cmd.Bool([]string{"l", "-link"}, false, "Remove the specified link")
force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
var errs []string
for _, name := range cmd.Args() {
if name == "" {
return fmt.Errorf("Container name cannot be empty")
}
name = strings.Trim(name, "/")
warnings, err := cli.removeContainer(ctx, name, *v, *link, *force)
if err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintf(cli.out, "%s\n", name)
for _, w := range warnings {
fmt.Fprintf(cli.out, "NOTICE : %s\n", w)
}
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}
func (cli *DockerCli) removeContainer(ctx context.Context, containerID string, removeVolumes, removeLinks, force bool) ([]string, error) {
options := types.ContainerRemoveOptions{
RemoveVolumes: removeVolumes,
RemoveLinks: removeLinks,
Force: force,
}
warnings, err := cli.client.ContainerRemove(ctx, containerID, options)
if err != nil {
return nil, err
}
return warnings, nil
}

60
vendor/github.com/hyperhq/hypercli/api/client/rmi.go generated vendored Normal file
View File

@@ -0,0 +1,60 @@
package client
import (
"fmt"
"net/url"
"strings"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdRmi removes all images with the specified name(s).
//
// Usage: docker rmi [OPTIONS] IMAGE [IMAGE...]
func (cli *DockerCli) CmdRmi(args ...string) error {
cmd := Cli.Subcmd("rmi", []string{"IMAGE [IMAGE...]"}, Cli.DockerCommands["rmi"].Description, true)
force := cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image")
noprune := cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
v := url.Values{}
if *force {
v.Set("force", "1")
}
if *noprune {
v.Set("noprune", "1")
}
ctx := context.Background()
var errs []string
for _, name := range cmd.Args() {
options := types.ImageRemoveOptions{
Force: *force,
PruneChildren: !*noprune,
}
dels, err := cli.client.ImageRemove(ctx, name, options)
if err != nil {
errs = append(errs, err.Error())
} else {
for _, del := range dels {
if del.Deleted != "" {
fmt.Fprintf(cli.out, "Deleted: %s\n", del.Deleted)
} else {
fmt.Fprintf(cli.out, "Untagged: %s\n", del.Untagged)
}
}
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

312
vendor/github.com/hyperhq/hypercli/api/client/run.go generated vendored Normal file
View File

@@ -0,0 +1,312 @@
package client
import (
"fmt"
"io"
"os"
"runtime"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/engine-api/types"
"github.com/docker/libnetwork/resolvconf/dns"
Cli "github.com/hyperhq/hypercli/cli"
derr "github.com/hyperhq/hypercli/errors"
"github.com/hyperhq/hypercli/opts"
"github.com/hyperhq/hypercli/pkg/promise"
"github.com/hyperhq/hypercli/pkg/signal"
"github.com/hyperhq/hypercli/pkg/stringid"
runconfigopts "github.com/hyperhq/hypercli/runconfig/opts"
"golang.org/x/net/context"
)
type InitVolume struct {
Source string
Destination string
Name string
}
func (cid *cidFile) Close() error {
cid.file.Close()
if !cid.written {
if err := os.Remove(cid.path); err != nil {
return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
}
}
return nil
}
func (cid *cidFile) Write(id string) error {
if _, err := cid.file.Write([]byte(id)); err != nil {
return fmt.Errorf("Failed to write the container ID to the file: %s", err)
}
cid.written = true
return nil
}
// if container start fails with 'command not found' error, return 127
// if container start fails with 'command cannot be invoked' error, return 126
// return 125 for generic docker daemon failures
func runStartContainerErr(err error) error {
trimmedErr := strings.Trim(err.Error(), "Error response from daemon: ")
statusError := Cli.StatusError{}
derrCmdNotFound := derr.ErrorCodeCmdNotFound.Message()
derrCouldNotInvoke := derr.ErrorCodeCmdCouldNotBeInvoked.Message()
derrNoSuchImage := derr.ErrorCodeNoSuchImageHash.Message()
derrNoSuchImageTag := derr.ErrorCodeNoSuchImageTag.Message()
switch trimmedErr {
case derrCmdNotFound:
statusError = Cli.StatusError{StatusCode: 127}
case derrCouldNotInvoke:
statusError = Cli.StatusError{StatusCode: 126}
case derrNoSuchImage, derrNoSuchImageTag:
statusError = Cli.StatusError{StatusCode: 125}
default:
statusError = Cli.StatusError{StatusCode: 125}
}
return statusError
}
// CmdRun runs a command in a new container.
//
// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
func (cli *DockerCli) CmdRun(args ...string) error {
cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["run"].Description, true)
addTrustedFlags(cmd, true)
// These are flags not stored in Config/HostConfig
var (
flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits")
flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
flSigProxy = cmd.Bool([]string{}, true, "Proxy received signals to the process")
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
flDetachKeys = cmd.String([]string{}, "", "Override the key sequence for detaching a container")
flAttach *opts.ListOpts
ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d")
ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
ErrConflictProtectionAutoRemove = fmt.Errorf("Conflicting options: --rm and --protection")
)
config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
// just in case the Parse does not exit
if err != nil {
cmd.ReportError(err.Error(), true)
os.Exit(125)
}
if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 {
fmt.Fprintf(cli.err, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.\n")
}
if len(hostConfig.DNS) > 0 {
// check the DNS settings passed via --dns against
// localhost regexp to warn if they are trying to
// set a DNS to a localhost address
for _, dnsIP := range hostConfig.DNS {
if dns.IsLocalhost(dnsIP) {
fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
break
}
}
}
if config.Image == "" {
cmd.Usage()
return nil
}
config.ArgsEscaped = false
if !*flDetach {
if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
return err
}
} else {
if fl := cmd.Lookup("-attach"); fl != nil {
flAttach = fl.Value.(*opts.ListOpts)
if flAttach.Len() != 0 {
return ErrConflictAttachDetach
}
}
if *flAutoRemove {
return ErrConflictDetachAutoRemove
}
config.AttachStdin = false
config.AttachStdout = false
config.AttachStderr = false
config.StdinOnce = false
}
// Disable flSigProxy when in TTY mode
sigProxy := *flSigProxy
if config.Tty {
sigProxy = false
}
// Telling the Windows daemon the initial size of the tty during start makes
// a far better user experience rather than relying on subsequent resizes
// to cause things to catch up.
if runtime.GOOS == "windows" {
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
}
ctx := context.Background()
createResponse, err := cli.createContainer(ctx, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
if err != nil {
cmd.ReportError(err.Error(), true)
return runStartContainerErr(err)
}
if sigProxy {
sigc := cli.forwardAllSignals(ctx, createResponse.ID)
defer signal.StopCatch(sigc)
}
var (
waitDisplayID chan struct{}
errCh chan error
)
if !config.AttachStdout && !config.AttachStderr {
// Make this asynchronous to allow the client to write to stdin before having to read the ID
waitDisplayID = make(chan struct{})
go func() {
defer close(waitDisplayID)
fmt.Fprintf(cli.out, "%s\n", createResponse.ID)
}()
}
if *flAutoRemove {
if hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure() {
return ErrConflictRestartPolicyAndAutoRemove
}
if _, ok := config.Labels["sh_hyper_container_protection"]; ok {
return ErrConflictProtectionAutoRemove
}
}
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
var (
out, stderr io.Writer
in io.ReadCloser
)
if config.AttachStdin {
in = cli.in
}
if config.AttachStdout {
out = cli.out
}
if config.AttachStderr {
if config.Tty {
stderr = cli.out
} else {
stderr = cli.err
}
}
if *flDetachKeys != "" {
cli.configFile.DetachKeys = *flDetachKeys
}
options := types.ContainerAttachOptions{
Stream: true,
Stdin: config.AttachStdin,
Stdout: config.AttachStdout,
Stderr: config.AttachStderr,
DetachKeys: cli.configFile.DetachKeys,
}
resp, err := cli.client.ContainerAttach(ctx, createResponse.ID, options)
if err != nil {
return err
}
if in != nil && config.Tty {
if err := cli.setRawTerminal(); err != nil {
return err
}
defer cli.restoreTerminal(in)
}
errCh = promise.Go(func() error {
return cli.holdHijackedConnection(config.Tty, in, out, stderr, resp)
})
}
if *flAutoRemove {
defer func() {
if _, err := cli.removeContainer(ctx, createResponse.ID, true, false, false); err != nil {
fmt.Fprintf(cli.err, "%v\n", err)
}
}()
}
//start the container
if err := cli.client.ContainerStart(ctx, createResponse.ID, ""); err != nil {
cmd.ReportError(err.Error(), false)
return runStartContainerErr(err)
}
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
if err := cli.monitorTtySize(ctx, createResponse.ID, false); err != nil {
fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
}
}
if errCh != nil {
if err := <-errCh; err != nil {
logrus.Debugf("Error hijack: %s", err)
return err
}
}
// Detached mode: wait for the id to be displayed and return.
if !config.AttachStdout && !config.AttachStderr {
// Detached mode
<-waitDisplayID
return nil
}
var status int
// Attached mode
if *flAutoRemove {
// Warn user if they detached us
js, err := cli.client.ContainerInspect(ctx, createResponse.ID)
if err != nil {
return runStartContainerErr(err)
}
if js.State.Running == true || js.State.Paused == true {
fmt.Fprintf(cli.err, "Detached from %s, awaiting its termination in order to uphold \"--rm\".\n",
stringid.TruncateID(createResponse.ID))
}
// Autoremove: wait for the container to finish, retrieve
// the exit code and remove the container
if status, err = cli.client.ContainerWait(ctx, createResponse.ID); err != nil {
return runStartContainerErr(err)
}
if _, status, err = getExitCode(ctx, cli, createResponse.ID); err != nil {
return err
}
} else {
// No Autoremove: Simply retrieve the exit code
if !config.Tty {
// In non-TTY mode, we can't detach, so we must wait for container exit
if status, err = cli.client.ContainerWait(ctx, createResponse.ID); err != nil {
return err
}
} else {
// In TTY mode, there is a race: if the process dies too slowly, the state could
// be updated after the getExitCode call and result in the wrong exit code being reported
if _, status, err = getExitCode(ctx, cli, createResponse.ID); err != nil {
return err
}
}
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}

42
vendor/github.com/hyperhq/hypercli/api/client/save.go generated vendored Normal file
View File

@@ -0,0 +1,42 @@
package client
import (
"errors"
"io"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdSave saves one or more images to a tar archive.
//
// The tar archive is written to STDOUT by default, or written to a file.
//
// Usage: docker save [OPTIONS] IMAGE [IMAGE...]
func (cli *DockerCli) Save(args ...string) error {
cmd := Cli.Subcmd("save", []string{"IMAGE [IMAGE...]"}, Cli.DockerCommands["save"].Description+" (streamed to STDOUT by default)", true)
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if *outfile == "" && cli.isTerminalOut {
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
}
responseBody, err := cli.client.ImageSave(context.Background(), cmd.Args())
if err != nil {
return err
}
defer responseBody.Close()
if *outfile == "" {
_, err := io.Copy(cli.out, responseBody)
return err
}
return copyToFile(*outfile, responseBody)
}

View File

@@ -0,0 +1,99 @@
package client
import (
"fmt"
"net/url"
"sort"
"strings"
"text/tabwriter"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
registrytypes "github.com/docker/engine-api/types/registry"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/stringutils"
"github.com/hyperhq/hypercli/registry"
)
// CmdSearch searches the Docker Hub for images.
//
// Usage: docker search [OPTIONS] TERM
func (cli *DockerCli) CmdSearch(args ...string) error {
cmd := Cli.Subcmd("search", []string{"TERM"}, Cli.DockerCommands["search"].Description, true)
noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Don't truncate output")
automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds")
stars := cmd.Uint([]string{"s", "-stars"}, 0, "Only displays with at least x stars")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
name := cmd.Arg(0)
v := url.Values{}
v.Set("term", name)
indexInfo, err := registry.ParseSearchIndexInfo(name)
if err != nil {
return err
}
if err = cli.checkCloudConfig(); err != nil {
return err
}
ctx := context.Background()
authConfig := cli.resolveAuthConfig(ctx, cli.configFile.AuthConfigs, indexInfo)
requestPrivilege := cli.registryAuthenticationPrivilegedFunc(indexInfo, "search")
encodedAuth, err := encodeAuthToBase64(authConfig)
if err != nil {
return err
}
options := types.ImageSearchOptions{
RegistryAuth: encodedAuth,
PrivilegeFunc: requestPrivilege,
}
unorderedResults, err := cli.client.ImageSearch(ctx, name, options)
if err != nil {
return err
}
results := searchResultsByStars(unorderedResults)
sort.Sort(results)
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
for _, res := range results {
if (*automated && !res.IsAutomated) || (int(*stars) > res.StarCount) {
continue
}
desc := strings.Replace(res.Description, "\n", " ", -1)
desc = strings.Replace(desc, "\r", " ", -1)
if !*noTrunc && len(desc) > 45 {
desc = stringutils.Truncate(desc, 42) + "..."
}
fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount)
if res.IsOfficial {
fmt.Fprint(w, "[OK]")
}
fmt.Fprint(w, "\t")
if res.IsAutomated {
fmt.Fprint(w, "[OK]")
}
fmt.Fprint(w, "\n")
}
w.Flush()
return nil
}
// SearchResultsByStars sorts search results in descending order by number of stars.
type searchResultsByStars []registrytypes.SearchResult
func (r searchResultsByStars) Len() int { return len(r) }
func (r searchResultsByStars) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r searchResultsByStars) Less(i, j int) bool { return r[j].StarCount < r[i].StarCount }

View File

@@ -0,0 +1,423 @@
package client
import (
"fmt"
"io/ioutil"
"strconv"
"strings"
"text/tabwriter"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
"github.com/docker/engine-api/types/strslice"
Cli "github.com/hyperhq/hypercli/cli"
ropts "github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/signal"
"github.com/hyperhq/hypercli/runconfig/opts"
"golang.org/x/net/context"
)
// CmdService is the parent subcommand for all service commands
//
// Usage: docker service <COMMAND> [OPTIONS]
func (cli *DockerCli) CmdService(args ...string) error {
cmd := Cli.Subcmd("service", []string{"COMMAND [OPTIONS]"}, serviceUsage(), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdServiceCreate creates a new service with a given name
//
// Usage: hyper service create [OPTIONS] COUNT
func (cli *DockerCli) CmdServiceCreate(args ...string) error {
cmd := Cli.Subcmd("service create", []string{"IMAGE"}, "Create a new service", false)
var (
flSecurityGroups = ropts.NewListOpts(nil)
flEnv = ropts.NewListOpts(opts.ValidateEnv)
flLabels = ropts.NewListOpts(opts.ValidateEnv)
flEnvFile = ropts.NewListOpts(nil)
flVolumes = ropts.NewListOpts(nil)
flLabelsFile = ropts.NewListOpts(nil)
flName = cmd.String([]string{"-name"}, "", "Service name")
flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
flEntrypoint = cmd.String([]string{"-entrypoint"}, "", "Overwrite the default ENTRYPOINT of the image")
flNetMode = cmd.String([]string{}, "bridge", "Connect containers to a network, only bridge is supported now")
flStopSignal = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
flContainerSize = cmd.String([]string{"-size"}, "s4", "The size of service containers (e.g. s1, s2, s3, s4, m1, m2, m3, l1, l2, l3)")
flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
flSSLCert = cmd.String([]string{"-ssl-cert"}, "", "SSL cert file for httpsTerm service")
flServicePort = cmd.Int([]string{"-service-port"}, 0, "Publish port of the service")
flContainerPort = cmd.Int([]string{"-container-port"}, 0, "Container port of the service, default same with service port")
flReplicas = cmd.Int([]string{"-replicas"}, -1, "Number of containers belonging to this service")
flHealthCheckInterval = cmd.Int([]string{"-health-check-interval"}, 3, "Interval in seconds for health checking the containers")
flHealthCheckFall = cmd.Int([]string{"-health-check-fall"}, 3, "Number of consecutive valid health checks before considering the server as DOWN")
flHealthCheckRise = cmd.Int([]string{"-health-check-rise"}, 2, "Number of consecutive valid health checks before considering the server as UP")
flSessionAffinity = cmd.Bool([]string{"-session-affinity"}, false, "Whether the service uses sticky sessions")
flAlgorithm = cmd.String([]string{"-algorithm"}, types.LBAlgorithmRoundRobin, "Algorithm of the service (e.g. roundrobin, leastconn, source)")
flProtocol = cmd.String([]string{"-protocol"}, types.LBProtocolTCP, "Protocol of the service (e.g. http, https, tcp, httpsTerm).")
)
cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
cmd.Var(&flSecurityGroups, []string{"-sg"}, "Security group for each container")
cmd.Var(&flVolumes, []string{"v", "--volume"}, "Volume for each container")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
if *flReplicas <= 0 {
return fmt.Errorf("replicas must be bigger than 0")
}
var binds = map[string]struct{}{}
// add any bind targets to the list of container services
for bind := range flVolumes.GetMap() {
binds[bind] = struct{}{}
}
var (
parsedArgs = cmd.Args()
runCmd strslice.StrSlice
entrypoint strslice.StrSlice
image = cmd.Arg(0)
)
if _, _, err = cli.client.ImageInspectWithRaw(context.Background(), image, false); err != nil && strings.Contains(err.Error(), "No such image") {
if err := cli.pullImage(context.Background(), image); err != nil {
return err
}
}
if len(parsedArgs) > 1 {
runCmd = strslice.StrSlice(parsedArgs[1:])
}
if *flEntrypoint != "" {
entrypoint = strslice.StrSlice{*flEntrypoint}
}
// collect all the environment variables for the container
envVariables, err := opts.ReadKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
if err != nil {
return err
}
// collect all the labels for the container
labels, err := opts.ReadKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
if err != nil {
return err
}
var sgs = map[string]struct{}{}
for sg := range flSecurityGroups.GetMap() {
sgs[sg] = struct{}{}
}
sslData := []byte{}
if *flSSLCert != "" {
sslData, err = ioutil.ReadFile(*flSSLCert)
}
sv := types.Service{
Name: *flName,
Image: image,
WorkingDir: *flWorkingDir,
ContainerSize: *flContainerSize,
ServicePort: *flServicePort,
ContainerPort: *flContainerPort,
Replicas: *flReplicas,
Entrypoint: entrypoint,
Cmd: runCmd,
Env: envVariables,
Volumes: binds,
Labels: opts.ConvertKVStringsToMap(labels),
SecurityGroups: sgs,
Tty: *flTty,
Stdin: *flStdin,
NetMode: *flNetMode,
StopSignal: *flStopSignal,
HealthCheckInterval: *flHealthCheckInterval,
HealthCheckFall: *flHealthCheckFall,
HealthCheckRise: *flHealthCheckRise,
Algorithm: *flAlgorithm,
Protocol: *flProtocol,
SessionAffinity: *flSessionAffinity,
SSLCert: string(sslData),
}
service, err := cli.client.ServiceCreate(context.Background(), sv)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "Service %s is created.\n", service.Name)
return nil
}
// CmdServiceDelete deletes one or more services
//
// Usage: hyper service rm service [service...]
func (cli *DockerCli) CmdServiceRm(args ...string) error {
cmd := Cli.Subcmd("service rm", []string{"service [service...]"}, "Remove one or more services", false)
flKeep := cmd.Bool([]string{"-keep"}, false, "Keep the service container")
cmd.Require(flag.Min, 1)
if err := cmd.ParseFlags(args, true); err != nil {
return err
}
status := 0
for _, sn := range cmd.Args() {
if err := cli.client.ServiceDelete(context.Background(), sn, *flKeep); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
fmt.Fprintf(cli.out, "%s\n", sn)
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
return nil
}
// CmdServiceLs lists all the services
//
// Usage: hyper service ls [OPTIONS]
func (cli *DockerCli) CmdServiceLs(args ...string) error {
cmd := Cli.Subcmd("service ls", nil, "Lists services", true)
flFilter := ropts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
// Consolidate all filter flags, and sanity check them early.
// They'll get process after get response from server.
serviceFilterArgs := filters.NewArgs()
for _, f := range flFilter.GetAll() {
if serviceFilterArgs, err = filters.ParseFlag(f, serviceFilterArgs); err != nil {
return err
}
}
options := types.ServiceListOptions{
Filters: serviceFilterArgs,
}
services, err := cli.client.ServiceList(context.Background(), options)
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "Name\tFIP\tContainers\tStatus\tMessage\n")
for _, service := range services {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", service.Name, service.FIP, showContainersInList(service.Containers), service.Status, service.Message)
}
w.Flush()
return nil
}
func showContainersInList(containers []string) string {
var result = []string{}
for _, c := range containers {
result = append(result, c[:12])
}
if len(result) > 2 {
return strings.Join([]string{result[0], result[1], "..."}, ", ")
}
return strings.Join(result, ", ")
}
// CmdServiceInspect
//
// Usage: docker service inspect [OPTIONS] service [service...]
func (cli *DockerCli) CmdServiceInspect(args ...string) error {
cmd := Cli.Subcmd("service inspect", []string{"service [service...]"}, "Display detailed information on the given service", 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.ServiceInspect(ctx, name)
return i, nil, err
}
return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
}
// CmdServiceScale
//
// Usage: hyper service scale [OPTIONS] SERVICE=REPLICAS [SERVICE=REPLICAS...]
func (cli *DockerCli) CmdServiceScale(args ...string) error {
cmd := Cli.Subcmd("service scale", []string{"SERVICE=REPLICAS [SERVICE=REPLICAS...]"}, "", true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if err := cmd.Parse(args); err != nil {
return nil
}
ctx := context.Background()
for _, sr := range cmd.Args() {
fields := strings.SplitN(sr, "=", 2)
if len(fields) != 2 {
fmt.Fprintf(cli.err, "invalid argument")
continue
}
replicas, err := strconv.Atoi(fields[1])
if err != nil {
fmt.Fprintf(cli.err, "%v\n", err)
continue
}
sv := types.ServiceUpdate{
Replicas: &replicas,
}
service, err := cli.client.ServiceUpdate(ctx, fields[0], sv)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", service.Name)
}
return nil
}
// CmdServiceRolling_update
//
// Usage: hyper service rolling-update [OPTIONS] SERVICE [SERVICE...]
func (cli *DockerCli) CmdServiceRolling_update(args ...string) error {
cmd := Cli.Subcmd("service rolling-update", []string{"SERVICE [SERVICE...]"}, "Perform a rolling update of the given service", true)
flImage := cmd.String([]string{"-image"}, "", "New container image")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if err := cmd.Parse(args); err != nil {
return nil
}
if len(*flImage) == 0 {
return fmt.Errorf("image is required for rolling-update")
}
ctx := context.Background()
if _, _, err := cli.client.ImageInspectWithRaw(ctx, *flImage, false); err != nil && strings.Contains(err.Error(), "No such image") {
if err := cli.pullImage(ctx, *flImage); err != nil {
return err
}
}
for _, sr := range cmd.Args() {
sv := types.ServiceUpdate{
Image: flImage,
}
service, err := cli.client.ServiceUpdate(ctx, sr, sv)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "Rolling-update is requested for service %s.\n", service.Name)
}
return nil
}
// CmdServiceAttach_fip
//
// Usage: hyper service attach_fip [OPTIONS] SERVICE [SERVICE...]
func (cli *DockerCli) CmdServiceAttach_fip(args ...string) error {
cmd := Cli.Subcmd("service attach-fip", []string{"SERVICE"}, "Attach a fip to the service", true)
flFip := cmd.String([]string{"-fip"}, "", "Attach a fip to the service")
cmd.Require(flag.Exact, 1)
cmd.ParseFlags(args, true)
if err := cmd.Parse(args); err != nil {
return nil
}
if *flFip == "" {
return fmt.Errorf("Error: please provide the attached FIP via --fip")
}
ctx := context.Background()
sv := types.ServiceUpdate{
FIP: flFip,
}
service, err := cli.client.ServiceUpdate(ctx, cmd.Arg(0), sv)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", service.Name)
return nil
}
// CmdServiceDetach_fip
//
// Usage: hyper service detach_fip [OPTIONS] SERVICE [SERVICE...]
func (cli *DockerCli) CmdServiceDetach_fip(args ...string) error {
cmd := Cli.Subcmd("service detach-fip", []string{"SERVICE [SERVICE...]"}, "Detach a fip from the service", true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if err := cmd.Parse(args); err != nil {
return nil
}
ctx := context.Background()
fip := ""
for _, sr := range cmd.Args() {
sv := types.ServiceUpdate{
FIP: &fip,
}
service, err := cli.client.ServiceUpdate(ctx, sr, sv)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", service.Name)
}
return nil
}
func serviceUsage() string {
serviceCommands := [][]string{
{"create", "Create a service"},
{"inspect", "Display detailed information on the given service"},
{"ls", "List all services"},
{"scale", "Scale the service"},
{"rolling-update", "Perform a rolling update of the given service"},
{"attach-fip", "Attach a fip to the service"},
{"detach-fip", "Detach the fip from the service"},
{"rm", "Remove one or more services"},
}
help := "Commands:\n"
for _, cmd := range serviceCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
}
help += fmt.Sprintf("\nRun 'hyper service COMMAND --help' for more information on a command.")
return help
}

167
vendor/github.com/hyperhq/hypercli/api/client/sg.go generated vendored Normal file
View File

@@ -0,0 +1,167 @@
package client
import (
"encoding/json"
"fmt"
"os"
"text/tabwriter"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"golang.org/x/net/context"
"gopkg.in/yaml.v2"
)
// CmdSg is the parent subcommand for all sg commands
//
// Usage: hyper sg <COMMAND> [OPTIONS]
func (cli *DockerCli) CmdSg(args ...string) error {
cmd := Cli.Subcmd("sg", []string{"COMMAND [OPTIONS]"}, sgUsage(), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdSgCreate creates a new sg with a given name
//
// Usage: hyper sg create [OPTIONS] NAME
func (cli *DockerCli) CmdSgCreate(args ...string) error {
cmd := Cli.Subcmd("sg create", []string{"NAME"}, "Create a new security group", false)
file := cmd.String([]string{"f", "-file"}, "", "Yaml file to create security group")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
data, err := os.Open(*file)
if err != nil {
return err
}
err = cli.client.SgCreate(context.Background(), cmd.Arg(0), data)
if err != nil {
return err
}
return nil
}
// CmdSgRm removes a sg with a given name
//
// Usage: hyper sg rm [OPTIONS] NAME
func (cli *DockerCli) CmdSgRm(args ...string) error {
cmd := Cli.Subcmd("sg rm", []string{"NAME"}, "Remove a security group", false)
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
err = cli.client.SgRm(context.Background(), cmd.Arg(0))
if err != nil {
return err
}
return nil
}
// CmdSgLs list security groups
//
// Usage: hyper sg ls [OPTIONS]
func (cli *DockerCli) CmdSgLs(args ...string) error {
cmd := Cli.Subcmd("sg ls", []string{}, "List security groups", false)
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
sgs, err := cli.client.SgLs(context.Background())
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "Name\tDescription")
fmt.Fprintf(w, "\n")
for _, sg := range sgs {
fmt.Fprintf(w, "%s\t%s\n", sg.GroupName, sg.Description)
}
w.Flush()
return nil
}
// CmdSgInspect Inspect security groups
//
// Usage: hyper sg inspect [OPTIONS] NAME
func (cli *DockerCli) CmdSgInspect(args ...string) error {
cmd := Cli.Subcmd("sg inspect", []string{"NAME"}, "Inspect the security group", false)
output := cmd.String([]string{"o", "-output"}, "json", "Output format with inspect operation (e.g. yaml or json)")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
sg, err := cli.client.SgInspect(context.Background(), cmd.Arg(0))
if err != nil {
return err
}
var data []byte
if *output == "json" {
data, err = json.MarshalIndent(sg, "", "\t")
} else {
data, err = yaml.Marshal(sg)
}
if err != nil {
return err
}
fmt.Printf("%s\n", string(data))
return nil
}
// CmdSgUpdate Update the security group
//
// Usage: hyper sg update [OPTIONS] NAME
func (cli *DockerCli) CmdSgUpdate(args ...string) error {
cmd := Cli.Subcmd("sg update", []string{"NAME"}, "Update the security group", false)
file := cmd.String([]string{"f", "-file"}, "", "Yaml file to update security group")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
data, err := os.Open(*file)
if err != nil {
return err
}
err = cli.client.SgUpdate(context.Background(), cmd.Arg(0), data)
if err != nil {
return err
}
return nil
}
func sgUsage() string {
sgCommands := [][]string{
{"create", "Create a new security group"},
{"ls", "List all security groups"},
{"rm", "Remove a security group"},
{"inspect", "Inspect the security group"},
{"update", "Update the security group"},
}
help := "Commands:\n"
for _, cmd := range sgCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
}
help += fmt.Sprintf("\nRun 'hyper sg COMMAND --help' for more information on a command.")
return help
}

View File

@@ -0,0 +1,160 @@
package client
import (
"fmt"
"text/tabwriter"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdSnapshot is the parent subcommand for all snapshot commands
//
// Usage: docker snapshot <COMMAND> <OPTS>
func (cli *DockerCli) CmdSnapshot(args ...string) error {
description := Cli.DockerCommands["snapshot"].Description + "\n\nSnapshots:\n"
commands := [][]string{
{"create", "Create a snapshot"},
{"inspect", "Return low-level information on a snapshot"},
{"ls", "List snapshots"},
{"rm", "Remove a snapshot"},
}
for _, cmd := range commands {
description += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
}
description += "\nRun 'hyper snapshot COMMAND --help' for more information on a command"
cmd := Cli.Subcmd("snapshot", []string{"[COMMAND]"}, description, false)
cmd.Require(flag.Exact, 0)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}
// CmdSnapshotLs outputs a list of Docker snapshots.
//
// Usage: hyper snapshot ls [OPTIONS]
func (cli *DockerCli) CmdSnapshotLs(args ...string) error {
cmd := Cli.Subcmd("snapshot ls", nil, "List snapshots", true)
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display snapshot names")
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
}
}
snapshots, err := cli.client.SnapshotList(context.Background(), volFilterArgs)
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet {
for _, warn := range snapshots.Warnings {
fmt.Fprintln(cli.err, warn)
}
fmt.Fprintf(w, "Snapshot Name \tVolume\tSize")
fmt.Fprintf(w, "\n")
}
for _, vol := range snapshots.Snapshots {
if *quiet {
fmt.Fprintln(w, vol.Name)
continue
}
fmt.Fprintf(w, "%s\t%s\t%d\n", vol.Name, vol.Volume, vol.Size)
}
w.Flush()
return nil
}
// CmdSnapshotInspect displays low-level information on one or more snapshots.
//
// Usage: docker snapshot inspect [OPTIONS] snapshot [snapshot...]
func (cli *DockerCli) CmdSnapshotInspect(args ...string) error {
cmd := Cli.Subcmd("snapshot inspect", []string{"snapshot [snapshot...]"}, "Return low-level information on a snapshot", 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
}
inspectSearcher := func(name string) (interface{}, []byte, error) {
i, err := cli.client.SnapshotInspect(context.Background(), name)
return i, nil, err
}
return cli.inspectElements(*tmplStr, cmd.Args(), inspectSearcher)
}
// CmdSnapshotCreate creates a new snapshot.
//
// Usage: docker snapshot create [OPTIONS]
func (cli *DockerCli) CmdSnapshotCreate(args ...string) error {
cmd := Cli.Subcmd("snapshot create", []string{"-v volume"}, "Create a snapshot", true)
flForce := cmd.Bool([]string{"f", "-force"}, false, "Force to create snapshot, needed if volume is in use")
flVolume := cmd.String([]string{"v", "-volume"}, "", "Specify volume to create snapshot")
flName := cmd.String([]string{"-name"}, "", "Specify snapshot name")
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
volReq := types.SnapshotCreateRequest{
Name: *flName,
Volume: *flVolume,
Force: *flForce,
}
vol, err := cli.client.SnapshotCreate(context.Background(), volReq)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", vol.Name)
return nil
}
// CmdSnapshotRm removes one or more snapshots.
//
// Usage: docker snapshot rm snapshot [snapshot...]
func (cli *DockerCli) CmdSnapshotRm(args ...string) error {
cmd := Cli.Subcmd("snapshot rm", []string{"snapshot [snapshot...]"}, "Remove a snapshot", true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var status = 0
for _, name := range cmd.Args() {
if err := cli.client.SnapshotRemove(context.Background(), 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
}

158
vendor/github.com/hyperhq/hypercli/api/client/start.go generated vendored Normal file
View File

@@ -0,0 +1,158 @@
package client
import (
"fmt"
"io"
"os"
"strings"
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
"github.com/docker/engine-api/types"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/pkg/promise"
"github.com/hyperhq/hypercli/pkg/signal"
)
func (cli *DockerCli) forwardAllSignals(ctx context.Context, cid string) chan os.Signal {
sigc := make(chan os.Signal, 128)
signal.CatchAll(sigc)
go func() {
for s := range sigc {
if s == signal.SIGCHLD {
continue
}
var sig string
for sigStr, sigN := range signal.SignalMap {
if sigN == s {
sig = sigStr
break
}
}
if sig == "" {
fmt.Fprintf(cli.err, "Unsupported signal: %v. Discarding.\n", s)
continue
}
if err := cli.client.ContainerKill(ctx, cid, sig); err != nil {
logrus.Debugf("Error sending signal: %s", err)
}
}
}()
return sigc
}
// CmdStart starts one or more containers.
//
// Usage: docker start [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdStart(args ...string) error {
cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["start"].Description, true)
attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
detachKeys := cmd.String([]string{}, "", "Override the key sequence for detaching a container")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
if *attach || *openStdin {
// We're going to attach to a container.
// 1. Ensure we only have one container.
if cmd.NArg() > 1 {
return fmt.Errorf("You cannot start and attach multiple containers at once.")
}
// 2. Attach to the container.
containerID := cmd.Arg(0)
c, err := cli.client.ContainerInspect(ctx, containerID)
if err != nil {
return err
}
if !c.Config.Tty {
sigc := cli.forwardAllSignals(ctx, containerID)
defer signal.StopCatch(sigc)
}
if *detachKeys != "" {
cli.configFile.DetachKeys = *detachKeys
}
options := types.ContainerAttachOptions{
Stream: true,
Stdin: *openStdin && c.Config.OpenStdin,
Stdout: true,
Stderr: true,
DetachKeys: cli.configFile.DetachKeys,
}
var in io.ReadCloser
if options.Stdin {
in = cli.in
}
resp, err := cli.client.ContainerAttach(ctx, containerID, options)
if err != nil {
return err
}
defer resp.Close()
if in != nil && c.Config.Tty {
if err := cli.setRawTerminal(); err != nil {
return err
}
defer cli.restoreTerminal(in)
}
cErr := promise.Go(func() error {
return cli.holdHijackedConnection(c.Config.Tty, in, cli.out, cli.err, resp)
})
// 3. Start the container.
if err := cli.client.ContainerStart(ctx, containerID, ""); err != nil {
return err
}
// 4. Wait for attachment to break.
if c.Config.Tty && cli.isTerminalOut {
if err := cli.monitorTtySize(ctx, containerID, false); err != nil {
fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
}
}
if attchErr := <-cErr; attchErr != nil {
return attchErr
}
_, status, err := getExitCode(ctx, cli, containerID)
if err != nil {
return err
}
if status != 0 {
return Cli.StatusError{StatusCode: status}
}
} else {
// We're not going to attach to anything.
// Start as many containers as we want.
return cli.startContainersWithoutAttachments(ctx, cmd.Args())
}
return nil
}
func (cli *DockerCli) startContainersWithoutAttachments(ctx context.Context, containerIDs []string) error {
var failedContainers []string
for _, containerID := range containerIDs {
if err := cli.client.ContainerStart(ctx, containerID, ""); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
failedContainers = append(failedContainers, containerID)
} else {
fmt.Fprintf(cli.out, "%s\n", containerID)
}
}
if len(failedContainers) > 0 {
return fmt.Errorf("Error: failed to start containers: %v", strings.Join(failedContainers, ", "))
}
return nil
}

190
vendor/github.com/hyperhq/hypercli/api/client/stats.go generated vendored Normal file
View File

@@ -0,0 +1,190 @@
package client
import (
"fmt"
"io"
"strings"
"sync"
"time"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/events"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/cli/command"
"github.com/hyperhq/hypercli/cli/command/formatter"
"golang.org/x/net/context"
)
// CmdStats displays a live stream of resource usage statistics for one or more containers.
//
// This shows real-time information on CPU usage, memory usage, and network I/O.
//
// Usage: hyper stats [OPTIONS] [CONTAINER...]
func (cli *DockerCli) CmdStats(args ...string) error {
cmd := Cli.Subcmd("stats", []string{"[CONTAINER...]"}, Cli.DockerCommands["stats"].Description, true)
all := cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
cmd.ParseFlags(args, true)
names := cmd.Args()
showAll := len(names) == 0
closeChan := make(chan error)
ctx := context.Background()
eventChan := make(chan events.Message)
// monitorContainerEvents watches for container creation and removal (only
// used when calling `docker stats` without arguments).
monitorContainerEvents := func(started chan<- struct{}, c chan events.Message) {
eventq, errq := cli.Events(ctx)
// Whether we successfully subscribed to eventq or not, we can now
// unblock the main goroutine.
close(started)
for {
select {
case event := <-eventq:
c <- event
case err := <-errq:
closeChan <- err
return
}
}
}
// waitFirst is a WaitGroup to wait first stat data's reach for each container
waitFirst := &sync.WaitGroup{}
cStats := stats{}
// getContainerList simulates creation event for all previously existing
// containers (only used when calling `docker stats` without arguments).
getContainerList := func() {
options := types.ContainerListOptions{
All: *all,
}
cs, err := cli.client.ContainerList(ctx, options)
if err != nil {
closeChan <- err
}
for _, container := range cs {
s := formatter.NewContainerStats(container.ID[:12])
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, cli, !*noStream, waitFirst, eventChan, false)
}
}
}
if showAll {
// If no names were specified, start a long running goroutine which
// monitors container events. We make sure we're subscribed before
// retrieving the list of running containers to avoid a race where we
// would "miss" a creation.
started := make(chan struct{})
eh := command.InitEventHandler()
eh.Handle("start", func(e events.Message) {
s := formatter.NewContainerStats(e.ID[:12])
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, cli, !*noStream, waitFirst, eventChan, false)
}
})
eh.Handle("stop", func(e events.Message) {
if !*all {
cStats.remove(e.ID[:12])
}
})
go eh.Watch(eventChan)
go monitorContainerEvents(started, eventChan)
defer close(eventChan)
<-started
// Start a short-lived goroutine to retrieve the initial list of
// containers.
getContainerList()
} else {
// Artificially send creation events for the containers we were asked to
// monitor (same code path than we use when monitoring all containers).
for _, name := range names {
s := formatter.NewContainerStats(name)
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, cli, !*noStream, waitFirst, nil, true)
}
}
// We don't expect any asynchronous errors: closeChan can be closed.
close(closeChan)
// Do a quick pause to detect any error with the provided list of
// container names.
time.Sleep(1500 * time.Millisecond)
var errs []string
cStats.mu.Lock()
for _, c := range cStats.cs {
cErr := c.GetError()
if cErr != nil {
errs = append(errs, fmt.Sprintf("%s: %v", c.Name, cErr))
}
}
cStats.mu.Unlock()
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, ", "))
}
}
// before print to screen, make sure each container get at least one valid stat data
waitFirst.Wait()
statsCtx := formatter.Context{
Output: cli.out,
Format: formatter.NewStatsFormat(formatter.TableFormatKey),
}
cleanScreen := func() {
if !*noStream {
fmt.Fprint(cli.out, "\033[2J")
fmt.Fprint(cli.out, "\033[H")
}
}
var err error
for range time.Tick(500 * time.Millisecond) {
cleanScreen()
ccstats := []formatter.StatsEntry{}
cStats.mu.Lock()
for _, c := range cStats.cs {
ccstats = append(ccstats, c.GetStatistics())
}
cStats.mu.Unlock()
if err = formatter.ContainerStatsWrite(statsCtx, ccstats); err != nil {
break
}
if len(cStats.cs) == 0 && !showAll {
break
}
if *noStream {
break
}
select {
case err, ok := <-closeChan:
if ok {
if err != nil {
// this is suppressing "unexpected EOF" in the cli when the
// daemon restarts so it shutdowns cleanly
if err == io.ErrUnexpectedEOF {
return nil
}
return err
}
}
default:
// just skip
}
}
return err
}

View File

@@ -0,0 +1,206 @@
package client
import (
"encoding/json"
"errors"
"io"
"strings"
"sync"
"time"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/events"
"github.com/hyperhq/hypercli/cli/command/formatter"
"golang.org/x/net/context"
)
type stats struct {
mu sync.Mutex
cs []*formatter.ContainerStats
}
func (s *stats) add(cs *formatter.ContainerStats) bool {
s.mu.Lock()
defer s.mu.Unlock()
if _, exists := s.isKnownContainer(cs.Container); !exists {
s.cs = append(s.cs, cs)
return true
}
return false
}
func (s *stats) remove(id string) {
s.mu.Lock()
if i, exists := s.isKnownContainer(id); exists {
s.cs = append(s.cs[:i], s.cs[i+1:]...)
}
s.mu.Unlock()
}
func (s *stats) isKnownContainer(cid string) (int, bool) {
for i, c := range s.cs {
if c.Container == cid {
return i, true
}
}
return -1, false
}
func collect(ctx context.Context, s *formatter.ContainerStats, cli *DockerCli, streamStats bool, waitFirst *sync.WaitGroup, c chan events.Message, specified bool) {
var (
getFirst bool
previousCPU uint64
previousSystem uint64
u = make(chan error, 1)
end = make(chan bool, 1)
)
defer func() {
// if error happens and we get nothing of stats, release wait group whatever
if !getFirst {
getFirst = true
waitFirst.Done()
}
}()
responseBody, err := cli.client.ContainerStats(ctx, s.Container, streamStats)
if err != nil {
s.SetError(err)
return
}
defer responseBody.Close()
dec := json.NewDecoder(responseBody)
go func() {
for {
var (
v *types.StatsJSON
memPercent = 0.0
cpuPercent = 0.0
blkRead, blkWrite uint64
mem = 0.0
memLimit = 0.0
memPerc = 0.0
)
if err := dec.Decode(&v); err != nil {
dec = json.NewDecoder(io.MultiReader(dec.Buffered(), responseBody))
u <- err
// TODO: add EOF in hyper.sh
if err == io.EOF {
end <- true
break
}
time.Sleep(100 * time.Millisecond)
continue
}
// if mem usage == 0, we think this container is stopped.
if v.MemoryStats.Usage == 0 {
if !specified {
stopEvent := events.Message{
ID: s.Container,
Action: "stop",
}
c <- stopEvent
end <- true
break
} else {
u <- errors.New("This container is stopped.")
continue
}
}
// MemoryStats.Limit will never be 0 unless the container is not running and we haven't
// got any data from cgroup
if v.MemoryStats.Limit != 0 {
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
}
previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
previousSystem = v.PreCPUStats.SystemUsage
cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
blkRead, blkWrite = calculateBlockIO(v.BlkioStats)
mem = float64(v.MemoryStats.Usage)
memLimit = float64(v.MemoryStats.Limit)
memPerc = memPercent
netRx, netTx := calculateNetwork(v.Networks)
s.SetStatistics(formatter.StatsEntry{
CPUPercentage: cpuPercent,
Memory: mem,
MemoryPercentage: memPerc,
MemoryLimit: memLimit,
NetworkRx: netRx,
NetworkTx: netTx,
BlockRead: float64(blkRead),
BlockWrite: float64(blkWrite),
})
u <- nil
if !streamStats {
return
}
}
}()
for {
select {
case <-time.After(2 * time.Second):
// zero out the values if we have not received an update within
// the specified duration.
s.SetErrorAndReset(errors.New("timeout waiting for stats"))
// if this is the first stat you get, release WaitGroup
if !getFirst {
getFirst = true
waitFirst.Done()
}
case err := <-u:
if err != nil {
s.SetError(err)
continue
}
s.SetError(nil)
// if this is the first stat you get, release WaitGroup
if !getFirst {
getFirst = true
waitFirst.Done()
}
case <-end:
s.SetError(errors.New("This container is stopped."))
if !getFirst {
getFirst = true
waitFirst.Done()
}
break
}
if !streamStats {
return
}
}
}
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
return float64(v.CPUStats.CPUUsage.TotalUsage) / 100.0
}
func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
for _, bioEntry := range blkio.IoServiceBytesRecursive {
switch strings.ToLower(bioEntry.Op) {
case "read":
blkRead = blkRead + bioEntry.Value
case "write":
blkWrite = blkWrite + bioEntry.Value
}
}
return
}
func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
var rx, tx float64
for _, v := range network {
rx += float64(v.RxBytes)
tx += float64(v.TxBytes)
}
return rx, tx
}

39
vendor/github.com/hyperhq/hypercli/api/client/stop.go generated vendored Normal file
View File

@@ -0,0 +1,39 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdStop stops one or more containers.
//
// A running container is stopped by first sending SIGTERM and then SIGKILL if the container fails to stop within a grace period (the default is 10 seconds).
//
// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdStop(args ...string) error {
cmd := Cli.Subcmd("stop", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["stop"].Description+".\nSending SIGTERM and then SIGKILL after a grace period", true)
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
var errs []string
for _, name := range cmd.Args() {
if err := cli.client.ContainerStop(ctx, name, *nSeconds); err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

26
vendor/github.com/hyperhq/hypercli/api/client/tag.go generated vendored Normal file
View File

@@ -0,0 +1,26 @@
package client
import (
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdTag tags an image into a repository.
//
// Usage: docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
func (cli *DockerCli) Tag(args ...string) error {
cmd := Cli.Subcmd("tag", []string{"IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]"}, Cli.DockerCommands["tag"].Description, true)
force := cmd.Bool([]string{"#f", "#-force"}, false, "Force the tagging even if there's a conflict")
cmd.Require(flag.Exact, 2)
cmd.ParseFlags(args, true)
options := types.ImageTagOptions{
Force: *force,
}
return cli.client.ImageTag(context.Background(), cmd.Arg(0), cmd.Arg(1), options)
}

View File

@@ -0,0 +1,246 @@
package client
import (
"archive/tar"
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"github.com/cheggaaa/pb"
)
const (
TARINFO_HEADER = iota
TARINFO_FILE
TARINFO_PAD
TARINFO_UPLOADED
TARINFO_FINISHED
)
type tarInfo struct {
info os.FileInfo
relPath string
linkName string
path string
pad int // number of zeros to pad at the end of the file entry
headerBuf *bytes.Buffer
pos int64
state int
}
type TarFile struct {
fileList []*tarInfo
blockSize int
endPad int
padding []byte
closed bool
source string
progress *pb.ProgressBar
}
func (t *TarFile) writeHeader(p []byte, info *tarInfo) (int, error) {
if info.headerBuf == nil {
header, err := tar.FileInfoHeader(info.info, info.linkName)
if err != nil {
return 0, err
}
header.Name = info.relPath
buf := &bytes.Buffer{}
tarWriter := tar.NewWriter(buf)
defer tarWriter.Close()
err = tarWriter.WriteHeader(header)
if err != nil {
return 0, err
}
info.headerBuf = &bytes.Buffer{}
io.Copy(info.headerBuf, buf)
}
size, err := info.headerBuf.Read(p)
if err != nil && err != io.EOF {
return 0, err
}
if info.headerBuf.Len() == 0 {
info.headerBuf.Truncate(0)
}
return size, nil
}
func (t *TarFile) writeFile(p []byte, info *tarInfo) (int, error) {
var f *os.File
var err error
if f, err = os.Open(info.path); err != nil {
return 0, err
}
defer f.Close()
// resuming
if info.pos != 0 {
if _, err = f.Seek(info.pos, os.SEEK_SET); err != nil {
return 0, err
}
}
if ret, err := f.Read(p); err != nil && err != io.EOF {
return 0, err
} else {
return ret, nil
}
}
func (t *TarFile) writePad(p []byte, info *tarInfo) (int, error) {
buf := bytes.NewBuffer(t.padding[0:info.pad])
if ret, err := buf.Read(p); err != nil && err != io.EOF {
return 0, err
} else {
return ret, nil
}
}
// write the trailing zeros of a tar file
func (t *TarFile) writeClose(p []byte) (n int, err error) {
size := t.endPad
buf := bytes.NewBuffer(make([]byte, size))
if ret, err := buf.Read(p); err != nil && err != io.EOF {
return 0, err
} else {
t.endPad -= ret
}
if t.endPad <= 0 {
if t.progress != nil {
t.progress.Finish()
}
t.closed = true
return size, io.EOF
}
return size - t.endPad, nil
}
func (t *TarFile) AllocBar(pool *pb.Pool) *pb.ProgressBar {
if t.progress == nil {
t.progress = pb.New(len(t.fileList)).Prefix(fmt.Sprintf("Sending %s", t.source))
pool.Add(t.progress)
}
return t.progress
}
func (t *TarFile) AddFile(info os.FileInfo, relPath, linkName, path string) {
t.fileList = append(t.fileList, &tarInfo{
info: info,
relPath: relPath,
linkName: linkName,
path: path,
pad: (t.blockSize - (int(info.Size()) % t.blockSize)) % t.blockSize,
})
}
func (t *TarFile) Close() error {
if !t.closed {
if t.progress != nil {
t.progress.Finish()
}
}
t.closed = true
return nil
}
func (t *TarFile) Read(p []byte) (n int, err error) {
var (
file *tarInfo
idx int
length = len(p)
)
if length == 0 {
return
}
if t.closed {
return 0, io.EOF
}
for idx, file = range t.fileList {
if file.state != TARINFO_FINISHED {
break
}
if idx == len(t.fileList)-1 {
file = nil
break
}
}
defer func() {
if err != nil && err != io.EOF {
t.Close()
}
}()
if file == nil {
return t.writeClose(p)
}
for n < length && file.state != TARINFO_FINISHED {
switch file.state {
case TARINFO_HEADER:
if ret, err := t.writeHeader(p, file); err != nil {
return 0, err
} else {
n += ret
p = p[n:]
if file.headerBuf.Len() == 0 {
if file.info.Mode().IsRegular() {
file.state = TARINFO_FILE
} else {
file.state = TARINFO_UPLOADED
}
}
}
case TARINFO_FILE:
if ret, err := t.writeFile(p, file); err != nil {
return 0, err
} else {
file.pos += int64(ret)
n += ret
p = p[ret:]
if file.pos >= file.info.Size() {
file.pos = 0
file.state = TARINFO_PAD
}
}
case TARINFO_PAD:
if file.pad == 0 {
file.state = TARINFO_UPLOADED
} else if ret, err := t.writePad(p, file); err != nil {
return 0, err
} else {
file.pos += int64(ret)
n += ret
p = p[ret:]
if file.pos >= int64(file.pad) {
file.state = TARINFO_UPLOADED
}
}
case TARINFO_UPLOADED:
file.state = TARINFO_FINISHED
if t.progress != nil {
t.progress.Increment()
}
}
}
return n, nil
}
func NewTarFile(path string, blockSize int) *TarFile {
return &TarFile{
blockSize: blockSize,
endPad: blockSize * 2,
source: filepath.Base(path),
padding: make([]byte, blockSize),
}
}

41
vendor/github.com/hyperhq/hypercli/api/client/top.go generated vendored Normal file
View File

@@ -0,0 +1,41 @@
package client
import (
"fmt"
"strings"
"text/tabwriter"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdTop displays the running processes of a container.
//
// Usage: docker top CONTAINER
func (cli *DockerCli) Top(args ...string) error {
cmd := Cli.Subcmd("top", []string{"CONTAINER [ps OPTIONS]"}, Cli.DockerCommands["top"].Description, true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
var arguments []string
if cmd.NArg() > 1 {
arguments = cmd.Args()[1:]
}
procList, err := cli.client.ContainerTop(context.Background(), cmd.Arg(0), arguments)
if err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintln(w, strings.Join(procList.Titles, "\t"))
for _, proc := range procList.Processes {
fmt.Fprintln(w, strings.Join(proc, "\t"))
}
w.Flush()
return nil
}

467
vendor/github.com/hyperhq/hypercli/api/client/trust.go generated vendored Normal file
View File

@@ -0,0 +1,467 @@
package client
import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"sort"
"strconv"
"time"
"golang.org/x/net/context"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
"github.com/docker/distribution/registry/client/auth"
"github.com/docker/distribution/registry/client/transport"
"github.com/docker/engine-api/types"
registrytypes "github.com/docker/engine-api/types/registry"
"github.com/docker/go-connections/tlsconfig"
"github.com/docker/notary/client"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/store"
"github.com/hyperhq/hypercli/cliconfig"
"github.com/hyperhq/hypercli/distribution"
"github.com/hyperhq/hypercli/dockerversion"
"github.com/hyperhq/hypercli/pkg/jsonmessage"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/reference"
"github.com/hyperhq/hypercli/registry"
)
var (
releasesRole = path.Join(data.CanonicalTargetsRole, "releases")
untrusted bool
)
func addTrustedFlags(fs *flag.FlagSet, verify bool) {
var trusted bool
if e := os.Getenv("HYPER_CONTENT_TRUST"); e != "" {
if t, err := strconv.ParseBool(e); t || err != nil {
// treat any other value as true
trusted = true
}
}
message := "Skip image signing"
if verify {
message = "Skip image verification"
}
fs.BoolVar(&untrusted, []string{"-disable-content-trust"}, !trusted, message)
}
func isTrusted() bool {
return !untrusted
}
type target struct {
reference registry.Reference
digest digest.Digest
size int64
}
func (cli *DockerCli) trustDirectory() string {
return filepath.Join(cliconfig.ConfigDir(), "trust")
}
// certificateDirectory returns the directory containing
// TLS certificates for the given server. An error is
// returned if there was an error parsing the server string.
func (cli *DockerCli) certificateDirectory(server string) (string, error) {
u, err := url.Parse(server)
if err != nil {
return "", err
}
return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
}
func trustServer(index *registrytypes.IndexInfo) (string, error) {
if s := os.Getenv("HYPER_CONTENT_TRUST_SERVER"); s != "" {
urlObj, err := url.Parse(s)
if err != nil || urlObj.Scheme != "https" {
return "", fmt.Errorf("valid https URL required for trust server, got %s", s)
}
return s, nil
}
if index.Official {
return registry.NotaryServer, nil
}
return "https://" + index.Name, nil
}
type simpleCredentialStore struct {
auth types.AuthConfig
}
func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
return scs.auth.Username, scs.auth.Password
}
func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig) (*client.NotaryRepository, error) {
server, err := trustServer(repoInfo.Index)
if err != nil {
return nil, err
}
var cfg = tlsconfig.ClientDefault
cfg.InsecureSkipVerify = !repoInfo.Index.Secure
// Get certificate base directory
certDir, err := cli.certificateDirectory(server)
if err != nil {
return nil, err
}
logrus.Debugf("reading certificate directory: %s", certDir)
if err := registry.ReadCertsDirectory(&cfg, certDir); err != nil {
return nil, err
}
base := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: &cfg,
DisableKeepAlives: true,
}
// Skip configuration headers since request is not going to Docker daemon
modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(), http.Header{})
authTransport := transport.NewTransport(base, modifiers...)
pingClient := &http.Client{
Transport: authTransport,
Timeout: 5 * time.Second,
}
endpointStr := server + "/v2/"
req, err := http.NewRequest("GET", endpointStr, nil)
if err != nil {
return nil, err
}
challengeManager := auth.NewSimpleChallengeManager()
resp, err := pingClient.Do(req)
if err != nil {
// Ignore error on ping to operate in offline mode
logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err)
} else {
defer resp.Body.Close()
// Add response to the challenge manager to parse out
// authentication header and register authentication method
if err := challengeManager.AddResponse(resp); err != nil {
return nil, err
}
}
creds := simpleCredentialStore{auth: authConfig}
tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), "push", "pull")
basicHandler := auth.NewBasicHandler(creds)
modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
tr := transport.NewTransport(base, modifiers...)
return client.NewNotaryRepository(cli.trustDirectory(), repoInfo.FullName(), server, tr, cli.getPassphraseRetriever())
}
func convertTarget(t client.Target) (target, error) {
h, ok := t.Hashes["sha256"]
if !ok {
return target{}, errors.New("no valid hash, expecting sha256")
}
return target{
reference: registry.ParseReference(t.Name),
digest: digest.NewDigestFromHex("sha256", hex.EncodeToString(h)),
size: t.Length,
}, nil
}
func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
aliasMap := map[string]string{
"root": "root",
"snapshot": "repository",
"targets": "repository",
"targets/releases": "repository",
}
baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap)
env := map[string]string{
"root": os.Getenv("HYPER_CONTENT_TRUST_ROOT_PASSPHRASE"),
"snapshot": os.Getenv("HYPER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
"targets": os.Getenv("HYPER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
"targets/releases": os.Getenv("HYPER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
}
// Backwards compatibility with old env names. We should remove this in 1.10
if env["root"] == "" {
if passphrase := os.Getenv("HYPER_CONTENT_TRUST_OFFLINE_PASSPHRASE"); passphrase != "" {
env["root"] = passphrase
fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable HYPER_CONTENT_TRUST_OFFLINE_PASSPHRASE has been deprecated and will be removed in v1.10. Please use HYPER_CONTENT_TRUST_ROOT_PASSPHRASE\n")
}
}
if env["snapshot"] == "" || env["targets"] == "" || env["targets/releases"] == "" {
if passphrase := os.Getenv("HYPER_CONTENT_TRUST_TAGGING_PASSPHRASE"); passphrase != "" {
env["snapshot"] = passphrase
env["targets"] = passphrase
env["targets/releases"] = passphrase
fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable HYPER_CONTENT_TRUST_TAGGING_PASSPHRASE has been deprecated and will be removed in v1.10. Please use HYPER_CONTENT_TRUST_REPOSITORY_PASSPHRASE\n")
}
}
return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
if v := env[alias]; v != "" {
return v, numAttempts > 1, nil
}
return baseRetriever(keyName, alias, createNew, numAttempts)
}
}
func (cli *DockerCli) trustedReference(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) {
repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
return nil, err
}
// Resolve the Auth config relevant for this server
authConfig := cli.resolveAuthConfig(ctx, cli.configFile.AuthConfigs, repoInfo.Index)
notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
if err != nil {
fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err)
return nil, err
}
t, err := notaryRepo.GetTargetByName(ref.Tag(), releasesRole, data.CanonicalTargetsRole)
if err != nil {
return nil, err
}
r, err := convertTarget(t.Target)
if err != nil {
return nil, err
}
return reference.WithDigest(ref, r.digest)
}
func (cli *DockerCli) tagTrusted(ctx context.Context, trustedRef reference.Canonical, ref reference.NamedTagged) error {
fmt.Fprintf(cli.out, "Tagging %s as %s\n", trustedRef.String(), ref.String())
options := types.ImageTagOptions{
Force: true,
}
return cli.client.ImageTag(ctx, trustedRef.String(), ref.String(), options)
}
func notaryError(repoName string, err error) error {
switch err.(type) {
case *json.SyntaxError:
logrus.Debugf("Notary syntax error: %s", err)
return fmt.Errorf("Error: no trust data available for remote repository %s. Try running notary server and setting HYPER_CONTENT_TRUST_SERVER to its HTTPS address?", repoName)
case signed.ErrExpired:
return fmt.Errorf("Error: remote repository %s out-of-date: %v", repoName, err)
case trustmanager.ErrKeyNotFound:
return fmt.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err)
case *net.OpError:
return fmt.Errorf("Error: error contacting notary server: %v", err)
case store.ErrMetaNotFound:
return fmt.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err)
case signed.ErrInvalidKeyType:
return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err)
case signed.ErrNoKeys:
return fmt.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err)
case signed.ErrLowVersion:
return fmt.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err)
case signed.ErrRoleThreshold:
return fmt.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err)
case client.ErrRepositoryNotExist:
return fmt.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err)
case signed.ErrInsufficientSignatures:
return fmt.Errorf("Error: could not produce valid signature for %s. If Yubikey was used, was touch input provided?: %v", repoName, err)
}
return err
}
func (cli *DockerCli) trustedPull(ctx context.Context, repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
var refs []target
notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
if err != nil {
fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err)
return err
}
if ref.String() == "" {
// List all targets
targets, err := notaryRepo.ListTargets(releasesRole, data.CanonicalTargetsRole)
if err != nil {
return notaryError(repoInfo.FullName(), err)
}
for _, tgt := range targets {
t, err := convertTarget(tgt.Target)
if err != nil {
fmt.Fprintf(cli.out, "Skipping target for %q\n", repoInfo.Name())
continue
}
refs = append(refs, t)
}
} else {
t, err := notaryRepo.GetTargetByName(ref.String(), releasesRole, data.CanonicalTargetsRole)
if err != nil {
return notaryError(repoInfo.FullName(), err)
}
r, err := convertTarget(t.Target)
if err != nil {
return err
}
refs = append(refs, r)
}
for i, r := range refs {
displayTag := r.reference.String()
if displayTag != "" {
displayTag = ":" + displayTag
}
fmt.Fprintf(cli.out, "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.Name(), displayTag, r.digest)
ref, err := reference.WithDigest(repoInfo, r.digest)
if err != nil {
return err
}
if err := cli.imagePullPrivileged(ctx, authConfig, ref.String(), requestPrivilege, false); err != nil {
return err
}
// If reference is not trusted, tag by trusted reference
if !r.reference.HasDigest() {
tagged, err := reference.WithTag(repoInfo, r.reference.String())
if err != nil {
return err
}
trustedRef, err := reference.WithDigest(repoInfo, r.digest)
if err != nil {
return err
}
if err := cli.tagTrusted(ctx, trustedRef, tagged); err != nil {
return err
}
}
}
return nil
}
func (cli *DockerCli) trustedPush(ctx context.Context, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
responseBody, err := cli.imagePushPrivileged(ctx, authConfig, ref.String(), requestPrivilege)
if err != nil {
return err
}
defer responseBody.Close()
targets := []target{}
handleTarget := func(aux *json.RawMessage) {
var pushResult distribution.PushResult
err := json.Unmarshal(*aux, &pushResult)
if err == nil && pushResult.Tag != "" && pushResult.Digest.Validate() == nil {
targets = append(targets, target{
reference: registry.ParseReference(pushResult.Tag),
digest: pushResult.Digest,
size: int64(pushResult.Size),
})
}
}
var tag string
switch x := ref.(type) {
case reference.Canonical:
return errors.New("cannot push a digest reference")
case reference.NamedTagged:
tag = x.Tag()
}
if tag == "" {
err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, handleTarget)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "No tag specified, skipping trust metadata push\n")
return nil
}
err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, handleTarget)
if err != nil {
return err
}
if len(targets) == 0 {
fmt.Fprintf(cli.out, "No targets found, skipping trust metadata push\n")
return nil
}
fmt.Fprintf(cli.out, "Signing and pushing trust metadata\n")
repo, err := cli.getNotaryRepository(repoInfo, authConfig)
if err != nil {
fmt.Fprintf(cli.out, "Error establishing connection to notary repository: %s\n", err)
return err
}
for _, target := range targets {
h, err := hex.DecodeString(target.digest.Hex())
if err != nil {
return err
}
t := &client.Target{
Name: target.reference.String(),
Hashes: data.Hashes{
string(target.digest.Algorithm()): h,
},
Length: int64(target.size),
}
if err := repo.AddTarget(t, releasesRole); err != nil {
return err
}
}
err = repo.Publish()
if _, ok := err.(client.ErrRepoNotInitialized); !ok {
return notaryError(repoInfo.FullName(), err)
}
keys := repo.CryptoService.ListKeys(data.CanonicalRootRole)
var rootKeyID string
// always select the first root key
if len(keys) > 0 {
sort.Strings(keys)
rootKeyID = keys[0]
} else {
rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, data.ECDSAKey)
if err != nil {
return err
}
rootKeyID = rootPublicKey.ID()
}
if err := repo.Initialize(rootKeyID); err != nil {
return notaryError(repoInfo.FullName(), err)
}
fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.FullName())
return notaryError(repoInfo.FullName(), repo.Publish())
}

View File

@@ -0,0 +1,56 @@
package client
import (
"os"
"testing"
"github.com/hyperhq/hypercli/registry"
registrytypes "github.com/docker/engine-api/types/registry"
)
func unsetENV() {
os.Unsetenv("DOCKER_CONTENT_TRUST")
os.Unsetenv("DOCKER_CONTENT_TRUST_SERVER")
}
func TestENVTrustServer(t *testing.T) {
defer unsetENV()
indexInfo := &registrytypes.IndexInfo{Name: "testserver"}
if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "https://notary-test.com:5000"); err != nil {
t.Fatal("Failed to set ENV variable")
}
output, err := trustServer(indexInfo)
expectedStr := "https://notary-test.com:5000"
if err != nil || output != expectedStr {
t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
}
}
func TestHTTPENVTrustServer(t *testing.T) {
defer unsetENV()
indexInfo := &registrytypes.IndexInfo{Name: "testserver"}
if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "http://notary-test.com:5000"); err != nil {
t.Fatal("Failed to set ENV variable")
}
_, err := trustServer(indexInfo)
if err == nil {
t.Fatal("Expected error with invalid scheme")
}
}
func TestOfficialTrustServer(t *testing.T) {
indexInfo := &registrytypes.IndexInfo{Name: "testserver", Official: true}
output, err := trustServer(indexInfo)
if err != nil || output != registry.NotaryServer {
t.Fatalf("Expected server to be %s, got %s", registry.NotaryServer, output)
}
}
func TestNonOfficialTrustServer(t *testing.T) {
indexInfo := &registrytypes.IndexInfo{Name: "testserver", Official: false}
output, err := trustServer(indexInfo)
expectedStr := "https://" + indexInfo.Name
if err != nil || output != expectedStr {
t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
}
}

View File

@@ -0,0 +1,36 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdUnpause unpauses all processes within a container, for one or more containers.
//
// Usage: docker unpause CONTAINER [CONTAINER...]
func (cli *DockerCli) Unpause(args ...string) error {
cmd := Cli.Subcmd("unpause", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["unpause"].Description, true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
var errs []string
for _, name := range cmd.Args() {
if err := cli.client.ContainerUnpause(ctx, name); err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

View File

@@ -0,0 +1,78 @@
package client
import (
"fmt"
"strconv"
"strings"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/opts"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdUpdate updates resources of one or more containers.
//
// Usage: hyper update [OPTIONS] CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdUpdate(args ...string) error {
cmd := Cli.Subcmd("update", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["update"].Description, true)
flAddSecurityGroups := opts.NewListOpts(nil)
flRmSecurityGroups := opts.NewListOpts(nil)
cmd.Var(&flAddSecurityGroups, []string{"-sg-add"}, "Add a new security group for each container")
cmd.Var(&flRmSecurityGroups, []string{"-sg-rm"}, "Remove a new security group for each container")
// make this flag string type to distinguish between 'not set', 'set to false' and 'set to true'
flContainerProtection := cmd.String([]string{"-protection"}, "", "Termination protection for container (true|false)")
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
if cmd.NFlag() == 0 {
return fmt.Errorf("You must provide one or more flags when using this command.")
}
if *flContainerProtection != "" {
value, err := strconv.ParseBool(*flContainerProtection)
if err != nil {
return fmt.Errorf("Parse protection false failed: %v", err)
}
*flContainerProtection = strconv.FormatBool(value)
}
ctx := context.Background()
names := cmd.Args()
var errs []string
for _, name := range names {
var updateConfig struct {
AddSecurityGroups map[string]string
RemoveSecurityGroups map[string]string
SetContainerProtection string
}
sgs := map[string]string{}
for _, label := range flAddSecurityGroups.GetAll() {
if label == "" {
continue
}
sgs[label] = "yes"
}
updateConfig.AddSecurityGroups = sgs
sgs = map[string]string{}
for _, label := range flRmSecurityGroups.GetAll() {
if label == "" {
continue
}
sgs[label] = "yes"
}
updateConfig.RemoveSecurityGroups = sgs
updateConfig.SetContainerProtection = *flContainerProtection
if err := cli.client.ContainerUpdate(ctx, name, updateConfig); err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

261
vendor/github.com/hyperhq/hypercli/api/client/utils.go generated vendored Normal file
View File

@@ -0,0 +1,261 @@
package client
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
gosignal "os/signal"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/engine-api/client"
"github.com/docker/engine-api/types"
registrytypes "github.com/docker/engine-api/types/registry"
"github.com/dutchcoders/goftp"
"github.com/hyperhq/hypercli/pkg/signal"
"github.com/hyperhq/hypercli/pkg/term"
"github.com/hyperhq/hypercli/registry"
"golang.org/x/net/context"
)
func (cli *DockerCli) electAuthServer(ctx context.Context) string {
// The daemon `/info` endpoint informs us of the default registry being
// used. This is essential in cross-platforms environment, where for
// example a Linux client might be interacting with a Windows daemon, hence
// the default registry URL might be Windows specific.
serverAddress := registry.IndexServer
if info, err := cli.client.Info(ctx); err != nil {
fmt.Fprintf(cli.out, "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n", err, serverAddress)
} else {
serverAddress = info.IndexServerAddress
}
return serverAddress
}
// encodeAuthToBase64 serializes the auth configuration as JSON base64 payload
func encodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
buf, err := json.Marshal(authConfig)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(buf), nil
}
func (cli *DockerCli) registryAuthenticationPrivilegedFunc(index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
return func() (string, error) {
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
indexServer := registry.GetAuthConfigKey(index)
authConfig, err := cli.configureAuth("", "", "", indexServer)
if err != nil {
return "", err
}
return encodeAuthToBase64(authConfig)
}
}
func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) {
height, width := cli.getTtySize()
if height == 0 && width == 0 {
return
}
options := types.ResizeOptions{
Height: height,
Width: width,
}
var err error
if isExec {
err = cli.client.ContainerExecResize(ctx, id, options)
} else {
err = cli.client.ContainerResize(ctx, id, options)
}
if err != nil {
logrus.Debugf("Error resize: %s", err)
}
}
// getExitCode perform an inspect on the container. It returns
// the running state and the exit code.
func getExitCode(ctx context.Context, cli *DockerCli, containerID string) (bool, int, error) {
c, err := cli.client.ContainerInspect(ctx, containerID)
if err != nil {
// If we can't connect, then the daemon probably died.
if err != client.ErrConnectionFailed {
return false, -1, err
}
return false, -1, nil
}
return c.State.Running, c.State.ExitCode, nil
}
// getExecExitCode perform an inspect on the exec command. It returns
// the running state and the exit code.
func getExecExitCode(ctx context.Context, cli *DockerCli, execID string) (bool, int, error) {
resp, err := cli.client.ContainerExecInspect(ctx, execID)
if err != nil {
// If we can't connect, then the daemon probably died.
if err != client.ErrConnectionFailed {
return false, -1, err
}
return false, -1, nil
}
return resp.Running, resp.ExitCode, nil
}
func (cli *DockerCli) monitorTtySize(ctx context.Context, id string, isExec bool) error {
cli.resizeTty(ctx, id, isExec)
if runtime.GOOS == "windows" {
go func() {
prevH, prevW := cli.getTtySize()
for {
time.Sleep(time.Millisecond * 250)
h, w := cli.getTtySize()
if prevW != w || prevH != h {
cli.resizeTty(ctx, id, isExec)
}
prevH = h
prevW = w
}
}()
} else {
sigchan := make(chan os.Signal, 1)
gosignal.Notify(sigchan, signal.SIGWINCH)
go func() {
for range sigchan {
cli.resizeTty(ctx, id, isExec)
}
}()
}
return nil
}
func (cli *DockerCli) getTtySize() (int, int) {
if !cli.isTerminalOut {
return 0, 0
}
ws, err := term.GetWinsize(cli.outFd)
if err != nil {
logrus.Debugf("Error getting size: %s", err)
if ws == nil {
return 0, 0
}
}
return int(ws.Height), int(ws.Width)
}
func copyToFile(outfile string, r io.Reader) error {
tmpFile, err := ioutil.TempFile(filepath.Dir(outfile), ".docker_temp_")
if err != nil {
return err
}
tmpPath := tmpFile.Name()
_, err = io.Copy(tmpFile, r)
tmpFile.Close()
if err != nil {
os.Remove(tmpPath)
return err
}
if err = os.Rename(tmpPath, outfile); err != nil {
os.Remove(tmpPath)
return err
}
return nil
}
// resolveAuthConfig is like registry.ResolveAuthConfig, but if using the
// default index, it uses the default index name for the daemon's platform,
// not the client's platform.
func (cli *DockerCli) resolveAuthConfig(ctx context.Context, authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
configKey := index.Name
if index.Official {
configKey = cli.electAuthServer(ctx)
}
// First try the happy case
if c, found := authConfigs[configKey]; found || index.Official {
return c
}
convertToHostname := func(url string) string {
stripped := url
if strings.HasPrefix(url, "http://") {
stripped = strings.Replace(url, "http://", "", 1)
} else if strings.HasPrefix(url, "https://") {
stripped = strings.Replace(url, "https://", "", 1)
}
nameParts := strings.SplitN(stripped, "/", 2)
return nameParts[0]
}
// Maybe they have a legacy config file, we will iterate the keys converting
// them to the new format and testing
for registry, ac := range authConfigs {
if configKey == convertToHostname(registry) {
return ac
}
}
// When all else fails, return an empty auth config
return types.AuthConfig{}
}
// uploadLocalResource upload a local file/directory to a container
func (cli *DockerCli) uploadLocalResource(source, dest, serverIP, user, passwd string, isFile bool) error {
if !strings.Contains(serverIP, ":") {
serverIP += ":21"
}
ftp, err := goftp.Connect(serverIP)
if err != nil {
return err
}
defer func() {
ftp.Close()
}()
if err = ftp.Login(user, passwd); err != nil {
return err
}
if isFile {
file, err := os.Open(source)
if err != nil {
return err
}
defer func() {
file.Close()
}()
if err = ftp.Stor(dest, file); err != nil {
return err
}
} else {
if err = ftp.Cwd(dest); err != nil {
return err
}
if err = ftp.Upload(source); err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,19 @@
// +build !windows
package client
import (
"path/filepath"
)
func getContextRoot(srcPath string) (string, error) {
return filepath.Join(srcPath, "."), nil
}
func convertToUnixPath(path string) (int, string) {
return 0, path
}
func recoverPath(pathType int, path string) string {
return path
}

View File

@@ -0,0 +1,60 @@
// +build windows
package client
import (
"path/filepath"
"strings"
"github.com/hyperhq/hypercli/pkg/longpath"
)
const (
LOCAL_PATH_UNIX = iota
LOCAL_PATH_WIN_VOLUME
LOCAL_PATH_WIN_SHARE
)
func getContextRoot(srcPath string) (string, error) {
cr, err := filepath.Abs(srcPath)
if err != nil {
return "", err
}
return longpath.AddPrefix(cr), nil
}
// convertToUnixPath converts whatever valid path to unix format
// network path is treated as unix path unchanged
// /foo/bar -> /foo/bar
// C:\foo\bar -> /C/foo/bar
// \\host\share\foo\bar -> //host/share/foo/bar
func convertToUnixPath(path string) (int, string) {
if strings.HasPrefix(path, "git://") || strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
return LOCAL_PATH_UNIX, path
}
switch len(filepath.VolumeName(path)) {
case 0:
return LOCAL_PATH_UNIX, path
case 2:
// C:
return LOCAL_PATH_WIN_VOLUME, "/" + strings.Replace(filepath.ToSlash(path), ":", "", 1)
default:
// \\host\share
return LOCAL_PATH_WIN_SHARE, filepath.ToSlash(path)
}
}
// recoverPath recovers a file path according to path type
func recoverPath(pathType int, path string) string {
switch pathType {
case LOCAL_PATH_WIN_VOLUME:
path = filepath.FromSlash(path)
return strings.Replace(path[1:], "\\", ":\\", 1)
case LOCAL_PATH_WIN_SHARE:
return filepath.FromSlash(path)
default:
// LOCAL_PATH_UNIX
return path
}
}

View File

@@ -0,0 +1,96 @@
package client
import (
"runtime"
"text/template"
"time"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
Cli "github.com/hyperhq/hypercli/cli"
"github.com/hyperhq/hypercli/dockerversion"
flag "github.com/hyperhq/hypercli/pkg/mflag"
"github.com/hyperhq/hypercli/utils"
)
var versionTemplate = `Client:
Version: {{.Client.Version}}
API version: {{.Client.APIVersion}}
Go version: {{.Client.GoVersion}}
Git commit: {{.Client.GitCommit}}
Built: {{.Client.BuildTime}}
OS/Arch: {{.Client.Os}}/{{.Client.Arch}}{{if .Client.Experimental}}
Experimental: {{.Client.Experimental}}{{end}}{{if .ServerOK}}
Server:
Version: {{.Server.Version}}
API version: {{.Server.APIVersion}}
Go version: {{.Server.GoVersion}}
Git commit: {{.Server.GitCommit}}
Built: {{.Server.BuildTime}}
OS/Arch: {{.Server.Os}}/{{.Server.Arch}}{{if .Server.Experimental}}
Experimental: {{.Server.Experimental}}{{end}}{{end}}`
// CmdVersion shows Docker version information.
//
// Available version information is shown for: client Docker version, client API version, client Go version, client Git commit, client OS/Arch, server Docker version, server API version, server Go version, server Git commit, and server OS/Arch.
//
// Usage: docker version
func (cli *DockerCli) CmdVersion(args ...string) (err error) {
cmd := Cli.Subcmd("version", nil, Cli.DockerCommands["version"].Description, true)
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
cmd.Require(flag.Exact, 0)
cmd.ParseFlags(args, true)
ctx := context.Background()
templateFormat := versionTemplate
if *tmplStr != "" {
templateFormat = *tmplStr
}
var tmpl *template.Template
if tmpl, err = template.New("").Funcs(funcMap).Parse(templateFormat); err != nil {
return Cli.StatusError{StatusCode: 64,
Status: "Template parsing error: " + err.Error()}
}
vd := types.VersionResponse{
Client: &types.Version{
Version: dockerversion.Version,
APIVersion: cli.client.ClientVersion(),
GoVersion: runtime.Version(),
GitCommit: dockerversion.GitCommit,
BuildTime: dockerversion.BuildTime,
Os: runtime.GOOS,
Arch: runtime.GOARCH,
Experimental: utils.ExperimentalBuild(),
},
}
serverVersion, err := cli.client.ServerVersion(ctx)
if err == nil {
vd.Server = &serverVersion
}
// first we need to make BuildTime more human friendly
t, errTime := time.Parse(time.RFC3339Nano, vd.Client.BuildTime)
if errTime == nil {
vd.Client.BuildTime = t.Format(time.ANSIC)
}
if vd.ServerOK() {
t, errTime = time.Parse(time.RFC3339Nano, vd.Server.BuildTime)
if errTime == nil {
vd.Server.BuildTime = t.Format(time.ANSIC)
}
}
if err2 := tmpl.Execute(cli.out, vd); err2 != nil && err == nil {
err = err2
}
cli.out.Write([]byte{'\n'})
return err
}

411
vendor/github.com/hyperhq/hypercli/api/client/volume.go generated vendored Normal file
View File

@@ -0,0 +1,411 @@
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/docker/engine-api/types"
"github.com/docker/engine-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
}

39
vendor/github.com/hyperhq/hypercli/api/client/wait.go generated vendored Normal file
View File

@@ -0,0 +1,39 @@
package client
import (
"fmt"
"strings"
"golang.org/x/net/context"
Cli "github.com/hyperhq/hypercli/cli"
flag "github.com/hyperhq/hypercli/pkg/mflag"
)
// CmdWait blocks until a container stops, then prints its exit code.
//
// If more than one container is specified, this will wait synchronously on each container.
//
// Usage: docker wait CONTAINER [CONTAINER...]
func (cli *DockerCli) CmdWait(args ...string) error {
cmd := Cli.Subcmd("wait", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["wait"].Description, true)
cmd.Require(flag.Min, 1)
cmd.ParseFlags(args, true)
ctx := context.Background()
var errs []string
for _, name := range cmd.Args() {
status, err := cli.client.ContainerWait(ctx, name)
if err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintf(cli.out, "%d\n", status)
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}