Move api/client -> cli/command

Using
  gomvpkg
     -from github.com/docker/docker/api/client
     -to github.com/docker/docker/cli/command
     -vcs_mv_cmd 'git mv {{.Src}} {{.Dst}}'

Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
Daniel Nephin
2016-09-08 13:11:39 -04:00
parent efdd29abcf
commit 3bd1eb4b76
134 changed files with 15221 additions and 0 deletions

452
command/image/build.go Normal file
View File

@ -0,0 +1,452 @@
package image
import (
"archive/tar"
"bufio"
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"runtime"
"golang.org/x/net/context"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/builder"
"github.com/docker/docker/builder/dockerignore"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/urlutil"
"github.com/docker/docker/reference"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/docker/go-units"
"github.com/spf13/cobra"
)
type buildOptions struct {
context string
dockerfileName string
tags opts.ListOpts
labels []string
buildArgs opts.ListOpts
ulimits *runconfigopts.UlimitOpt
memory string
memorySwap string
shmSize string
cpuShares int64
cpuPeriod int64
cpuQuota int64
cpuSetCpus string
cpuSetMems string
cgroupParent string
isolation string
quiet bool
noCache bool
rm bool
forceRm bool
pull bool
}
// NewBuildCommand creates a new `docker build` command
func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
ulimits := make(map[string]*units.Ulimit)
options := buildOptions{
tags: opts.NewListOpts(validateTag),
buildArgs: opts.NewListOpts(runconfigopts.ValidateEnv),
ulimits: runconfigopts.NewUlimitOpt(&ulimits),
}
cmd := &cobra.Command{
Use: "build [OPTIONS] PATH | URL | -",
Short: "Build an image from a Dockerfile",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
options.context = args[0]
return runBuild(dockerCli, options)
},
}
flags := cmd.Flags()
flags.VarP(&options.tags, "tag", "t", "Name and optionally a tag in the 'name:tag' format")
flags.Var(&options.buildArgs, "build-arg", "Set build-time variables")
flags.Var(options.ulimits, "ulimit", "Ulimit options")
flags.StringVarP(&options.dockerfileName, "file", "f", "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
flags.StringVarP(&options.memory, "memory", "m", "", "Memory limit")
flags.StringVar(&options.memorySwap, "memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
flags.StringVar(&options.shmSize, "shm-size", "", "Size of /dev/shm, default value is 64MB")
flags.Int64VarP(&options.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
flags.Int64Var(&options.cpuPeriod, "cpu-period", 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
flags.Int64Var(&options.cpuQuota, "cpu-quota", 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
flags.StringVar(&options.cpuSetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
flags.StringVar(&options.cpuSetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
flags.StringVar(&options.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container")
flags.StringVar(&options.isolation, "isolation", "", "Container isolation technology")
flags.StringSliceVar(&options.labels, "label", []string{}, "Set metadata for an image")
flags.BoolVar(&options.noCache, "no-cache", false, "Do not use cache when building the image")
flags.BoolVar(&options.rm, "rm", true, "Remove intermediate containers after a successful build")
flags.BoolVar(&options.forceRm, "force-rm", false, "Always remove intermediate containers")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the build output and print image ID on success")
flags.BoolVar(&options.pull, "pull", false, "Always attempt to pull a newer version of the image")
command.AddTrustedFlags(flags, true)
return cmd
}
// lastProgressOutput is the same as progress.Output except
// that it only output with the last update. It is used in
// non terminal scenarios to depresss verbose messages
type lastProgressOutput struct {
output progress.Output
}
// WriteProgress formats progress information from a ProgressReader.
func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error {
if !prog.LastUpdate {
return nil
}
return out.output.WriteProgress(prog)
}
func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
var (
buildCtx io.ReadCloser
err error
)
specifiedContext := options.context
var (
contextDir string
tempDir string
relDockerfile string
progBuff io.Writer
buildBuff io.Writer
)
progBuff = dockerCli.Out()
buildBuff = dockerCli.Out()
if options.quiet {
progBuff = bytes.NewBuffer(nil)
buildBuff = bytes.NewBuffer(nil)
}
switch {
case specifiedContext == "-":
buildCtx, relDockerfile, err = builder.GetContextFromReader(dockerCli.In(), options.dockerfileName)
case urlutil.IsGitURL(specifiedContext):
tempDir, relDockerfile, err = builder.GetContextFromGitURL(specifiedContext, options.dockerfileName)
case urlutil.IsURL(specifiedContext):
buildCtx, relDockerfile, err = builder.GetContextFromURL(progBuff, specifiedContext, options.dockerfileName)
default:
contextDir, relDockerfile, err = builder.GetContextFromLocalDir(specifiedContext, options.dockerfileName)
}
if err != nil {
if options.quiet && urlutil.IsURL(specifiedContext) {
fmt.Fprintln(dockerCli.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
}
defer f.Close()
var excludes []string
if err == nil {
excludes, err = dockerignore.ReadAll(f)
if err != nil {
return err
}
}
if err := builder.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 command.IsTrusted() {
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
// Dockerfile which uses trusted pulls.
buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, dockerCli.TrustedReference, &resolvedTags)
}
// Setup an upload progress bar
progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true)
if !dockerCli.Out().IsTerminal() {
progressOutput = &lastProgressOutput{output: progressOutput}
}
var body io.Reader = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
var memory int64
if options.memory != "" {
parsedMemory, err := units.RAMInBytes(options.memory)
if err != nil {
return err
}
memory = parsedMemory
}
var memorySwap int64
if options.memorySwap != "" {
if options.memorySwap == "-1" {
memorySwap = -1
} else {
parsedMemorySwap, err := units.RAMInBytes(options.memorySwap)
if err != nil {
return err
}
memorySwap = parsedMemorySwap
}
}
var shmSize int64
if options.shmSize != "" {
shmSize, err = units.RAMInBytes(options.shmSize)
if err != nil {
return err
}
}
buildOptions := types.ImageBuildOptions{
Memory: memory,
MemorySwap: memorySwap,
Tags: options.tags.GetAll(),
SuppressOutput: options.quiet,
NoCache: options.noCache,
Remove: options.rm,
ForceRemove: options.forceRm,
PullParent: options.pull,
Isolation: container.Isolation(options.isolation),
CPUSetCPUs: options.cpuSetCpus,
CPUSetMems: options.cpuSetMems,
CPUShares: options.cpuShares,
CPUQuota: options.cpuQuota,
CPUPeriod: options.cpuPeriod,
CgroupParent: options.cgroupParent,
Dockerfile: relDockerfile,
ShmSize: shmSize,
Ulimits: options.ulimits.GetList(),
BuildArgs: runconfigopts.ConvertKVStringsToMap(options.buildArgs.GetAll()),
AuthConfigs: dockerCli.RetrieveAuthConfigs(),
Labels: runconfigopts.ConvertKVStringsToMap(options.labels),
}
response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)
if err != nil {
return err
}
defer response.Body.Close()
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.Out().FD(), dockerCli.Out().IsTerminal(), 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 options.quiet {
fmt.Fprintf(dockerCli.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" && !options.quiet {
fmt.Fprintln(dockerCli.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 options.quiet {
fmt.Fprintf(dockerCli.Out(), "%s", buildBuff)
}
if command.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 := dockerCli.TagTrusted(ctx, resolved.digestRef, resolved.tagRef); err != nil {
return err
}
}
}
return nil
}
type translatorFunc func(context.Context, reference.NamedTagged) (reference.Canonical, error)
// 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
}
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 && command.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
}
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
}

99
command/image/history.go Normal file
View File

@ -0,0 +1,99 @@
package image
import (
"fmt"
"strconv"
"strings"
"text/tabwriter"
"time"
"golang.org/x/net/context"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/go-units"
"github.com/spf13/cobra"
)
type historyOptions struct {
image string
human bool
quiet bool
noTrunc bool
}
// NewHistoryCommand creates a new `docker history` command
func NewHistoryCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts historyOptions
cmd := &cobra.Command{
Use: "history [OPTIONS] IMAGE",
Short: "Show the history of an image",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.image = args[0]
return runHistory(dockerCli, opts)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.human, "human", "H", true, "Print sizes and dates in human readable format")
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only show numeric IDs")
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
return cmd
}
func runHistory(dockerCli *command.DockerCli, opts historyOptions) error {
ctx := context.Background()
history, err := dockerCli.Client().ImageHistory(ctx, opts.image)
if err != nil {
return err
}
w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
if opts.quiet {
for _, entry := range history {
if opts.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 !opts.noTrunc {
createdBy = stringutils.Ellipsis(createdBy, 45)
imageID = stringid.TruncateID(entry.ID)
}
if opts.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
}

103
command/image/images.go Normal file
View File

@ -0,0 +1,103 @@
package image
import (
"golang.org/x/net/context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/cli/command/formatter"
"github.com/spf13/cobra"
)
type imagesOptions struct {
matchName string
quiet bool
all bool
noTrunc bool
showDigests bool
format string
filter []string
}
// NewImagesCommand creates a new `docker images` command
func NewImagesCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts imagesOptions
cmd := &cobra.Command{
Use: "images [OPTIONS] [REPOSITORY[:TAG]]",
Short: "List images",
Args: cli.RequiresMaxArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
opts.matchName = args[0]
}
return runImages(dockerCli, opts)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only show numeric IDs")
flags.BoolVarP(&opts.all, "all", "a", false, "Show all images (default hides intermediate images)")
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
flags.BoolVar(&opts.showDigests, "digests", false, "Show digests")
flags.StringVar(&opts.format, "format", "", "Pretty-print images using a Go template")
flags.StringSliceVarP(&opts.filter, "filter", "f", []string{}, "Filter output based on conditions provided")
return cmd
}
func runImages(dockerCli *command.DockerCli, opts imagesOptions) error {
ctx := context.Background()
// Consolidate all filter flags, and sanity check them early.
// They'll get process in the daemon/server.
imageFilterArgs := filters.NewArgs()
for _, f := range opts.filter {
var err error
imageFilterArgs, err = filters.ParseFlag(f, imageFilterArgs)
if err != nil {
return err
}
}
matchName := opts.matchName
options := types.ImageListOptions{
MatchName: matchName,
All: opts.all,
Filters: imageFilterArgs,
}
images, err := dockerCli.Client().ImageList(ctx, options)
if err != nil {
return err
}
f := opts.format
if len(f) == 0 {
if len(dockerCli.ConfigFile().ImagesFormat) > 0 && !opts.quiet {
f = dockerCli.ConfigFile().ImagesFormat
} else {
f = "table"
}
}
imagesCtx := formatter.ImageContext{
Context: formatter.Context{
Output: dockerCli.Out(),
Format: f,
Quiet: opts.quiet,
Trunc: !opts.noTrunc,
},
Digest: opts.showDigests,
Images: images,
}
imagesCtx.Write()
return nil
}

88
command/image/import.go Normal file
View File

@ -0,0 +1,88 @@
package image
import (
"io"
"os"
"golang.org/x/net/context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
dockeropts "github.com/docker/docker/opts"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/urlutil"
"github.com/spf13/cobra"
)
type importOptions struct {
source string
reference string
changes dockeropts.ListOpts
message string
}
// NewImportCommand creates a new `docker import` command
func NewImportCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts importOptions
cmd := &cobra.Command{
Use: "import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]",
Short: "Import the contents from a tarball to create a filesystem image",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.source = args[0]
if len(args) > 1 {
opts.reference = args[1]
}
return runImport(dockerCli, opts)
},
}
flags := cmd.Flags()
opts.changes = dockeropts.NewListOpts(nil)
flags.VarP(&opts.changes, "change", "c", "Apply Dockerfile instruction to the created image")
flags.StringVarP(&opts.message, "message", "m", "", "Set commit message for imported image")
return cmd
}
func runImport(dockerCli *command.DockerCli, opts importOptions) error {
var (
in io.Reader
srcName = opts.source
)
if opts.source == "-" {
in = dockerCli.In()
} else if !urlutil.IsURL(opts.source) {
srcName = "-"
file, err := os.Open(opts.source)
if err != nil {
return err
}
defer file.Close()
in = file
}
source := types.ImageImportSource{
Source: in,
SourceName: srcName,
}
options := types.ImageImportOptions{
Message: opts.message,
Changes: opts.changes.GetAll(),
}
clnt := dockerCli.Client()
responseBody, err := clnt.ImageImport(context.Background(), source, opts.reference, options)
if err != nil {
return err
}
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil)
}

