github.com/ncdc/docker@v0.10.1-0.20160129113957-6c6729ef5b74/api/server/router/build/build_routes.go (about)

     1  package build
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"strconv"
    12  	"strings"
    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/builder/dockerfile"
    18  	"github.com/docker/docker/daemon/daemonbuilder"
    19  	"github.com/docker/docker/pkg/ioutils"
    20  	"github.com/docker/docker/pkg/progress"
    21  	"github.com/docker/docker/pkg/streamformatter"
    22  	"github.com/docker/docker/reference"
    23  	"github.com/docker/docker/utils"
    24  	"github.com/docker/engine-api/types"
    25  	"github.com/docker/engine-api/types/container"
    26  	"github.com/docker/go-units"
    27  	"golang.org/x/net/context"
    28  )
    29  
    30  // sanitizeRepoAndTags parses the raw "t" parameter received from the client
    31  // to a slice of repoAndTag.
    32  // It also validates each repoName and tag.
    33  func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
    34  	var (
    35  		repoAndTags []reference.Named
    36  		// This map is used for deduplicating the "-t" parameter.
    37  		uniqNames = make(map[string]struct{})
    38  	)
    39  	for _, repo := range names {
    40  		if repo == "" {
    41  			continue
    42  		}
    43  
    44  		ref, err := reference.ParseNamed(repo)
    45  		if err != nil {
    46  			return nil, err
    47  		}
    48  
    49  		ref = reference.WithDefaultTag(ref)
    50  
    51  		if _, isCanonical := ref.(reference.Canonical); isCanonical {
    52  			return nil, errors.New("build tag cannot contain a digest")
    53  		}
    54  
    55  		if _, isTagged := ref.(reference.NamedTagged); !isTagged {
    56  			ref, err = reference.WithTag(ref, reference.DefaultTag)
    57  		}
    58  
    59  		nameWithTag := ref.String()
    60  
    61  		if _, exists := uniqNames[nameWithTag]; !exists {
    62  			uniqNames[nameWithTag] = struct{}{}
    63  			repoAndTags = append(repoAndTags, ref)
    64  		}
    65  	}
    66  	return repoAndTags, nil
    67  }
    68  
    69  func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBuildOptions, error) {
    70  	version := httputils.VersionFromContext(ctx)
    71  	options := &types.ImageBuildOptions{}
    72  	if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
    73  		options.Remove = true
    74  	} else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
    75  		options.Remove = true
    76  	} else {
    77  		options.Remove = httputils.BoolValue(r, "rm")
    78  	}
    79  	if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
    80  		options.PullParent = true
    81  	}
    82  
    83  	options.Dockerfile = r.FormValue("dockerfile")
    84  	options.SuppressOutput = httputils.BoolValue(r, "q")
    85  	options.NoCache = httputils.BoolValue(r, "nocache")
    86  	options.ForceRemove = httputils.BoolValue(r, "forcerm")
    87  	options.MemorySwap = httputils.Int64ValueOrZero(r, "memswap")
    88  	options.Memory = httputils.Int64ValueOrZero(r, "memory")
    89  	options.CPUShares = httputils.Int64ValueOrZero(r, "cpushares")
    90  	options.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod")
    91  	options.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota")
    92  	options.CPUSetCPUs = r.FormValue("cpusetcpus")
    93  	options.CPUSetMems = r.FormValue("cpusetmems")
    94  	options.CgroupParent = r.FormValue("cgroupparent")
    95  
    96  	if r.Form.Get("shmsize") != "" {
    97  		shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)
    98  		if err != nil {
    99  			return nil, err
   100  		}
   101  		options.ShmSize = shmSize
   102  	}
   103  
   104  	if i := container.IsolationLevel(r.FormValue("isolation")); i != "" {
   105  		if !container.IsolationLevel.IsValid(i) {
   106  			return nil, fmt.Errorf("Unsupported isolation: %q", i)
   107  		}
   108  		options.IsolationLevel = i
   109  	}
   110  
   111  	var buildUlimits = []*units.Ulimit{}
   112  	ulimitsJSON := r.FormValue("ulimits")
   113  	if ulimitsJSON != "" {
   114  		if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
   115  			return nil, err
   116  		}
   117  		options.Ulimits = buildUlimits
   118  	}
   119  
   120  	var buildArgs = map[string]string{}
   121  	buildArgsJSON := r.FormValue("buildargs")
   122  	if buildArgsJSON != "" {
   123  		if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
   124  			return nil, err
   125  		}
   126  		options.BuildArgs = buildArgs
   127  	}
   128  	return options, nil
   129  }
   130  
   131  func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   132  	var (
   133  		authConfigs        = map[string]types.AuthConfig{}
   134  		authConfigsEncoded = r.Header.Get("X-Registry-Config")
   135  		notVerboseBuffer   = bytes.NewBuffer(nil)
   136  	)
   137  
   138  	if authConfigsEncoded != "" {
   139  		authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
   140  		if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil {
   141  			// for a pull it is not an error if no auth was given
   142  			// to increase compatibility with the existing api it is defaulting
   143  			// to be empty.
   144  		}
   145  	}
   146  
   147  	w.Header().Set("Content-Type", "application/json")
   148  
   149  	output := ioutils.NewWriteFlusher(w)
   150  	defer output.Close()
   151  	sf := streamformatter.NewJSONStreamFormatter()
   152  	errf := func(err error) error {
   153  		if httputils.BoolValue(r, "q") && notVerboseBuffer.Len() > 0 {
   154  			output.Write(notVerboseBuffer.Bytes())
   155  		}
   156  		// Do not write the error in the http output if it's still empty.
   157  		// This prevents from writing a 200(OK) when there is an internal error.
   158  		if !output.Flushed() {
   159  			return err
   160  		}
   161  		_, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
   162  		if err != nil {
   163  			logrus.Warnf("could not write error response: %v", err)
   164  		}
   165  		return nil
   166  	}
   167  
   168  	buildOptions, err := newImageBuildOptions(ctx, r)
   169  	if err != nil {
   170  		return errf(err)
   171  	}
   172  
   173  	repoAndTags, err := sanitizeRepoAndTags(r.Form["t"])
   174  	if err != nil {
   175  		return errf(err)
   176  	}
   177  
   178  	remoteURL := r.FormValue("remote")
   179  
   180  	// Currently, only used if context is from a remote url.
   181  	// Look at code in DetectContextFromRemoteURL for more information.
   182  	createProgressReader := func(in io.ReadCloser) io.ReadCloser {
   183  		progressOutput := sf.NewProgressOutput(output, true)
   184  		if buildOptions.SuppressOutput {
   185  			progressOutput = sf.NewProgressOutput(notVerboseBuffer, true)
   186  		}
   187  		return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", remoteURL)
   188  	}
   189  
   190  	var (
   191  		context        builder.ModifiableContext
   192  		dockerfileName string
   193  	)
   194  	context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, createProgressReader)
   195  	if err != nil {
   196  		return errf(err)
   197  	}
   198  	defer func() {
   199  		if err := context.Close(); err != nil {
   200  			logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
   201  		}
   202  	}()
   203  	if len(dockerfileName) > 0 {
   204  		buildOptions.Dockerfile = dockerfileName
   205  	}
   206  
   207  	b, err := dockerfile.NewBuilder(
   208  		buildOptions, // result of newBuildConfig
   209  		&daemonbuilder.Docker{br.backend},
   210  		builder.DockerIgnoreContext{ModifiableContext: context},
   211  		nil)
   212  	if err != nil {
   213  		return errf(err)
   214  	}
   215  	if buildOptions.SuppressOutput {
   216  		b.Output = notVerboseBuffer
   217  	} else {
   218  		b.Output = output
   219  	}
   220  	b.Stdout = &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf}
   221  	b.Stderr = &streamformatter.StderrFormatter{Writer: output, StreamFormatter: sf}
   222  	if buildOptions.SuppressOutput {
   223  		b.Stdout = &streamformatter.StdoutFormatter{Writer: notVerboseBuffer, StreamFormatter: sf}
   224  		b.Stderr = &streamformatter.StderrFormatter{Writer: notVerboseBuffer, StreamFormatter: sf}
   225  	}
   226  
   227  	if closeNotifier, ok := w.(http.CloseNotifier); ok {
   228  		finished := make(chan struct{})
   229  		defer close(finished)
   230  		clientGone := closeNotifier.CloseNotify()
   231  		go func() {
   232  			select {
   233  			case <-finished:
   234  			case <-clientGone:
   235  				logrus.Infof("Client disconnected, cancelling job: build")
   236  				b.Cancel()
   237  			}
   238  		}()
   239  	}
   240  
   241  	imgID, err := b.Build()
   242  	if err != nil {
   243  		return errf(err)
   244  	}
   245  
   246  	for _, rt := range repoAndTags {
   247  		if err := br.backend.TagImage(rt, imgID); err != nil {
   248  			return errf(err)
   249  		}
   250  	}
   251  
   252  	// Everything worked so if -q was provided the output from the daemon
   253  	// should be just the image ID and we'll print that to stdout.
   254  	if buildOptions.SuppressOutput {
   255  		stdout := &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf}
   256  		fmt.Fprintf(stdout, "%s\n", string(imgID))
   257  	}
   258  
   259  	return nil
   260  }