diff options
Diffstat (limited to 'pkg/api/handlers/compat/images_build.go')
-rw-r--r-- | pkg/api/handlers/compat/images_build.go | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go new file mode 100644 index 000000000..e208e6ddc --- /dev/null +++ b/pkg/api/handlers/compat/images_build.go @@ -0,0 +1,271 @@ +package compat + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/containers/buildah" + "github.com/containers/buildah/imagebuildah" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/storage/pkg/archive" + "github.com/gorilla/schema" +) + +func BuildImage(w http.ResponseWriter, r *http.Request) { + authConfigs := map[string]handlers.AuthConfig{} + if hdr, found := r.Header["X-Registry-Config"]; found && len(hdr) > 0 { + authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(hdr[0])) + if json.NewDecoder(authConfigsJSON).Decode(&authConfigs) != nil { + utils.BadRequest(w, "X-Registry-Config", hdr[0], json.NewDecoder(authConfigsJSON).Decode(&authConfigs)) + return + } + } + + if hdr, found := r.Header["Content-Type"]; found && len(hdr) > 0 { + if hdr[0] != "application/x-tar" { + utils.BadRequest(w, "Content-Type", hdr[0], + fmt.Errorf("Content-Type: %s is not supported. Should be \"application/x-tar\"", hdr[0])) + } + } + + anchorDir, err := extractTarFile(r, w) + if err != nil { + utils.InternalServerError(w, err) + return + } + defer os.RemoveAll(anchorDir) + + query := struct { + Dockerfile string `schema:"dockerfile"` + Tag string `schema:"t"` + ExtraHosts string `schema:"extrahosts"` + Remote string `schema:"remote"` + Quiet bool `schema:"q"` + NoCache bool `schema:"nocache"` + CacheFrom string `schema:"cachefrom"` + Pull bool `schema:"pull"` + Rm bool `schema:"rm"` + ForceRm bool `schema:"forcerm"` + Memory int64 `schema:"memory"` + MemSwap int64 `schema:"memswap"` + CpuShares uint64 `schema:"cpushares"` + CpuSetCpus string `schema:"cpusetcpus"` + CpuPeriod uint64 `schema:"cpuperiod"` + CpuQuota int64 `schema:"cpuquota"` + BuildArgs string `schema:"buildargs"` + ShmSize int `schema:"shmsize"` + Squash bool `schema:"squash"` + Labels string `schema:"labels"` + NetworkMode string `schema:"networkmode"` + Platform string `schema:"platform"` + Target string `schema:"target"` + Outputs string `schema:"outputs"` + Registry string `schema:"registry"` + }{ + Dockerfile: "Dockerfile", + Tag: "", + ExtraHosts: "", + Remote: "", + Quiet: false, + NoCache: false, + CacheFrom: "", + Pull: false, + 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: "", + Registry: "docker.io", + } + decoder := r.Context().Value("decoder").(*schema.Decoder) + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) + return + } + + var ( + // Tag is the name with optional tag... + name = query.Tag + tag = "latest" + ) + if strings.Contains(query.Tag, ":") { + tokens := strings.SplitN(query.Tag, ":", 2) + name = tokens[0] + tag = tokens[1] + } + + if _, found := r.URL.Query()["target"]; found { + name = query.Target + } + + var buildArgs = map[string]string{} + if _, found := r.URL.Query()["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 _, found := r.URL.Query()["labels"]; found { + 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, k+"="+v) + } + } + + pullPolicy := buildah.PullIfMissing + if _, found := r.URL.Query()["pull"]; found { + if query.Pull { + pullPolicy = buildah.PullAlways + } + } + + // build events will be recorded here + var ( + buildEvents = []string{} + progress = bytes.Buffer{} + ) + + buildOptions := imagebuildah.BuildOptions{ + ContextDirectory: filepath.Join(anchorDir, "build"), + PullPolicy: pullPolicy, + Registry: query.Registry, + IgnoreUnrecognizedInstructions: true, + Quiet: query.Quiet, + Isolation: buildah.IsolationChroot, + Runtime: "", + RuntimeArgs: nil, + TransientMounts: nil, + Compression: archive.Gzip, + Args: buildArgs, + Output: name, + AdditionalTags: []string{tag}, + Log: func(format string, args ...interface{}) { + buildEvents = append(buildEvents, fmt.Sprintf(format, args...)) + }, + In: nil, + Out: &progress, + Err: &progress, + SignaturePolicyPath: "", + ReportWriter: &progress, + OutputFormat: buildah.Dockerv2ImageManifest, + SystemContext: nil, + NamespaceOptions: nil, + ConfigureNetwork: 0, + CNIPluginPath: "", + CNIConfigDir: "", + IDMappingOptions: nil, + AddCapabilities: nil, + DropCapabilities: nil, + CommonBuildOpts: &buildah.CommonBuildOptions{ + AddHost: nil, + CgroupParent: "", + CPUPeriod: query.CpuPeriod, + CPUQuota: query.CpuQuota, + CPUShares: query.CpuShares, + CPUSetCPUs: query.CpuSetCpus, + CPUSetMems: "", + HTTPProxy: false, + Memory: query.Memory, + DNSSearch: nil, + DNSServers: nil, + DNSOptions: nil, + MemorySwap: query.MemSwap, + LabelOpts: nil, + SeccompProfilePath: "", + ApparmorProfile: "", + ShmSize: strconv.Itoa(query.ShmSize), + Ulimit: nil, + Volumes: nil, + }, + 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, + } + + runtime := r.Context().Value("runtime").(*libpod.Runtime) + id, _, err := runtime.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: progress.String() + "\n" + + strings.Join(buildEvents, "\n") + + fmt.Sprintf("\nSuccessfully built %s\n", id), + }) +} + +func extractTarFile(r *http.Request, w http.ResponseWriter) (string, 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() + + // Content-Length not used as too many existing API clients didn't honor it + _, err = io.Copy(tarBall, r.Body) + r.Body.Close() + + if err != nil { + utils.InternalServerError(w, + fmt.Errorf("failed Request: Unable to copy tar file from request body %s", r.RequestURI)) + } + + _, _ = tarBall.Seek(0, 0) + if err := archive.Untar(tarBall, buildDir, &archive.TarOptions{}); err != nil { + return "", err + } + return anchorDir, nil +} |