Implement docker build with standalone client lib.

Signed-off-by: David Calavera <david.calavera@gmail.com>
This commit is contained in:
David Calavera 2015-12-03 18:27:01 -05:00
parent 900ad2897f
commit 535c4c9a59
2 changed files with 189 additions and 97 deletions

View File

@ -3,23 +3,19 @@ package client
import ( import (
"archive/tar" "archive/tar"
"bufio" "bufio"
"encoding/base64"
"encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http"
"net/url"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime" "runtime"
"strconv"
"strings" "strings"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/docker/docker/api" "github.com/docker/docker/api"
"github.com/docker/docker/api/client/lib"
Cli "github.com/docker/docker/cli" Cli "github.com/docker/docker/cli"
"github.com/docker/docker/opts" "github.com/docker/docker/opts"
"github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/archive"
@ -33,7 +29,6 @@ import (
"github.com/docker/docker/pkg/units" "github.com/docker/docker/pkg/units"
"github.com/docker/docker/pkg/urlutil" "github.com/docker/docker/pkg/urlutil"
"github.com/docker/docker/registry" "github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
tagpkg "github.com/docker/docker/tag" tagpkg "github.com/docker/docker/tag"
"github.com/docker/docker/utils" "github.com/docker/docker/utils"
) )
@ -207,108 +202,55 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
} }
} }
// Send the build context var remoteContext string
v := url.Values{
"t": flTags.GetAll(),
}
if *suppressOutput {
v.Set("q", "1")
}
if isRemote { if isRemote {
v.Set("remote", cmd.Arg(0)) remoteContext = cmd.Arg(0)
}
if *noCache {
v.Set("nocache", "1")
}
if *rm {
v.Set("rm", "1")
} else {
v.Set("rm", "0")
} }
if *forceRm { options := lib.ImageBuildOptions{
v.Set("forcerm", "1") 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 { response, err := cli.client.ImageBuild(options)
v.Set("pull", "1") if err != nil {
return err
} }
if !runconfig.IsolationLevel.IsDefault(runconfig.IsolationLevel(*isolation)) { err = jsonmessage.DisplayJSONMessagesStream(response.Body, cli.out, cli.outFd, cli.isTerminalOut)
v.Set("isolation", *isolation) if err != nil {
} if jerr, ok := err.(*jsonmessage.JSONError); ok {
// If no error code is set, default to 1
v.Set("cpusetcpus", *flCPUSetCpus) if jerr.Code == 0 {
v.Set("cpusetmems", *flCPUSetMems) jerr.Code = 1
v.Set("cpushares", strconv.FormatInt(*flCPUShares, 10)) }
v.Set("cpuquota", strconv.FormatInt(*flCPUQuota, 10)) return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
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
} }
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. // Windows: show error message about modified file permissions.
if runtime.GOOS == "windows" { if response.OSType == "windows" {
h, err := httputils.ParseServerHeader(serverResp.header.Get("Server")) 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 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
} }
// Since the build was successful, now we must tag any of the resolved // Since the build was successful, now we must tag any of the resolved

View File

@ -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
}