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