67
command/image/load.go Normal file
View File

@ -0,0 +1,67 @@
package image
import (
"io"
"os"
"golang.org/x/net/context"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/spf13/cobra"
)
type loadOptions struct {
input string
quiet bool
}
// NewLoadCommand creates a new `docker load` command
func NewLoadCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts loadOptions
cmd := &cobra.Command{
Use: "load [OPTIONS]",
Short: "Load an image from a tar archive or STDIN",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runLoad(dockerCli, opts)
},
}
flags := cmd.Flags()
flags.StringVarP(&opts.input, "input", "i", "", "Read from tar archive file, instead of STDIN")
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress the load output")
return cmd
}
func runLoad(dockerCli *command.DockerCli, opts loadOptions) error {
var input io.Reader = dockerCli.In()
if opts.input != "" {
file, err := os.Open(opts.input)
if err != nil {
return err
}
defer file.Close()
input = file
}
if !dockerCli.Out().IsTerminal() {
opts.quiet = true
}
response, err := dockerCli.Client().ImageLoad(context.Background(), input, opts.quiet)
if err != nil {
return err
}
defer response.Body.Close()
if response.Body != nil && response.JSON {
return jsonmessage.DisplayJSONMessagesToStream(response.Body, dockerCli.Out(), nil)
}
_, err = io.Copy(dockerCli.Out(), response.Body)
return err
}

