From 535c4c9a59b1e58c897677d6948a595cb3d28639 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 3 Dec 2015 18:27:01 -0500 Subject: [PATCH] Implement docker build with standalone client lib. Signed-off-by: David Calavera --- api/client/build.go | 136 +++++++++--------------------- api/client/lib/image_build.go | 150 ++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 97 deletions(-) create mode 100644 api/client/lib/image_build.go diff --git a/api/client/build.go b/api/client/build.go index 8de9c6837d..6268464c72 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -3,23 +3,19 @@ package client import ( "archive/tar" "bufio" - "encoding/base64" - "encoding/json" "fmt" "io" "io/ioutil" - "net/http" - "net/url" "os" "os/exec" "path/filepath" "regexp" "runtime" - "strconv" "strings" "github.com/docker/distribution/reference" "github.com/docker/docker/api" + "github.com/docker/docker/api/client/lib" Cli "github.com/docker/docker/cli" "github.com/docker/docker/opts" "github.com/docker/docker/pkg/archive" @@ -33,7 +29,6 @@ import ( "github.com/docker/docker/pkg/units" "github.com/docker/docker/pkg/urlutil" "github.com/docker/docker/registry" - "github.com/docker/docker/runconfig" tagpkg "github.com/docker/docker/tag" "github.com/docker/docker/utils" ) @@ -207,108 +202,55 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } } - // Send the build context - v := url.Values{ - "t": flTags.GetAll(), - } - if *suppressOutput { - v.Set("q", "1") - } + var remoteContext string if isRemote { - v.Set("remote", cmd.Arg(0)) - } - if *noCache { - v.Set("nocache", "1") - } - if *rm { - v.Set("rm", "1") - } else { - v.Set("rm", "0") + remoteContext = cmd.Arg(0) } - if *forceRm { - v.Set("forcerm", "1") + options := lib.ImageBuildOptions{ + Context: body, + Memory: memory, + MemorySwap: memorySwap, + Tags: flTags.GetAll(), + SuppressOutput: *suppressOutput, + RemoteContext: remoteContext, + NoCache: *noCache, + Remove: *rm, + ForceRemove: *forceRm, + PullParent: *pull, + Isolation: *isolation, + CPUSetCPUs: *flCPUSetCpus, + CPUSetMems: *flCPUSetMems, + CPUShares: *flCPUShares, + CPUQuota: *flCPUQuota, + CPUPeriod: *flCPUPeriod, + CgroupParent: *flCgroupParent, + ShmSize: *flShmSize, + Dockerfile: relDockerfile, + Ulimits: flUlimits.GetList(), + BuildArgs: flBuildArg.GetAll(), + AuthConfigs: cli.configFile.AuthConfigs, } - if *pull { - v.Set("pull", "1") + response, err := cli.client.ImageBuild(options) + if err != nil { + return err } - if !runconfig.IsolationLevel.IsDefault(runconfig.IsolationLevel(*isolation)) { - v.Set("isolation", *isolation) - } - - v.Set("cpusetcpus", *flCPUSetCpus) - v.Set("cpusetmems", *flCPUSetMems) - v.Set("cpushares", strconv.FormatInt(*flCPUShares, 10)) - v.Set("cpuquota", strconv.FormatInt(*flCPUQuota, 10)) - v.Set("cpuperiod", strconv.FormatInt(*flCPUPeriod, 10)) - v.Set("memory", strconv.FormatInt(memory, 10)) - v.Set("memswap", strconv.FormatInt(memorySwap, 10)) - v.Set("cgroupparent", *flCgroupParent) - - if *flShmSize != "" { - parsedShmSize, err := units.RAMInBytes(*flShmSize) - if err != nil { - return err + err = jsonmessage.DisplayJSONMessagesStream(response.Body, cli.out, cli.outFd, cli.isTerminalOut) + 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 + } + return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code} } - v.Set("shmsize", strconv.FormatInt(parsedShmSize, 10)) } - v.Set("dockerfile", relDockerfile) - - ulimitsVar := flUlimits.GetList() - ulimitsJSON, err := json.Marshal(ulimitsVar) - if err != nil { - return err - } - v.Set("ulimits", string(ulimitsJSON)) - - // collect all the build-time environment variables for the container - buildArgs := runconfig.ConvertKVStringsToMap(flBuildArg.GetAll()) - buildArgsJSON, err := json.Marshal(buildArgs) - if err != nil { - return err - } - v.Set("buildargs", string(buildArgsJSON)) - - headers := http.Header(make(map[string][]string)) - buf, err := json.Marshal(cli.configFile.AuthConfigs) - if err != nil { - return err - } - headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf)) - headers.Set("Content-Type", "application/tar") - - sopts := &streamOpts{ - rawTerminal: true, - in: body, - out: cli.out, - headers: headers, - } - - serverResp, err := cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), sopts) - // Windows: show error message about modified file permissions. - if runtime.GOOS == "windows" { - h, err := httputils.ParseServerHeader(serverResp.header.Get("Server")) - if err == nil { - if h.OS != "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.`) - } - } - } - - if jerr, ok := err.(*jsonmessage.JSONError); ok { - // If no error code is set, default to 1 - if jerr.Code == 0 { - jerr.Code = 1 - } - return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code} - } - - if err != nil { - return err + if response.OSType == "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.`) } // Since the build was successful, now we must tag any of the resolved diff --git a/api/client/lib/image_build.go b/api/client/lib/image_build.go new file mode 100644 index 0000000000..0b89b05ff6 --- /dev/null +++ b/api/client/lib/image_build.go @@ -0,0 +1,150 @@ +package lib + +import ( + "encoding/base64" + "encoding/json" + "io" + "net/http" + "net/url" + "strconv" + + "github.com/docker/docker/cliconfig" + "github.com/docker/docker/pkg/httputils" + "github.com/docker/docker/pkg/ulimit" + "github.com/docker/docker/pkg/units" + "github.com/docker/docker/runconfig" +) + +// ImageBuildOptions holds the information +// necessary to build images. +type ImageBuildOptions struct { + Tags []string + SuppressOutput bool + RemoteContext string + NoCache bool + Remove bool + ForceRemove bool + PullParent bool + Isolation string + CPUSetCPUs string + CPUSetMems string + CPUShares int64 + CPUQuota int64 + CPUPeriod int64 + Memory int64 + MemorySwap int64 + CgroupParent string + ShmSize string + Dockerfile string + Ulimits []*ulimit.Ulimit + BuildArgs []string + AuthConfigs map[string]cliconfig.AuthConfig + Context io.Reader +} + +// ImageBuildResponse holds information +// returned by a server after building +// an image. +type ImageBuildResponse struct { + Body io.ReadCloser + OSType string +} + +// ImageBuild sends request to the daemon to build images. +// The Body in the response implement an io.ReadCloser and it's up to the caller to +// close it. +func (cli *Client) ImageBuild(options ImageBuildOptions) (ImageBuildResponse, error) { + query, err := imageBuildOptionsToQuery(options) + if err != nil { + return ImageBuildResponse{}, err + } + + headers := http.Header(make(map[string][]string)) + buf, err := json.Marshal(options.AuthConfigs) + if err != nil { + return ImageBuildResponse{}, err + } + headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf)) + headers.Set("Content-Type", "application/tar") + + serverResp, err := cli.POSTRaw("/build", query, options.Context, headers) + if err != nil { + return ImageBuildResponse{}, err + } + + var osType string + if h, err := httputils.ParseServerHeader(serverResp.header.Get("Server")); err == nil { + osType = h.OS + } + + return ImageBuildResponse{ + Body: serverResp.body, + OSType: osType, + }, nil +} + +func imageBuildOptionsToQuery(options ImageBuildOptions) (url.Values, error) { + query := url.Values{ + "t": options.Tags, + } + if options.SuppressOutput { + query.Set("q", "1") + } + if options.RemoteContext != "" { + query.Set("remote", options.RemoteContext) + } + if options.NoCache { + query.Set("nocache", "1") + } + if options.Remove { + query.Set("rm", "1") + } else { + query.Set("rm", "0") + } + + if options.ForceRemove { + query.Set("forcerm", "1") + } + + if options.PullParent { + query.Set("pull", "1") + } + + if !runconfig.IsolationLevel.IsDefault(runconfig.IsolationLevel(options.Isolation)) { + query.Set("isolation", options.Isolation) + } + + query.Set("cpusetcpus", options.CPUSetCPUs) + query.Set("cpusetmems", options.CPUSetMems) + query.Set("cpushares", strconv.FormatInt(options.CPUShares, 10)) + query.Set("cpuquota", strconv.FormatInt(options.CPUQuota, 10)) + query.Set("cpuperiod", strconv.FormatInt(options.CPUPeriod, 10)) + query.Set("memory", strconv.FormatInt(options.Memory, 10)) + query.Set("memswap", strconv.FormatInt(options.MemorySwap, 10)) + query.Set("cgroupparent", options.CgroupParent) + + if options.ShmSize != "" { + parsedShmSize, err := units.RAMInBytes(options.ShmSize) + if err != nil { + return query, err + } + query.Set("shmsize", strconv.FormatInt(parsedShmSize, 10)) + } + + query.Set("dockerfile", options.Dockerfile) + + ulimitsJSON, err := json.Marshal(options.Ulimits) + if err != nil { + return query, err + } + query.Set("ulimits", string(ulimitsJSON)) + + buildArgs := runconfig.ConvertKVStringsToMap(options.BuildArgs) + buildArgsJSON, err := json.Marshal(buildArgs) + if err != nil { + return query, err + } + query.Set("buildargs", string(buildArgsJSON)) + + return query, nil +}