github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/api/handlers/compat/images_build.go (about) 1 package compat 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "net/http" 11 "os" 12 "path/filepath" 13 "strconv" 14 "strings" 15 16 "github.com/containers/buildah" 17 "github.com/containers/buildah/imagebuildah" 18 "github.com/containers/libpod/libpod" 19 "github.com/containers/libpod/pkg/api/handlers" 20 "github.com/containers/libpod/pkg/api/handlers/utils" 21 "github.com/containers/storage/pkg/archive" 22 "github.com/gorilla/schema" 23 ) 24 25 func BuildImage(w http.ResponseWriter, r *http.Request) { 26 authConfigs := map[string]handlers.AuthConfig{} 27 if hdr, found := r.Header["X-Registry-Config"]; found && len(hdr) > 0 { 28 authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(hdr[0])) 29 if json.NewDecoder(authConfigsJSON).Decode(&authConfigs) != nil { 30 utils.BadRequest(w, "X-Registry-Config", hdr[0], json.NewDecoder(authConfigsJSON).Decode(&authConfigs)) 31 return 32 } 33 } 34 35 if hdr, found := r.Header["Content-Type"]; found && len(hdr) > 0 { 36 if hdr[0] != "application/x-tar" { 37 utils.BadRequest(w, "Content-Type", hdr[0], 38 fmt.Errorf("Content-Type: %s is not supported. Should be \"application/x-tar\"", hdr[0])) 39 } 40 } 41 42 anchorDir, err := extractTarFile(r, w) 43 if err != nil { 44 utils.InternalServerError(w, err) 45 return 46 } 47 defer os.RemoveAll(anchorDir) 48 49 query := struct { 50 Dockerfile string `schema:"dockerfile"` 51 Tag string `schema:"t"` 52 ExtraHosts string `schema:"extrahosts"` 53 Remote string `schema:"remote"` 54 Quiet bool `schema:"q"` 55 NoCache bool `schema:"nocache"` 56 CacheFrom string `schema:"cachefrom"` 57 Pull bool `schema:"pull"` 58 Rm bool `schema:"rm"` 59 ForceRm bool `schema:"forcerm"` 60 Memory int64 `schema:"memory"` 61 MemSwap int64 `schema:"memswap"` 62 CpuShares uint64 `schema:"cpushares"` 63 CpuSetCpus string `schema:"cpusetcpus"` 64 CpuPeriod uint64 `schema:"cpuperiod"` 65 CpuQuota int64 `schema:"cpuquota"` 66 BuildArgs string `schema:"buildargs"` 67 ShmSize int `schema:"shmsize"` 68 Squash bool `schema:"squash"` 69 Labels string `schema:"labels"` 70 NetworkMode string `schema:"networkmode"` 71 Platform string `schema:"platform"` 72 Target string `schema:"target"` 73 Outputs string `schema:"outputs"` 74 Registry string `schema:"registry"` 75 }{ 76 Dockerfile: "Dockerfile", 77 Tag: "", 78 ExtraHosts: "", 79 Remote: "", 80 Quiet: false, 81 NoCache: false, 82 CacheFrom: "", 83 Pull: false, 84 Rm: true, 85 ForceRm: false, 86 Memory: 0, 87 MemSwap: 0, 88 CpuShares: 0, 89 CpuSetCpus: "", 90 CpuPeriod: 0, 91 CpuQuota: 0, 92 BuildArgs: "", 93 ShmSize: 64 * 1024 * 1024, 94 Squash: false, 95 Labels: "", 96 NetworkMode: "", 97 Platform: "", 98 Target: "", 99 Outputs: "", 100 Registry: "docker.io", 101 } 102 decoder := r.Context().Value("decoder").(*schema.Decoder) 103 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 104 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) 105 return 106 } 107 108 var ( 109 // Tag is the name with optional tag... 110 name = query.Tag 111 tag = "latest" 112 ) 113 if strings.Contains(query.Tag, ":") { 114 tokens := strings.SplitN(query.Tag, ":", 2) 115 name = tokens[0] 116 tag = tokens[1] 117 } 118 119 if _, found := r.URL.Query()["target"]; found { 120 name = query.Target 121 } 122 123 var buildArgs = map[string]string{} 124 if _, found := r.URL.Query()["buildargs"]; found { 125 if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil { 126 utils.BadRequest(w, "buildargs", query.BuildArgs, err) 127 return 128 } 129 } 130 131 // convert label formats 132 var labels = []string{} 133 if _, found := r.URL.Query()["labels"]; found { 134 var m = map[string]string{} 135 if err := json.Unmarshal([]byte(query.Labels), &m); err != nil { 136 utils.BadRequest(w, "labels", query.Labels, err) 137 return 138 } 139 140 for k, v := range m { 141 labels = append(labels, k+"="+v) 142 } 143 } 144 145 pullPolicy := buildah.PullIfMissing 146 if _, found := r.URL.Query()["pull"]; found { 147 if query.Pull { 148 pullPolicy = buildah.PullAlways 149 } 150 } 151 152 // build events will be recorded here 153 var ( 154 buildEvents = []string{} 155 progress = bytes.Buffer{} 156 ) 157 158 buildOptions := imagebuildah.BuildOptions{ 159 ContextDirectory: filepath.Join(anchorDir, "build"), 160 PullPolicy: pullPolicy, 161 Registry: query.Registry, 162 IgnoreUnrecognizedInstructions: true, 163 Quiet: query.Quiet, 164 Isolation: buildah.IsolationChroot, 165 Runtime: "", 166 RuntimeArgs: nil, 167 TransientMounts: nil, 168 Compression: archive.Gzip, 169 Args: buildArgs, 170 Output: name, 171 AdditionalTags: []string{tag}, 172 Log: func(format string, args ...interface{}) { 173 buildEvents = append(buildEvents, fmt.Sprintf(format, args...)) 174 }, 175 In: nil, 176 Out: &progress, 177 Err: &progress, 178 SignaturePolicyPath: "", 179 ReportWriter: &progress, 180 OutputFormat: buildah.Dockerv2ImageManifest, 181 SystemContext: nil, 182 NamespaceOptions: nil, 183 ConfigureNetwork: 0, 184 CNIPluginPath: "", 185 CNIConfigDir: "", 186 IDMappingOptions: nil, 187 AddCapabilities: nil, 188 DropCapabilities: nil, 189 CommonBuildOpts: &buildah.CommonBuildOptions{ 190 AddHost: nil, 191 CgroupParent: "", 192 CPUPeriod: query.CpuPeriod, 193 CPUQuota: query.CpuQuota, 194 CPUShares: query.CpuShares, 195 CPUSetCPUs: query.CpuSetCpus, 196 CPUSetMems: "", 197 HTTPProxy: false, 198 Memory: query.Memory, 199 DNSSearch: nil, 200 DNSServers: nil, 201 DNSOptions: nil, 202 MemorySwap: query.MemSwap, 203 LabelOpts: nil, 204 SeccompProfilePath: "", 205 ApparmorProfile: "", 206 ShmSize: strconv.Itoa(query.ShmSize), 207 Ulimit: nil, 208 Volumes: nil, 209 }, 210 DefaultMountsFilePath: "", 211 IIDFile: "", 212 Squash: query.Squash, 213 Labels: labels, 214 Annotations: nil, 215 OnBuild: nil, 216 Layers: false, 217 NoCache: query.NoCache, 218 RemoveIntermediateCtrs: query.Rm, 219 ForceRmIntermediateCtrs: query.ForceRm, 220 BlobDirectory: "", 221 Target: query.Target, 222 Devices: nil, 223 } 224 225 runtime := r.Context().Value("runtime").(*libpod.Runtime) 226 id, _, err := runtime.Build(r.Context(), buildOptions, query.Dockerfile) 227 if err != nil { 228 utils.InternalServerError(w, err) 229 } 230 231 // Find image ID that was built... 232 utils.WriteResponse(w, http.StatusOK, 233 struct { 234 Stream string `json:"stream"` 235 }{ 236 Stream: progress.String() + "\n" + 237 strings.Join(buildEvents, "\n") + 238 fmt.Sprintf("\nSuccessfully built %s\n", id), 239 }) 240 } 241 242 func extractTarFile(r *http.Request, w http.ResponseWriter) (string, error) { 243 // build a home for the request body 244 anchorDir, err := ioutil.TempDir("", "libpod_builder") 245 if err != nil { 246 return "", err 247 } 248 buildDir := filepath.Join(anchorDir, "build") 249 250 path := filepath.Join(anchorDir, "tarBall") 251 tarBall, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 252 if err != nil { 253 return "", err 254 } 255 defer tarBall.Close() 256 257 // Content-Length not used as too many existing API clients didn't honor it 258 _, err = io.Copy(tarBall, r.Body) 259 r.Body.Close() 260 261 if err != nil { 262 utils.InternalServerError(w, 263 fmt.Errorf("failed Request: Unable to copy tar file from request body %s", r.RequestURI)) 264 } 265 266 _, _ = tarBall.Seek(0, 0) 267 if err := archive.Untar(tarBall, buildDir, &archive.TarOptions{}); err != nil { 268 return "", err 269 } 270 return anchorDir, nil 271 }