93
command/image/pull.go Normal file
View File

@ -0,0 +1,93 @@
package image
import (
"errors"
"fmt"
"strings"
"golang.org/x/net/context"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/reference"
"github.com/docker/docker/registry"
"github.com/spf13/cobra"
)
type pullOptions struct {
remote string
all bool
}
// NewPullCommand creates a new `docker pull` command
func NewPullCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts pullOptions
cmd := &cobra.Command{
Use: "pull [OPTIONS] NAME[:TAG|@DIGEST]",
Short: "Pull an image or a repository from a registry",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.remote = args[0]
return runPull(dockerCli, opts)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.all, "all-tags", "a", false, "Download all tagged images in the repository")
command.AddTrustedFlags(flags, true)
return cmd
}
func runPull(dockerCli *command.DockerCli, opts pullOptions) error {
distributionRef, err := reference.ParseNamed(opts.remote)
if err != nil {
return err
}
if opts.all && !reference.IsNameOnly(distributionRef) {
return errors.New("tag can't be used with --all-tags/-a")
}
if !opts.all && reference.IsNameOnly(distributionRef) {
distributionRef = reference.WithDefaultTag(distributionRef)
fmt.Fprintf(dockerCli.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()
}
registryRef := 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 := dockerCli.ResolveAuthConfig(ctx, repoInfo.Index)
requestPrivilege := dockerCli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "pull")
if command.IsTrusted() && !registryRef.HasDigest() {
// Check if tag is digest
err = dockerCli.TrustedPull(ctx, repoInfo, registryRef, authConfig, requestPrivilege)
} else {
err = dockerCli.ImagePullPrivileged(ctx, authConfig, distributionRef.String(), requestPrivilege, opts.all)
}
if err != nil {
if strings.Contains(err.Error(), "target is a plugin") {
return errors.New(err.Error() + " - Use `docker plugin install`")
}
return err
}
return nil
}

