github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/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  	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  
   154  func 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  
   164  func (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  
   194  func (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  
   205  func (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  
   287  func 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  
   302  type syncWriter struct {
   303  	w  io.Writer
   304  	mu sync.Mutex
   305  }
   306  
   307  func (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  
   314  func 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  
   331  type flusher interface {
   332  	Flush()
   333  }
   334  
   335  func 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  
   359  type rcNotifier struct {
   360  	io.Reader
   361  	io.Closer
   362  	notify func()
   363  }
   364  
   365  func (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  
   373  func (r *rcNotifier) Close() error {
   374  	r.notify()
   375  	return r.Closer.Close()
   376  }
   377  
   378  type wcf struct {
   379  	io.Writer
   380  	flusher
   381  	mu      sync.Mutex
   382  	ready   bool
   383  	buf     *bytes.Buffer
   384  	flushed bool
   385  }
   386  
   387  func (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  
   398  func (w *wcf) Flushed() bool {
   399  	w.mu.Lock()
   400  	b := w.flushed
   401  	w.mu.Unlock()
   402  	return b
   403  }
   404  
   405  func (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  
   416  func (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  }