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