github.com/yamamoto-febc/docker@v1.9.0/builder/remote.go (about) 1 package builder 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "regexp" 10 11 "github.com/docker/docker/pkg/httputils" 12 ) 13 14 // When downloading remote contexts, limit the amount (in bytes) 15 // to be read from the response body in order to detect its Content-Type 16 const maxPreambleLength = 100 17 18 const acceptableRemoteMIME = `(?:application/(?:(?:x\-)?tar|octet\-stream|((?:x\-)?(?:gzip|bzip2?|xz)))|(?:text/plain))` 19 20 var mimeRe = regexp.MustCompile(acceptableRemoteMIME) 21 22 // MakeRemoteContext downloads a context from remoteURL and returns it. 23 // 24 // If contentTypeHandlers is non-nil, then the Content-Type header is read along with a maximum of 25 // maxPreambleLength bytes from the body to help detecting the MIME type. 26 // Look at acceptableRemoteMIME for more details. 27 // 28 // If a match is found, then the body is sent to the contentType handler and a (potentially compressed) tar stream is expected 29 // to be returned. If no match is found, it is assumed the body is a tar stream (compressed or not). 30 // In either case, an (assumed) tar stream is passed to MakeTarSumContext whose result is returned. 31 func MakeRemoteContext(remoteURL string, contentTypeHandlers map[string]func(io.ReadCloser) (io.ReadCloser, error)) (ModifiableContext, error) { 32 f, err := httputils.Download(remoteURL) 33 if err != nil { 34 return nil, fmt.Errorf("Error downloading remote context %s: %v", remoteURL, err) 35 } 36 defer f.Body.Close() 37 38 var contextReader io.ReadCloser 39 if contentTypeHandlers != nil { 40 contentType := f.Header.Get("Content-Type") 41 clen := f.ContentLength 42 43 contentType, contextReader, err = inspectResponse(contentType, f.Body, clen) 44 if err != nil { 45 return nil, fmt.Errorf("Error detecting content type for remote %s: %v", remoteURL, err) 46 } 47 defer contextReader.Close() 48 49 // This loop tries to find a content-type handler for the detected content-type. 50 // If it could not find one from the caller-supplied map, it tries the empty content-type `""` 51 // which is interpreted as a fallback handler (usually used for raw tar contexts). 52 for _, ct := range []string{contentType, ""} { 53 if fn, ok := contentTypeHandlers[ct]; ok { 54 defer contextReader.Close() 55 if contextReader, err = fn(contextReader); err != nil { 56 return nil, err 57 } 58 break 59 } 60 } 61 } 62 63 // Pass through - this is a pre-packaged context, presumably 64 // with a Dockerfile with the right name inside it. 65 return MakeTarSumContext(contextReader) 66 } 67 68 // inspectResponse looks into the http response data at r to determine whether its 69 // content-type is on the list of acceptable content types for remote build contexts. 70 // This function returns: 71 // - a string representation of the detected content-type 72 // - an io.Reader for the response body 73 // - an error value which will be non-nil either when something goes wrong while 74 // reading bytes from r or when the detected content-type is not acceptable. 75 func inspectResponse(ct string, r io.ReadCloser, clen int64) (string, io.ReadCloser, error) { 76 plen := clen 77 if plen <= 0 || plen > maxPreambleLength { 78 plen = maxPreambleLength 79 } 80 81 preamble := make([]byte, plen, plen) 82 rlen, err := r.Read(preamble) 83 if rlen == 0 { 84 return ct, r, errors.New("Empty response") 85 } 86 if err != nil && err != io.EOF { 87 return ct, r, err 88 } 89 90 preambleR := bytes.NewReader(preamble) 91 bodyReader := ioutil.NopCloser(io.MultiReader(preambleR, r)) 92 // Some web servers will use application/octet-stream as the default 93 // content type for files without an extension (e.g. 'Dockerfile') 94 // so if we receive this value we better check for text content 95 contentType := ct 96 if len(ct) == 0 || ct == httputils.MimeTypes.OctetStream { 97 contentType, _, err = httputils.DetectContentType(preamble) 98 if err != nil { 99 return contentType, bodyReader, err 100 } 101 } 102 103 contentType = selectAcceptableMIME(contentType) 104 var cterr error 105 if len(contentType) == 0 { 106 cterr = fmt.Errorf("unsupported Content-Type %q", ct) 107 contentType = ct 108 } 109 110 return contentType, bodyReader, cterr 111 } 112 113 func selectAcceptableMIME(ct string) string { 114 return mimeRe.FindString(ct) 115 }