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