61
command/image/push.go Normal file
View File

@ -0,0 +1,61 @@
package image
import (
"golang.org/x/net/context"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/reference"
"github.com/docker/docker/registry"
"github.com/spf13/cobra"
)
// NewPushCommand creates a new `docker push` command
func NewPushCommand(dockerCli *command.DockerCli) *cobra.Command {
cmd := &cobra.Command{
Use: "push [OPTIONS] NAME[:TAG]",
Short: "Push an image or a repository to a registry",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runPush(dockerCli, args[0])
},
}
flags := cmd.Flags()
command.AddTrustedFlags(flags, true)
return cmd
}
func runPush(dockerCli *command.DockerCli, remote string) error {
ref, err := reference.ParseNamed(remote)
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 := dockerCli.ResolveAuthConfig(ctx, repoInfo.Index)
requestPrivilege := dockerCli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "push")
if command.IsTrusted() {
return dockerCli.TrustedPush(ctx, repoInfo, ref, authConfig, requestPrivilege)
}
responseBody, err := dockerCli.ImagePushPrivileged(ctx, authConfig, ref.String(), requestPrivilege)
if err != nil {
return err
}
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil)
}

70
command/image/remove.go Normal file
View File

@ -0,0 +1,70 @@
package image
import (
"fmt"
"strings"
"golang.org/x/net/context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/spf13/cobra"
)
type removeOptions struct {
force bool
noPrune bool
}
// NewRemoveCommand creates a new `docker remove` command
func NewRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts removeOptions
cmd := &cobra.Command{
Use: "rmi [OPTIONS] IMAGE [IMAGE...]",
Short: "Remove one or more images",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runRemove(dockerCli, opts, args)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.force, "force", "f", false, "Force removal of the image")
flags.BoolVar(&opts.noPrune, "no-prune", false, "Do not delete untagged parents")
return cmd
}
func runRemove(dockerCli *command.DockerCli, opts removeOptions, images []string) error {
client := dockerCli.Client()
ctx := context.Background()
options := types.ImageRemoveOptions{
Force: opts.force,
PruneChildren: !opts.noPrune,
}
var errs []string
for _, image := range images {
dels, err := client.ImageRemove(ctx, image, options)
if err != nil {
errs = append(errs, err.Error())
} else {
for _, del := range dels {
if del.Deleted != "" {
fmt.Fprintf(dockerCli.Out(), "Deleted: %s\n", del.Deleted)
} else {
fmt.Fprintf(dockerCli.Out(), "Untagged: %s\n", del.Untagged)
}
}
}
}
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

57
command/image/save.go Normal file
View File

@ -0,0 +1,57 @@
package image
import (
"errors"
"io"
"golang.org/x/net/context"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/spf13/cobra"
)
type saveOptions struct {
images []string
output string
}
// NewSaveCommand creates a new `docker save` command
func NewSaveCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts saveOptions
cmd := &cobra.Command{
Use: "save [OPTIONS] IMAGE [IMAGE...]",
Short: "Save one or more images to a tar archive (streamed to STDOUT by default)",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.images = args
return runSave(dockerCli, opts)
},
}
flags := cmd.Flags()
flags.StringVarP(&opts.output, "output", "o", "", "Write to a file, instead of STDOUT")
return cmd
}
func runSave(dockerCli *command.DockerCli, opts saveOptions) error {
if opts.output == "" && dockerCli.Out().IsTerminal() {
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
}
responseBody, err := dockerCli.Client().ImageSave(context.Background(), opts.images)
if err != nil {
return err
}
defer responseBody.Close()
if opts.output == "" {
_, err := io.Copy(dockerCli.Out(), responseBody)
return err
}
return command.CopyToFile(opts.output, responseBody)
}

