Files
podman/pkg/api/handlers/images_build.go
baude 986feef2e8 fix e2e test failure
it is possible for layers.names to be nil and we must account for that.

Signed-off-by: baude <bbaude@redhat.com>
2020-01-10 13:06:26 -06:00

234 lines
6.7 KiB
Go

package handlers
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/containers/buildah"
"github.com/containers/buildah/imagebuildah"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/storage/pkg/archive"
log "github.com/sirupsen/logrus"
)
func BuildImage(w http.ResponseWriter, r *http.Request) {
authConfigs := map[string]AuthConfig{}
if hasHeader(r, "X-Registry-Config") {
registryHeader := getHeader(r, "X-Registry-Config")
authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(registryHeader))
if json.NewDecoder(authConfigsJSON).Decode(&authConfigs) != nil {
utils.BadRequest(w, "X-Registry-Config", registryHeader, json.NewDecoder(authConfigsJSON).Decode(&authConfigs))
return
}
}
anchorDir, err := extractTarFile(r, w)
if err != nil {
utils.InternalServerError(w, err)
return
}
// defer os.RemoveAll(anchorDir)
query := struct {
Dockerfile string `json:"dockerfile"`
Tag string `json:"t"`
ExtraHosts string `json:"extrahosts"`
Remote string `json:"remote"`
Quiet bool `json:"q"`
NoCache bool `json:"nocache"`
CacheFrom string `json:"cachefrom"`
Pull string `json:"pull"`
Rm bool `json:"rm"`
ForceRm bool `json:"forcerm"`
Memory int `json:"memory"`
MemSwap int `json:"memswap"`
CpuShares int `json:"cpushares"`
CpuSetCpus string `json:"cpusetcpus"`
CpuPeriod int `json:"cpuperiod"`
CpuQuota int `json:"cpuquota"`
BuildArgs string `json:"buildargs"`
ShmSize int `json:"shmsize"`
Squash bool `json:"squash"`
Labels string `json:"labels"`
NetworkMode string `json:"networkmode"`
Platform string `json:"platform"`
Target string `json:"target"`
Outputs string `json:"outputs"`
}{
Dockerfile: "Dockerfile",
Tag: "",
ExtraHosts: "",
Remote: "",
Quiet: false,
NoCache: false,
CacheFrom: "",
Pull: "",
Rm: true,
ForceRm: false,
Memory: 0,
MemSwap: 0,
CpuShares: 0,
CpuSetCpus: "",
CpuPeriod: 0,
CpuQuota: 0,
BuildArgs: "",
ShmSize: 64 * 1024 * 1024,
Squash: false,
Labels: "",
NetworkMode: "",
Platform: "",
Target: "",
Outputs: "",
}
if err := decodeQuery(r, &query); err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
return
}
// Tag is the name with optional tag...
var name = query.Tag
var tag string
if strings.Contains(query.Tag, ":") {
tokens := strings.SplitN(query.Tag, ":", 2)
name = tokens[0]
tag = tokens[1]
}
var buildArgs = map[string]string{}
if found := hasVar(r, "buildargs"); found {
if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil {
utils.BadRequest(w, "buildargs", query.BuildArgs, err)
return
}
}
// convert label formats
var labels = []string{}
if hasVar(r, "labels") {
var m = map[string]string{}
if err := json.Unmarshal([]byte(query.Labels), &m); err != nil {
utils.BadRequest(w, "labels", query.Labels, err)
return
}
for k, v := range m {
labels = append(labels, fmt.Sprintf("%s=%v", k, v))
}
}
buildOptions := imagebuildah.BuildOptions{
ContextDirectory: filepath.Join(anchorDir, "build"),
PullPolicy: 0,
Registry: "",
IgnoreUnrecognizedInstructions: false,
Quiet: query.Quiet,
Isolation: 0,
Runtime: "",
RuntimeArgs: nil,
TransientMounts: nil,
Compression: 0,
Args: buildArgs,
Output: name,
AdditionalTags: []string{tag},
Log: nil,
In: nil,
Out: nil,
Err: nil,
SignaturePolicyPath: "",
ReportWriter: nil,
OutputFormat: "",
SystemContext: nil,
NamespaceOptions: nil,
ConfigureNetwork: 0,
CNIPluginPath: "",
CNIConfigDir: "",
IDMappingOptions: nil,
AddCapabilities: nil,
DropCapabilities: nil,
CommonBuildOpts: &buildah.CommonBuildOptions{},
DefaultMountsFilePath: "",
IIDFile: "",
Squash: query.Squash,
Labels: labels,
Annotations: nil,
OnBuild: nil,
Layers: false,
NoCache: query.NoCache,
RemoveIntermediateCtrs: query.Rm,
ForceRmIntermediateCtrs: query.ForceRm,
BlobDirectory: "",
Target: query.Target,
Devices: nil,
}
id, _, err := getRuntime(r).Build(r.Context(), buildOptions, query.Dockerfile)
if err != nil {
utils.InternalServerError(w, err)
}
// Find image ID that was built...
utils.WriteResponse(w, http.StatusOK,
struct {
Stream string `json:"stream"`
}{
Stream: fmt.Sprintf("Successfully built %s\n", id),
})
}
func extractTarFile(r *http.Request, w http.ResponseWriter) (string, error) {
var (
// length int64
// n int64
copyErr error
)
// build a home for the request body
anchorDir, err := ioutil.TempDir("", "libpod_builder")
if err != nil {
return "", err
}
buildDir := filepath.Join(anchorDir, "build")
path := filepath.Join(anchorDir, "tarBall")
tarBall, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return "", err
}
defer tarBall.Close()
// if hasHeader(r, "Content-Length") {
// length, err := strconv.ParseInt(getHeader(r, "Content-Length"), 10, 64)
// if err != nil {
// return "", errors.New(fmt.Sprintf("Failed request: unable to parse Content-Length of '%s'", getHeader(r, "Content-Length")))
// }
// n, copyErr = io.CopyN(tarBall, r.Body, length+1)
// } else {
_, copyErr = io.Copy(tarBall, r.Body)
// }
r.Body.Close()
if copyErr != nil {
utils.InternalServerError(w,
fmt.Errorf("failed Request: Unable to copy tar file from request body %s", r.RequestURI))
}
log.Debugf("Content-Length: %s", getVar(r, "Content-Length"))
// if hasHeader(r, "Content-Length") && n != length {
// return "", errors.New(fmt.Sprintf("Failed request: Given Content-Length does not match file size %d != %d", n, length))
// }
_, _ = tarBall.Seek(0, 0)
if err := archive.Untar(tarBall, buildDir, &archive.TarOptions{}); err != nil {
return "", err
}
return anchorDir, nil
}