package client import ( "context" "encoding/base64" "encoding/json" "io" "net/http" "net/url" "strconv" cerrdefs "github.com/containerd/errdefs" "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/network" ) // ImageBuild sends a request to the daemon to build images. // The Body in the response implements an [io.ReadCloser] and it's up to the caller to // close it. func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options ImageBuildOptions) (ImageBuildResult, error) { query, err := cli.imageBuildOptionsToQuery(ctx, options) if err != nil { return ImageBuildResult{}, err } buf, err := json.Marshal(options.AuthConfigs) if err != nil { return ImageBuildResult{}, err } headers := http.Header{} headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf)) headers.Set("Content-Type", "application/x-tar") resp, err := cli.postRaw(ctx, "/build", query, buildContext, headers) if err != nil { return ImageBuildResult{}, err } return ImageBuildResult{ Body: resp.Body, }, nil } func (cli *Client) imageBuildOptionsToQuery(_ context.Context, options ImageBuildOptions) (url.Values, error) { query := url.Values{} if len(options.Tags) > 0 { query["t"] = options.Tags } if len(options.SecurityOpt) > 0 { query["securityopt"] = options.SecurityOpt } if len(options.ExtraHosts) > 0 { query["extrahosts"] = options.ExtraHosts } 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 { // only send value when opting out because the daemon's default is // to remove intermediate containers after a successful build, // // TODO(thaJeztah): deprecate "Remove" option, and provide a "NoRemove" or "Keep" option instead. query.Set("rm", "0") } if options.ForceRemove { query.Set("forcerm", "1") } if options.PullParent { query.Set("pull", "1") } if options.Squash { // TODO(thaJeztah): squash is experimental, and deprecated when using BuildKit? query.Set("squash", "1") } if !container.Isolation.IsDefault(options.Isolation) { query.Set("isolation", string(options.Isolation)) } if options.CPUSetCPUs != "" { query.Set("cpusetcpus", options.CPUSetCPUs) } if options.NetworkMode != "" && options.NetworkMode != network.NetworkDefault { query.Set("networkmode", options.NetworkMode) } if options.CPUSetMems != "" { query.Set("cpusetmems", options.CPUSetMems) } if options.CPUShares != 0 { query.Set("cpushares", strconv.FormatInt(options.CPUShares, 10)) } if options.CPUQuota != 0 { query.Set("cpuquota", strconv.FormatInt(options.CPUQuota, 10)) } if options.CPUPeriod != 0 { query.Set("cpuperiod", strconv.FormatInt(options.CPUPeriod, 10)) } if options.Memory != 0 { query.Set("memory", strconv.FormatInt(options.Memory, 10)) } if options.MemorySwap != 0 { query.Set("memswap", strconv.FormatInt(options.MemorySwap, 10)) } if options.CgroupParent != "" { query.Set("cgroupparent", options.CgroupParent) } if options.ShmSize != 0 { query.Set("shmsize", strconv.FormatInt(options.ShmSize, 10)) } if options.Dockerfile != "" { query.Set("dockerfile", options.Dockerfile) } if options.Target != "" { query.Set("target", options.Target) } if len(options.Ulimits) != 0 { ulimitsJSON, err := json.Marshal(options.Ulimits) if err != nil { return query, err } query.Set("ulimits", string(ulimitsJSON)) } if len(options.BuildArgs) != 0 { buildArgsJSON, err := json.Marshal(options.BuildArgs) if err != nil { return query, err } query.Set("buildargs", string(buildArgsJSON)) } if len(options.Labels) != 0 { labelsJSON, err := json.Marshal(options.Labels) if err != nil { return query, err } query.Set("labels", string(labelsJSON)) } if len(options.CacheFrom) != 0 { cacheFromJSON, err := json.Marshal(options.CacheFrom) if err != nil { return query, err } query.Set("cachefrom", string(cacheFromJSON)) } if options.SessionID != "" { query.Set("session", options.SessionID) } if len(options.Platforms) > 0 { if len(options.Platforms) > 1 { // TODO(thaJeztah): update API spec and add equivalent check on the daemon. We need this still for older daemons, which would ignore it. return query, cerrdefs.ErrInvalidArgument.WithMessage("specifying multiple platforms is not yet supported") } query.Set("platform", formatPlatform(options.Platforms[0])) } if options.BuildID != "" { query.Set("buildid", options.BuildID) } if options.Version != "" { query.Set("version", string(options.Version)) } if options.Outputs != nil { outputsJSON, err := json.Marshal(options.Outputs) if err != nil { return query, err } query.Set("outputs", string(outputsJSON)) } return query, nil }