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