135
command/image/search.go Normal file
View File

@ -0,0 +1,135 @@
package image
import (
"fmt"
"sort"
"strings"
"text/tabwriter"
"golang.org/x/net/context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/registry"
"github.com/spf13/cobra"
)
type searchOptions struct {
term string
noTrunc bool
limit int
filter []string
// Deprecated
stars uint
automated bool
}
// NewSearchCommand creates a new `docker search` command
func NewSearchCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts searchOptions
cmd := &cobra.Command{
Use: "search [OPTIONS] TERM",
Short: "Search the Docker Hub for images",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.term = args[0]
return runSearch(dockerCli, opts)
},
}
flags := cmd.Flags()
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
flags.StringSliceVarP(&opts.filter, "filter", "f", []string{}, "Filter output based on conditions provided")
flags.IntVar(&opts.limit, "limit", registry.DefaultSearchLimit, "Max number of search results")
flags.BoolVar(&opts.automated, "automated", false, "Only show automated builds")
flags.UintVarP(&opts.stars, "stars", "s", 0, "Only displays with at least x stars")
flags.MarkDeprecated("automated", "use --filter=automated=true instead")
flags.MarkDeprecated("stars", "use --filter=stars=3 instead")
return cmd
}
func runSearch(dockerCli *command.DockerCli, opts searchOptions) error {
indexInfo, err := registry.ParseSearchIndexInfo(opts.term)
if err != nil {
return err
}
ctx := context.Background()
authConfig := dockerCli.ResolveAuthConfig(ctx, indexInfo)
requestPrivilege := dockerCli.RegistryAuthenticationPrivilegedFunc(indexInfo, "search")
encodedAuth, err := command.EncodeAuthToBase64(authConfig)
if err != nil {
return err
}
searchFilters := filters.NewArgs()
for _, f := range opts.filter {
var err error
searchFilters, err = filters.ParseFlag(f, searchFilters)
if err != nil {
return err
}
}
options := types.ImageSearchOptions{
RegistryAuth: encodedAuth,
PrivilegeFunc: requestPrivilege,
Filters: searchFilters,
Limit: opts.limit,
}
clnt := dockerCli.Client()
unorderedResults, err := clnt.ImageSearch(ctx, opts.term, options)
if err != nil {
return err
}
results := searchResultsByStars(unorderedResults)
sort.Sort(results)
w := tabwriter.NewWriter(dockerCli.Out(), 10, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
for _, res := range results {
// --automated and -s, --stars are deprecated since Docker 1.12
if (opts.automated && !res.IsAutomated) || (int(opts.stars) > res.StarCount) {
continue
}
desc := strings.Replace(res.Description, "\n", " ", -1)
desc = strings.Replace(desc, "\r", " ", -1)
if !opts.noTrunc {
desc = stringutils.Ellipsis(desc, 45)
}
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 }

41
command/image/tag.go Normal file
View File

@ -0,0 +1,41 @@
package image
import (
"golang.org/x/net/context"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/spf13/cobra"
)
type tagOptions struct {
image string
name string
}
// NewTagCommand creates a new `docker tag` command
func NewTagCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts tagOptions
cmd := &cobra.Command{
Use: "tag IMAGE[:TAG] IMAGE[:TAG]",
Short: "Tag an image into a repository",
Args: cli.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
opts.image = args[0]
opts.name = args[1]
return runTag(dockerCli, opts)
},
}
flags := cmd.Flags()
flags.SetInterspersed(false)
return cmd
}
func runTag(dockerCli *command.DockerCli, opts tagOptions) error {
ctx := context.Background()
return dockerCli.Client().ImageTag(ctx, opts.image, opts.name)
}