| 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) // #nosec G117 -- ignore "Marshaled struct field "Password" (JSON key "password") matches secret pattern" | |
| 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 | |
| } |