|
@@ -2,6 +2,7 @@ package builder
|
|
|
|
|
|
import (
|
|
|
"bytes"
|
|
|
+ "errors"
|
|
|
"fmt"
|
|
|
"io"
|
|
|
"io/ioutil"
|
|
@@ -17,6 +18,7 @@ import (
|
|
|
"github.com/docker/docker/pkg/archive"
|
|
|
"github.com/docker/docker/pkg/httputils"
|
|
|
"github.com/docker/docker/pkg/parsers"
|
|
|
+ "github.com/docker/docker/pkg/progressreader"
|
|
|
"github.com/docker/docker/pkg/streamformatter"
|
|
|
"github.com/docker/docker/pkg/urlutil"
|
|
|
"github.com/docker/docker/registry"
|
|
@@ -24,6 +26,10 @@ import (
|
|
|
"github.com/docker/docker/utils"
|
|
|
)
|
|
|
|
|
|
+// When downloading remote contexts, limit the amount (in bytes)
|
|
|
+// to be read from the response body in order to detect its Content-Type
|
|
|
+const maxPreambleLength = 100
|
|
|
+
|
|
|
// whitelist of commands allowed for a commit/import
|
|
|
var validCommitCommands = map[string]bool{
|
|
|
"entrypoint": true,
|
|
@@ -91,6 +97,7 @@ func Build(d *daemon.Daemon, buildConfig *Config) error {
|
|
|
tag string
|
|
|
context io.ReadCloser
|
|
|
)
|
|
|
+ sf := streamformatter.NewJSONStreamFormatter()
|
|
|
|
|
|
repoName, tag = parsers.ParseRepositoryTag(buildConfig.RepoName)
|
|
|
if repoName != "" {
|
|
@@ -121,27 +128,50 @@ func Build(d *daemon.Daemon, buildConfig *Config) error {
|
|
|
} else if urlutil.IsURL(buildConfig.RemoteURL) {
|
|
|
f, err := httputils.Download(buildConfig.RemoteURL)
|
|
|
if err != nil {
|
|
|
- return err
|
|
|
+ return fmt.Errorf("Error downloading remote context %s: %v", buildConfig.RemoteURL, err)
|
|
|
}
|
|
|
defer f.Body.Close()
|
|
|
- dockerFile, err := ioutil.ReadAll(f.Body)
|
|
|
+ ct := f.Header.Get("Content-Type")
|
|
|
+ clen := int(f.ContentLength)
|
|
|
+ contentType, bodyReader, err := inspectResponse(ct, f.Body, clen)
|
|
|
+
|
|
|
+ defer bodyReader.Close()
|
|
|
+
|
|
|
if err != nil {
|
|
|
- return err
|
|
|
+ return fmt.Errorf("Error detecting content type for remote %s: %v", buildConfig.RemoteURL, err)
|
|
|
}
|
|
|
+ if contentType == httputils.MimeTypes.TextPlain {
|
|
|
+ dockerFile, err := ioutil.ReadAll(bodyReader)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
|
|
|
- // When we're downloading just a Dockerfile put it in
|
|
|
- // the default name - don't allow the client to move/specify it
|
|
|
- buildConfig.DockerfileName = api.DefaultDockerfileName
|
|
|
+ // When we're downloading just a Dockerfile put it in
|
|
|
+ // the default name - don't allow the client to move/specify it
|
|
|
+ buildConfig.DockerfileName = api.DefaultDockerfileName
|
|
|
|
|
|
- c, err := archive.Generate(buildConfig.DockerfileName, string(dockerFile))
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
+ c, err := archive.Generate(buildConfig.DockerfileName, string(dockerFile))
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ context = c
|
|
|
+ } else {
|
|
|
+ // Pass through - this is a pre-packaged context, presumably
|
|
|
+ // with a Dockerfile with the right name inside it.
|
|
|
+ prCfg := progressreader.Config{
|
|
|
+ In: bodyReader,
|
|
|
+ Out: buildConfig.Stdout,
|
|
|
+ Formatter: sf,
|
|
|
+ Size: clen,
|
|
|
+ NewLines: true,
|
|
|
+ ID: "Downloading context",
|
|
|
+ Action: buildConfig.RemoteURL,
|
|
|
+ }
|
|
|
+ context = progressreader.New(prCfg)
|
|
|
}
|
|
|
- context = c
|
|
|
}
|
|
|
- defer context.Close()
|
|
|
|
|
|
- sf := streamformatter.NewJSONStreamFormatter()
|
|
|
+ defer context.Close()
|
|
|
|
|
|
builder := &Builder{
|
|
|
Daemon: d,
|
|
@@ -241,3 +271,48 @@ func Commit(d *daemon.Daemon, name string, c *daemon.ContainerCommitConfig) (str
|
|
|
|
|
|
return img.ID, nil
|
|
|
}
|
|
|
+
|
|
|
+// inspectResponse looks into the http response data at r to determine whether its
|
|
|
+// content-type is on the list of acceptable content types for remote build contexts.
|
|
|
+// This function returns:
|
|
|
+// - a string representation of the detected content-type
|
|
|
+// - an io.Reader for the response body
|
|
|
+// - an error value which will be non-nil either when something goes wrong while
|
|
|
+// reading bytes from r or when the detected content-type is not acceptable.
|
|
|
+func inspectResponse(ct string, r io.ReadCloser, clen int) (string, io.ReadCloser, error) {
|
|
|
+ plen := clen
|
|
|
+ if plen <= 0 || plen > maxPreambleLength {
|
|
|
+ plen = maxPreambleLength
|
|
|
+ }
|
|
|
+
|
|
|
+ preamble := make([]byte, plen, plen)
|
|
|
+ rlen, err := r.Read(preamble)
|
|
|
+ if rlen == 0 {
|
|
|
+ return ct, r, errors.New("Empty response")
|
|
|
+ }
|
|
|
+ if err != nil && err != io.EOF {
|
|
|
+ return ct, r, err
|
|
|
+ }
|
|
|
+
|
|
|
+ preambleR := bytes.NewReader(preamble)
|
|
|
+ bodyReader := ioutil.NopCloser(io.MultiReader(preambleR, r))
|
|
|
+ // Some web servers will use application/octet-stream as the default
|
|
|
+ // content type for files without an extension (e.g. 'Dockerfile')
|
|
|
+ // so if we receive this value we better check for text content
|
|
|
+ contentType := ct
|
|
|
+ if len(ct) == 0 || ct == httputils.MimeTypes.OctetStream {
|
|
|
+ contentType, _, err = httputils.DetectContentType(preamble)
|
|
|
+ if err != nil {
|
|
|
+ return contentType, bodyReader, err
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ contentType = selectAcceptableMIME(contentType)
|
|
|
+ var cterr error
|
|
|
+ if len(contentType) == 0 {
|
|
|
+ cterr = fmt.Errorf("unsupported Content-Type %q", ct)
|
|
|
+ contentType = ct
|
|
|
+ }
|
|
|
+
|
|
|
+ return contentType, bodyReader, cterr
|
|
|
+}
|