github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/api/server/router/build/build_routes.go (about)

     1  package build
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"runtime"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  
    15  	"github.com/Sirupsen/logrus"
    16  	apierrors "github.com/docker/docker/api/errors"
    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/pkg/ioutils"
    23  	"github.com/docker/docker/pkg/progress"
    24  	"github.com/docker/docker/pkg/streamformatter"
    25  	units "github.com/docker/go-units"
    26  	"github.com/pkg/errors"
    27  	"golang.org/x/net/context"
    28  )
    29  
    30  func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBuildOptions, error) {
    31  	version := httputils.VersionFromContext(ctx)
    32  	options := &types.ImageBuildOptions{}
    33  	if httputils.BoolValue(r, "forcerm") && versions.GreaterThanOrEqualTo(version, "1.12") {
    34  		options.Remove = true
    35  	} else if r.FormValue("rm") == "" && versions.GreaterThanOrEqualTo(version, "1.12") {
    36  		options.Remove = true
    37  	} else {
    38  		options.Remove = httputils.BoolValue(r, "rm")
    39  	}
    40  	if httputils.BoolValue(r, "pull") && versions.GreaterThanOrEqualTo(version, "1.16") {
    41  		options.PullParent = true
    42  	}
    43  
    44  	options.Dockerfile = r.FormValue("dockerfile")
    45  	options.SuppressOutput = httputils.BoolValue(r, "q")
    46  	options.NoCache = httputils.BoolValue(r, "nocache")
    47  	options.ForceRemove = httputils.BoolValue(r, "forcerm")
    48  	options.MemorySwap = httputils.Int64ValueOrZero(r, "memswap")
    49  	options.Memory = httputils.Int64ValueOrZero(r, "memory")
    50  	options.CPUShares = httputils.Int64ValueOrZero(r, "cpushares")
    51  	options.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod")
    52  	options.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota")
    53  	options.CPUSetCPUs = r.FormValue("cpusetcpus")
    54  	options.CPUSetMems = r.FormValue("cpusetmems")
    55  	options.CgroupParent = r.FormValue("cgroupparent")
    56  	options.NetworkMode = r.FormValue("networkmode")
    57  	options.Tags = r.Form["t"]
    58  	options.ExtraHosts = r.Form["extrahosts"]
    59  	options.SecurityOpt = r.Form["securityopt"]
    60  	options.Squash = httputils.BoolValue(r, "squash")
    61  	options.Target = r.FormValue("target")
    62  	options.RemoteContext = r.FormValue("remote")
    63  
    64  	if r.Form.Get("shmsize") != "" {
    65  		shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)
    66  		if err != nil {
    67  			return nil, err
    68  		}
    69  		options.ShmSize = shmSize
    70  	}
    71  
    72  	if i := container.Isolation(r.FormValue("isolation")); i != "" {
    73  		if !container.Isolation.IsValid(i) {
    74  			return nil, fmt.Errorf("Unsupported isolation: %q", i)
    75  		}
    76  		options.Isolation = i
    77  	}
    78  
    79  	if runtime.GOOS != "windows" && options.SecurityOpt != nil {
    80  		return nil, fmt.Errorf("The daemon on this platform does not support setting security options on build")
    81  	}
    82  
    83  	var buildUlimits = []*units.Ulimit{}
    84  	ulimitsJSON := r.FormValue("ulimits")
    85  	if ulimitsJSON != "" {
    86  		if err := json.Unmarshal([]byte(ulimitsJSON), &buildUlimits); err != nil {
    87  			return nil, err
    88  		}
    89  		options.Ulimits = buildUlimits
    90  	}
    91  
    92  	// Note that there are two ways a --build-arg might appear in the
    93  	// json of the query param:
    94  	//     "foo":"bar"
    95  	// and "foo":nil
    96  	// The first is the normal case, ie. --build-arg foo=bar
    97  	// or  --build-arg foo
    98  	// where foo's value was picked up from an env var.
    99  	// The second ("foo":nil) is where they put --build-arg foo
   100  	// but "foo" isn't set as an env var. In that case we can't just drop
   101  	// the fact they mentioned it, we need to pass that along to the builder
   102  	// so that it can print a warning about "foo" being unused if there is
   103  	// no "ARG foo" in the Dockerfile.
   104  	buildArgsJSON := r.FormValue("buildargs")
   105  	if buildArgsJSON != "" {
   106  		var buildArgs = map[string]*string{}
   107  		if err := json.Unmarshal([]byte(buildArgsJSON), &buildArgs); err != nil {
   108  			return nil, err
   109  		}
   110  		options.BuildArgs = buildArgs
   111  	}
   112  
   113  	labelsJSON := r.FormValue("labels")
   114  	if labelsJSON != "" {
   115  		var labels = map[string]string{}
   116  		if err := json.Unmarshal([]byte(labelsJSON), &labels); err != nil {
   117  			return nil, err
   118  		}
   119  		options.Labels = labels
   120  	}
   121  
   122  	cacheFromJSON := r.FormValue("cachefrom")
   123  	if cacheFromJSON != "" {
   124  		var cacheFrom = []string{}
   125  		if err := json.Unmarshal([]byte(cacheFromJSON), &cacheFrom); err != nil {
   126  			return nil, err
   127  		}
   128  		options.CacheFrom = cacheFrom
   129  	}
   130  
   131  	return options, nil
   132  }
   133  
   134  func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   135  	var (
   136  		notVerboseBuffer = bytes.NewBuffer(nil)
   137  		version          = httputils.VersionFromContext(ctx)
   138  	)
   139  
   140  	w.Header().Set("Content-Type", "application/json")
   141  
   142  	output := ioutils.NewWriteFlusher(w)
   143  	defer output.Close()
   144  	errf := func(err error) error {
   145  		if httputils.BoolValue(r, "q") && notVerboseBuffer.Len() > 0 {
   146  			output.Write(notVerboseBuffer.Bytes())
   147  		}
   148  		// Do not write the error in the http output if it's still empty.
   149  		// This prevents from writing a 200(OK) when there is an internal error.
   150  		if !output.Flushed() {
   151  			return err
   152  		}
   153  		_, err = w.Write(streamformatter.FormatError(err))
   154  		if err != nil {
   155  			logrus.Warnf("could not write error response: %v", err)
   156  		}
   157  		return nil
   158  	}
   159  
   160  	buildOptions, err := newImageBuildOptions(ctx, r)
   161  	if err != nil {
   162  		return errf(err)
   163  	}
   164  	buildOptions.AuthConfigs = getAuthConfigs(r.Header)
   165  
   166  	if buildOptions.Squash && !br.daemon.HasExperimental() {
   167  		return apierrors.NewBadRequestError(
   168  			errors.New("squash is only supported with experimental mode"))
   169  	}
   170  
   171  	out := io.Writer(output)
   172  	if buildOptions.SuppressOutput {
   173  		out = notVerboseBuffer
   174  	}
   175  
   176  	// Currently, only used if context is from a remote url.
   177  	// Look at code in DetectContextFromRemoteURL for more information.
   178  	createProgressReader := func(in io.ReadCloser) io.ReadCloser {
   179  		progressOutput := streamformatter.NewJSONProgressOutput(out, true)
   180  		return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", buildOptions.RemoteContext)
   181  	}
   182  
   183  	wantAux := versions.GreaterThanOrEqualTo(version, "1.30")
   184  
   185  	imgID, err := br.backend.Build(ctx, backend.BuildConfig{
   186  		Source:         r.Body,
   187  		Options:        buildOptions,
   188  		ProgressWriter: buildProgressWriter(out, wantAux, createProgressReader),
   189  	})
   190  	if err != nil {
   191  		return errf(err)
   192  	}
   193  
   194  	// Everything worked so if -q was provided the output from the daemon
   195  	// should be just the image ID and we'll print that to stdout.
   196  	if buildOptions.SuppressOutput {
   197  		fmt.Fprintln(streamformatter.NewStdoutWriter(output), imgID)
   198  	}
   199  	return nil
   200  }
   201  
   202  func getAuthConfigs(header http.Header) map[string]types.AuthConfig {
   203  	authConfigs := map[string]types.AuthConfig{}
   204  	authConfigsEncoded := header.Get("X-Registry-Config")
   205  
   206  	if authConfigsEncoded == "" {
   207  		return authConfigs
   208  	}
   209  
   210  	authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
   211  	// Pulling an image does not error when no auth is provided so to remain
   212  	// consistent with the existing api decode errors are ignored
   213  	json.NewDecoder(authConfigsJSON).Decode(&authConfigs)
   214  	return authConfigs
   215  }
   216  
   217  type syncWriter struct {
   218  	w  io.Writer
   219  	mu sync.Mutex
   220  }
   221  
   222  func (s *syncWriter) Write(b []byte) (count int, err error) {
   223  	s.mu.Lock()
   224  	count, err = s.w.Write(b)
   225  	s.mu.Unlock()
   226  	return
   227  }
   228  
   229  func buildProgressWriter(out io.Writer, wantAux bool, createProgressReader func(io.ReadCloser) io.ReadCloser) backend.ProgressWriter {
   230  	out = &syncWriter{w: out}
   231  
   232  	var aux *streamformatter.AuxFormatter
   233  	if wantAux {
   234  		aux = &streamformatter.AuxFormatter{Writer: out}
   235  	}
   236  
   237  	return backend.ProgressWriter{
   238  		Output:             out,
   239  		StdoutFormatter:    streamformatter.NewStdoutWriter(out),
   240  		StderrFormatter:    streamformatter.NewStderrWriter(out),
   241  		AuxFormatter:       aux,
   242  		ProgressReaderFunc: createProgressReader,
   243  	}
   244  }