1package build // import "github.com/docker/docker/api/server/router/build"
2
3import (
4	"bufio"
5	"bytes"
6	"context"
7	"encoding/base64"
8	"encoding/json"
9	"fmt"
10	"io"
11	"net/http"
12	"runtime"
13	"strconv"
14	"strings"
15	"sync"
16
17	"github.com/docker/docker/api/server/httputils"
18	"github.com/docker/docker/api/types"
19	"github.com/docker/docker/api/types/backend"
20	"github.com/docker/docker/api/types/container"
21	"github.com/docker/docker/api/types/versions"
22	"github.com/docker/docker/errdefs"
23	"github.com/docker/docker/pkg/ioutils"
24	"github.com/docker/docker/pkg/progress"
25	"github.com/docker/docker/pkg/streamformatter"
26	"github.com/docker/docker/pkg/system"
27	"github.com/docker/go-units"
28	"github.com/pkg/errors"
29	"github.com/sirupsen/logrus"
30)
31
32type invalidIsolationError string
33
34func (e invalidIsolationError) Error() string {
35	return fmt.Sprintf("Unsupported isolation: %q", string(e))
36}
37
38func (e invalidIsolationError) InvalidParameter() {}
39
40func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBuildOptions, error) {
41	version := httputils.VersionFromContext(ctx)
42	options := &types.ImageBuildOptions{}
43	if httputils.BoolValue(r, "forcerm") && versions.GreaterThanOrEqualTo(version, "1.12") {
44		options.Remove = true
45	} else if r.FormValue("rm") == "" && versions.GreaterThanOrEqualTo(version, "1.12") {
46		options.Remove = true
47	} else {
48		options.Remove = httputils.BoolValue(r, "rm")
49	}
50	if httputils.BoolValue(r, "pull") && versions.GreaterThanOrEqualTo(version, "1.16") {
51		options.PullParent = true
52	}
53
54	options.Dockerfile = r.FormValue("dockerfile")
55	options.SuppressOutput = httputils.BoolValue(r, "q")
56	options.NoCache = httputils.BoolValue(r, "nocache")
57	options.ForceRemove = httputils.BoolValue(r, "forcerm")
58	options.MemorySwap = httputils.Int64ValueOrZero(r, "memswap")
59	options.Memory = httputils.Int64ValueOrZero(r, "memory")
60	options.CPUShares = httputils.Int64ValueOrZero(r, "cpushares")
61	options.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod")
62	options.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota")
63	options.CPUSetCPUs = r.FormValue("cpusetcpus")
64	options.CPUSetMems = r.FormValue("cpusetmems")
65	options.CgroupParent = r.FormValue("cgroupparent")
66	options.NetworkMode = r.FormValue("networkmode")
67	options.Tags = r.Form["t"]
68	options.ExtraHosts = r.Form["extrahosts"]
69	options.SecurityOpt = r.Form["securityopt"]
70	options.Squash = httputils.BoolValue(r, "squash")
71	options.Target = r.FormValue("target")
72	options.RemoteContext = r.FormValue("remote")
73	if versions.GreaterThanOrEqualTo(version, "1.32") {
74		apiPlatform := r.FormValue("platform")
75		p := system.ParsePlatform(apiPlatform)
76		if err := system.ValidatePlatform(p); err != nil {
77			return nil, errdefs.InvalidParameter(errors.Errorf("invalid platform: %s", err))
78		}
79		options.Platform = p.OS
80	}
81
82	if r.Form.Get("shmsize") != "" {
83		shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)
84		if err != nil {
85			return nil, err
86		}
87		options.ShmSize = shmSize
88	}
89
90	if i := container.Isolation(r.FormValue("isolation")); i != "" {
91		if !container.Isolation.IsValid(i) {
92			return nil, invalidIsolationError(i)
93		}
94		options.Isolation = i
95	}
96
97	if runtime.GOOS != "windows" && options.SecurityOpt != nil {
98		return nil, errdefs.InvalidParameter(errors.New("The daemon on this platform does not support setting security options on build"))
99	}
100
101	var buildUlimits = []*units.Ulimit{}
102	ulimitsJSON := r.FormValue("ulimits")
103	if ulimitsJSON != "" {
104		if err := json.Unmarshal([]byte(ulimitsJSON), &buildUlimits); err != nil {
105			return nil, errors.Wrap(errdefs.InvalidParameter(err), "error reading ulimit settings")
106		}
107		options.Ulimits = buildUlimits
108	}
109
110	// Note that there are two ways a --build-arg might appear in the
111	// json of the query param:
112	//     "foo":"bar"
113	// and "foo":nil
114	// The first is the normal case, ie. --build-arg foo=bar
115	// or  --build-arg foo
116	// where foo's value was picked up from an env var.
117	// The second ("foo":nil) is where they put --build-arg foo
118	// but "foo" isn't set as an env var. In that case we can't just drop
119	// the fact they mentioned it, we need to pass that along to the builder
120	// so that it can print a warning about "foo" being unused if there is
121	// no "ARG foo" in the Dockerfile.
122	buildArgsJSON := r.FormValue("buildargs")
123	if buildArgsJSON != "" {
124		var buildArgs = map[string]*string{}
125		if err := json.Unmarshal([]byte(buildArgsJSON), &buildArgs); err != nil {
126			return nil, errors.Wrap(errdefs.InvalidParameter(err), "error reading build args")
127		}
128		options.BuildArgs = buildArgs
129	}
130
131	labelsJSON := r.FormValue("labels")
132	if labelsJSON != "" {
133		var labels = map[string]string{}
134		if err := json.Unmarshal([]byte(labelsJSON), &labels); err != nil {
135			return nil, errors.Wrap(errdefs.InvalidParameter(err), "error reading labels")
136		}
137		options.Labels = labels
138	}
139
140	cacheFromJSON := r.FormValue("cachefrom")
141	if cacheFromJSON != "" {
142		var cacheFrom = []string{}
143		if err := json.Unmarshal([]byte(cacheFromJSON), &cacheFrom); err != nil {
144			return nil, err
145		}
146		options.CacheFrom = cacheFrom
147	}
148	options.SessionID = r.FormValue("session")
149	options.BuildID = r.FormValue("buildid")
150	builderVersion, err := parseVersion(r.FormValue("version"))
151	if err != nil {
152		return nil, err
153	}
154	options.Version = builderVersion
155
156	return options, nil
157}
158
159func parseVersion(s string) (types.BuilderVersion, error) {
160	if s == "" || s == string(types.BuilderV1) {
161		return types.BuilderV1, nil
162	}
163	if s == string(types.BuilderBuildKit) {
164		return types.BuilderBuildKit, nil
165	}
166	return "", errors.Errorf("invalid version %s", s)
167}
168
169func (br *buildRouter) postPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
170	report, err := br.backend.PruneCache(ctx)
171	if err != nil {
172		return err
173	}
174	return httputils.WriteJSON(w, http.StatusOK, report)
175}
176
177func (br *buildRouter) postCancel(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
178	w.Header().Set("Content-Type", "application/json")
179
180	id := r.FormValue("id")
181	if id == "" {
182		return errors.Errorf("build ID not provided")
183	}
184
185	return br.backend.Cancel(ctx, id)
186}
187
188func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
189	var (
190		notVerboseBuffer = bytes.NewBuffer(nil)
191		version          = httputils.VersionFromContext(ctx)
192	)
193
194	w.Header().Set("Content-Type", "application/json")
195
196	body := r.Body
197	var ww io.Writer = w
198	if body != nil {
199		// there is a possibility that output is written before request body
200		// has been fully read so we need to protect against it.
201		// this can be removed when
202		// https://github.com/golang/go/issues/15527
203		// https://github.com/golang/go/issues/22209
204		// has been fixed
205		body, ww = wrapOutputBufferedUntilRequestRead(body, ww)
206	}
207
208	output := ioutils.NewWriteFlusher(ww)
209	defer output.Close()
210
211	errf := func(err error) error {
212
213		if httputils.BoolValue(r, "q") && notVerboseBuffer.Len() > 0 {
214			output.Write(notVerboseBuffer.Bytes())
215		}
216
217		logrus.Debugf("isflushed %v", output.Flushed())
218		// Do not write the error in the http output if it's still empty.
219		// This prevents from writing a 200(OK) when there is an internal error.
220		if !output.Flushed() {
221			return err
222		}
223		_, err = output.Write(streamformatter.FormatError(err))
224		if err != nil {
225			logrus.Warnf("could not write error response: %v", err)
226		}
227		return nil
228	}
229
230	buildOptions, err := newImageBuildOptions(ctx, r)
231	if err != nil {
232		return errf(err)
233	}
234	buildOptions.AuthConfigs = getAuthConfigs(r.Header)
235
236	if buildOptions.Squash && !br.daemon.HasExperimental() {
237		return errdefs.InvalidParameter(errors.New("squash is only supported with experimental mode"))
238	}
239
240	out := io.Writer(output)
241	if buildOptions.SuppressOutput {
242		out = notVerboseBuffer
243	}
244
245	// Currently, only used if context is from a remote url.
246	// Look at code in DetectContextFromRemoteURL for more information.
247	createProgressReader := func(in io.ReadCloser) io.ReadCloser {
248		progressOutput := streamformatter.NewJSONProgressOutput(out, true)
249		return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", buildOptions.RemoteContext)
250	}
251
252	if buildOptions.Version == types.BuilderBuildKit && !br.daemon.HasExperimental() {
253		return errdefs.InvalidParameter(errors.New("buildkit is only supported with experimental mode"))
254	}
255
256	wantAux := versions.GreaterThanOrEqualTo(version, "1.30")
257
258	imgID, err := br.backend.Build(ctx, backend.BuildConfig{
259		Source:         body,
260		Options:        buildOptions,
261		ProgressWriter: buildProgressWriter(out, wantAux, createProgressReader),
262	})
263	if err != nil {
264		return errf(err)
265	}
266
267	// Everything worked so if -q was provided the output from the daemon
268	// should be just the image ID and we'll print that to stdout.
269	if buildOptions.SuppressOutput {
270		fmt.Fprintln(streamformatter.NewStdoutWriter(output), imgID)
271	}
272	return nil
273}
274
275func getAuthConfigs(header http.Header) map[string]types.AuthConfig {
276	authConfigs := map[string]types.AuthConfig{}
277	authConfigsEncoded := header.Get("X-Registry-Config")
278
279	if authConfigsEncoded == "" {
280		return authConfigs
281	}
282
283	authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
284	// Pulling an image does not error when no auth is provided so to remain
285	// consistent with the existing api decode errors are ignored
286	json.NewDecoder(authConfigsJSON).Decode(&authConfigs)
287	return authConfigs
288}
289
290type syncWriter struct {
291	w  io.Writer
292	mu sync.Mutex
293}
294
295func (s *syncWriter) Write(b []byte) (count int, err error) {
296	s.mu.Lock()
297	count, err = s.w.Write(b)
298	s.mu.Unlock()
299	return
300}
301
302func buildProgressWriter(out io.Writer, wantAux bool, createProgressReader func(io.ReadCloser) io.ReadCloser) backend.ProgressWriter {
303	out = &syncWriter{w: out}
304
305	var aux *streamformatter.AuxFormatter
306	if wantAux {
307		aux = &streamformatter.AuxFormatter{Writer: out}
308	}
309
310	return backend.ProgressWriter{
311		Output:             out,
312		StdoutFormatter:    streamformatter.NewStdoutWriter(out),
313		StderrFormatter:    streamformatter.NewStderrWriter(out),
314		AuxFormatter:       aux,
315		ProgressReaderFunc: createProgressReader,
316	}
317}
318
319type flusher interface {
320	Flush()
321}
322
323func wrapOutputBufferedUntilRequestRead(rc io.ReadCloser, out io.Writer) (io.ReadCloser, io.Writer) {
324	var fl flusher = &ioutils.NopFlusher{}
325	if f, ok := out.(flusher); ok {
326		fl = f
327	}
328
329	w := &wcf{
330		buf:     bytes.NewBuffer(nil),
331		Writer:  out,
332		flusher: fl,
333	}
334	r := bufio.NewReader(rc)
335	_, err := r.Peek(1)
336	if err != nil {
337		return rc, out
338	}
339	rc = &rcNotifier{
340		Reader: r,
341		Closer: rc,
342		notify: w.notify,
343	}
344	return rc, w
345}
346
347type rcNotifier struct {
348	io.Reader
349	io.Closer
350	notify func()
351}
352
353func (r *rcNotifier) Read(b []byte) (int, error) {
354	n, err := r.Reader.Read(b)
355	if err != nil {
356		r.notify()
357	}
358	return n, err
359}
360
361func (r *rcNotifier) Close() error {
362	r.notify()
363	return r.Closer.Close()
364}
365
366type wcf struct {
367	io.Writer
368	flusher
369	mu      sync.Mutex
370	ready   bool
371	buf     *bytes.Buffer
372	flushed bool
373}
374
375func (w *wcf) Flush() {
376	w.mu.Lock()
377	w.flushed = true
378	if !w.ready {
379		w.mu.Unlock()
380		return
381	}
382	w.mu.Unlock()
383	w.flusher.Flush()
384}
385
386func (w *wcf) Flushed() bool {
387	w.mu.Lock()
388	b := w.flushed
389	w.mu.Unlock()
390	return b
391}
392
393func (w *wcf) Write(b []byte) (int, error) {
394	w.mu.Lock()
395	if !w.ready {
396		n, err := w.buf.Write(b)
397		w.mu.Unlock()
398		return n, err
399	}
400	w.mu.Unlock()
401	return w.Writer.Write(b)
402}
403
404func (w *wcf) notify() {
405	w.mu.Lock()
406	if !w.ready {
407		if w.buf.Len() > 0 {
408			io.Copy(w.Writer, w.buf)
409		}
410		if w.flushed {
411			w.flusher.Flush()
412		}
413		w.ready = true
414	}
415	w.mu.Unlock()
416}
417