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