Merge pull request #23343 from vdemeester/migrate-build-to-cobra

Use spf13/cobra for docker build
This commit is contained in:
Alexander Morozov 2016-06-08 11:59:48 -07:00
commit 3b08711bb7
6 changed files with 130 additions and 97 deletions

View file

@ -3,7 +3,6 @@ package client
// Command returns a cli command handler if one exists
func (cli *DockerCli) Command(name string) func(...string) error {
return map[string]func(...string) error{
"build": cli.CmdBuild,
"commit": cli.CmdCommit,
"cp": cli.CmdCp,
"events": cli.CmdEvents,

View file

@ -1,4 +1,4 @@
package client
package image
import (
"archive/tar"
@ -14,14 +14,14 @@ import (
"golang.org/x/net/context"
"github.com/docker/docker/api"
"github.com/docker/docker/api/client"
"github.com/docker/docker/builder"
"github.com/docker/docker/builder/dockerignore"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/cli"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/jsonmessage"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/urlutil"
@ -30,58 +30,88 @@ import (
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
"github.com/docker/go-units"
"github.com/spf13/cobra"
)
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) CmdBuild(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")
isolation := cmd.String([]string{"-isolation"}, "", "Container isolation technology")
flLabels := opts.NewListOpts(nil)
cmd.Var(&flLabels, []string{"-label"}, "Set metadata for an image")
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 *client.DockerCli) *cobra.Command {
ulimits := make(map[string]*units.Ulimit)
flUlimits := runconfigopts.NewUlimitOpt(&ulimits)
cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
options := buildOptions{
tags: opts.NewListOpts(validateTag),
buildArgs: opts.NewListOpts(runconfigopts.ValidateEnv),
ulimits: runconfigopts.NewUlimitOpt(&ulimits),
}
cmd.Require(flag.Exact, 1)
cmd := &cobra.Command{
Use: "build 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)
},
}
// For trusted pull on "FROM <image>" instruction.
addTrustedFlags(cmd, true)
flags := cmd.Flags()
cmd.ParseFlags(args, true)
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")
client.AddTrustedFlags(flags, true)
return cmd
}
func runBuild(dockerCli *client.DockerCli, options buildOptions) error {
var (
buildCtx io.ReadCloser
err error
)
specifiedContext := cmd.Arg(0)
specifiedContext := options.context
var (
contextDir string
@ -91,27 +121,27 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
buildBuff io.Writer
)
progBuff = cli.out
buildBuff = cli.out
if *suppressOutput {
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(cli.in, *dockerfileName)
buildCtx, relDockerfile, err = builder.GetContextFromReader(dockerCli.In(), options.dockerfileName)
case urlutil.IsGitURL(specifiedContext):
tempDir, relDockerfile, err = builder.GetContextFromGitURL(specifiedContext, *dockerfileName)
tempDir, relDockerfile, err = builder.GetContextFromGitURL(specifiedContext, options.dockerfileName)
case urlutil.IsURL(specifiedContext):
buildCtx, relDockerfile, err = builder.GetContextFromURL(progBuff, specifiedContext, *dockerfileName)
buildCtx, relDockerfile, err = builder.GetContextFromURL(progBuff, specifiedContext, options.dockerfileName)
default:
contextDir, relDockerfile, err = builder.GetContextFromLocalDir(specifiedContext, *dockerfileName)
contextDir, relDockerfile, err = builder.GetContextFromLocalDir(specifiedContext, options.dockerfileName)
}
if err != nil {
if *suppressOutput && urlutil.IsURL(specifiedContext) {
fmt.Fprintln(cli.err, progBuff)
if options.quiet && urlutil.IsURL(specifiedContext) {
fmt.Fprintln(dockerCli.Err(), progBuff)
}
return fmt.Errorf("unable to prepare context: %s", err)
}
@ -172,10 +202,10 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
ctx := context.Background()
var resolvedTags []*resolvedTag
if IsTrusted() {
if client.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)
buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, dockerCli.TrustedReference, &resolvedTags)
}
// Setup an upload progress bar
@ -184,8 +214,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
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 options.memory != "" {
parsedMemory, err := units.RAMInBytes(options.memory)
if err != nil {
return err
}
@ -193,11 +223,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
}
var memorySwap int64
if *flMemorySwap != "" {
if *flMemorySwap == "-1" {
if options.memorySwap != "" {
if options.memorySwap == "-1" {
memorySwap = -1
} else {
parsedMemorySwap, err := units.RAMInBytes(*flMemorySwap)
parsedMemorySwap, err := units.RAMInBytes(options.memorySwap)
if err != nil {
return err
}
@ -206,74 +236,74 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
}
var shmSize int64
if *flShmSize != "" {
shmSize, err = units.RAMInBytes(*flShmSize)
if options.shmSize != "" {
shmSize, err = units.RAMInBytes(options.shmSize)
if err != nil {
return err
}
}
options := types.ImageBuildOptions{
buildOptions := types.ImageBuildOptions{
Memory: memory,
MemorySwap: memorySwap,
Tags: flTags.GetAll(),
SuppressOutput: *suppressOutput,
NoCache: *noCache,
Remove: *rm,
ForceRemove: *forceRm,
PullParent: *pull,
Isolation: container.Isolation(*isolation),
CPUSetCPUs: *flCPUSetCpus,
CPUSetMems: *flCPUSetMems,
CPUShares: *flCPUShares,
CPUQuota: *flCPUQuota,
CPUPeriod: *flCPUPeriod,
CgroupParent: *flCgroupParent,
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: flUlimits.GetList(),
BuildArgs: runconfigopts.ConvertKVStringsToMap(flBuildArg.GetAll()),
AuthConfigs: cli.retrieveAuthConfigs(),
Labels: runconfigopts.ConvertKVStringsToMap(flLabels.GetAll()),
Ulimits: options.ulimits.GetList(),
BuildArgs: runconfigopts.ConvertKVStringsToMap(options.buildArgs.GetAll()),
AuthConfigs: dockerCli.RetrieveAuthConfigs(),
Labels: runconfigopts.ConvertKVStringsToMap(options.labels),
}
response, err := cli.client.ImageBuild(ctx, body, options)
response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)
if err != nil {
return err
}
defer response.Body.Close()
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, cli.outFd, cli.isTerminalOut, nil)
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.OutFd(), dockerCli.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)
if options.quiet {
fmt.Fprintf(dockerCli.Err(), "%s%s", progBuff, buildBuff)
}
return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
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.`)
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 *suppressOutput {
fmt.Fprintf(cli.out, "%s", buildBuff)
if options.quiet {
fmt.Fprintf(dockerCli.Out(), "%s", buildBuff)
}
if IsTrusted() {
if client.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 {
if err := dockerCli.TagTrusted(ctx, resolved.digestRef, resolved.tagRef); err != nil {
return err
}
}
@ -282,6 +312,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
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)
@ -321,7 +353,7 @@ func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator
return nil, nil, err
}
ref = reference.WithDefaultTag(ref)
if ref, ok := ref.(reference.NamedTagged); ok && IsTrusted() {
if ref, ok := ref.(reference.NamedTagged); ok && client.IsTrusted() {
trustedRef, err := translator(ctx, ref)
if err != nil {
return nil, nil, err

View file

@ -202,7 +202,8 @@ func (cli *DockerCli) ResolveAuthConfig(ctx context.Context, index *registrytype
return a
}
func (cli *DockerCli) retrieveAuthConfigs() map[string]types.AuthConfig {
// RetrieveAuthConfigs return all credentials.
func (cli *DockerCli) RetrieveAuthConfigs() map[string]types.AuthConfig {
acs, _ := getAllCredentials(cli.configFile)
return acs
}

View file

@ -52,6 +52,7 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
container.NewTopCommand(dockerCli),
container.NewUnpauseCommand(dockerCli),
container.NewWaitCommand(dockerCli),
image.NewBuildCommand(dockerCli),
image.NewHistoryCommand(dockerCli),
image.NewImagesCommand(dockerCli),
image.NewRemoveCommand(dockerCli),

View file

@ -8,7 +8,6 @@ type Command struct {
// DockerCommandUsage lists the top level docker commands and their short usage
var DockerCommandUsage = []Command{
{"build", "Build an image from a Dockerfile"},
{"commit", "Create a new image from a container's changes"},
{"cp", "Copy files/folders between a container and the local filesystem"},
{"events", "Get real time events from the server"},

View file

@ -4685,16 +4685,17 @@ func (s *DockerSuite) TestBuildNotVerboseFailure(c *check.C) {
func (s *DockerSuite) TestBuildNotVerboseFailureRemote(c *check.C) {
// This test ensures that when given a wrong URL, stderr in quiet mode and
// stdout and stderr in verbose mode are identical.
URL := "http://bla.bla.com"
// stderr in verbose mode are identical.
// TODO(vdemeester) with cobra, stdout has a carriage return too much so this test should not check stdout
URL := "http://something.invalid"
Name := "quiet_build_wrong_remote"
_, _, qstderr, qerr := buildImageWithStdoutStderr(Name, "", false, "-q", "--force-rm", "--rm", URL)
_, vstdout, vstderr, verr := buildImageWithStdoutStderr(Name, "", false, "--force-rm", "--rm", URL)
_, _, vstderr, verr := buildImageWithStdoutStderr(Name, "", false, "--force-rm", "--rm", URL)
if qerr == nil || verr == nil {
c.Fatal(fmt.Errorf("Test [%s] expected to fail but didn't", Name))
}
if qstderr != vstdout+vstderr {
c.Fatal(fmt.Errorf("Test[%s] expected that quiet stderr and verbose stdout are equal; quiet [%v], verbose [%v]", Name, qstderr, vstdout))
if qstderr != vstderr {
c.Fatal(fmt.Errorf("Test[%s] expected that quiet stderr and verbose stdout are equal; quiet [%v], verbose [%v]", Name, qstderr, vstderr))
}
}