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  }