github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/api/server/router/build/build_routes.go (about)

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