github.com/argots/stencil@v0.0.2/pkg/stencil/binary.go (about)

     1  package stencil
     2  
     3  import (
     4  	"compress/gzip"
     5  	"errors"
     6  	"io"
     7  	"io/ioutil"
     8  	"net"
     9  	"net/http"
    10  	"path/filepath"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/bmatcuk/doublestar"
    15  )
    16  
    17  const dialTimeout = time.Second * 10
    18  const tlsTimeout = time.Second * 5
    19  const httpTimeout = time.Second * 30
    20  const targz = ".tar.gz"
    21  
    22  // Binary implements managing binaries.
    23  type Binary struct {
    24  	*Stencil
    25  }
    26  
    27  // CopyFromArchive copies a file from an archive at the url.
    28  // CopyFromArchive supports .tar, .tar.gz and .zip extensions for the archive.
    29  func (b *Binary) CopyFromArchive(key, destination, url, file string) error {
    30  	if b.Objects.existsArchiveFile(key, destination, url, file) {
    31  		return nil
    32  	}
    33  	b.Objects.addArchiveFile(key, destination, url, file)
    34  	seen := false
    35  	err := b.extract(url, func(fname string, r func() io.ReadCloser) error {
    36  		if !strings.EqualFold(file, fname) {
    37  			return nil
    38  		}
    39  
    40  		src := r()
    41  		defer src.Close()
    42  		return b.copy(key+fname, destination, src)
    43  	})
    44  	if err == nil && !seen {
    45  		err = errors.New("no such file: " + file)
    46  	}
    47  	return err
    48  }
    49  
    50  // CopyManyFromArchive extracts multiple files from an archive at the url.
    51  // CopyManyFromArchive supports .tar, .tar.gz and .zip extensions for
    52  // the archive. The glob pattern can be used to specify what files
    53  // need to be extracted. See https://github.com/bmatcuk/doublestar for
    54  // the set of allowed glob patterns. The destination is considered a
    55  // folder.
    56  func (b *Binary) CopyManyFromArchive(key, destination, url, glob string) error {
    57  	if b.Objects.existsArchiveGlob(key, destination, url, glob) {
    58  		return nil
    59  	}
    60  	b.Objects.addArchiveGlob(key, destination, url, glob)
    61  	return b.extract(url, func(fname string, r func() io.ReadCloser) error {
    62  		if match, err := doublestar.Match(glob, fname); err != nil || !match {
    63  			return err
    64  		}
    65  
    66  		src := r()
    67  		defer src.Close()
    68  		return b.copy(key+fname, filepath.Join(destination, fname), src)
    69  	})
    70  }
    71  
    72  func (b *Binary) extract(url string, visit func(name string, r func() io.ReadCloser) error) error {
    73  	client := &http.Client{
    74  		Timeout: httpTimeout,
    75  		Transport: &http.Transport{
    76  			Dial:                (&net.Dialer{Timeout: dialTimeout}).Dial,
    77  			TLSHandshakeTimeout: tlsTimeout,
    78  		},
    79  	}
    80  	resp, err := client.Get(url)
    81  	if err != nil {
    82  		return err
    83  	}
    84  	defer resp.Body.Close()
    85  
    86  	if resp.StatusCode != http.StatusOK {
    87  		return errors.New("http.Status " + resp.Status)
    88  	}
    89  
    90  	switch b.guessExtension(resp.Header.Get("Content-Type"), url) {
    91  	case ".tar":
    92  		return Untar(resp.Body, visit)
    93  	case targz:
    94  		r, err := gzip.NewReader(resp.Body)
    95  		if err != nil {
    96  			return err
    97  		}
    98  		return Untar(r, visit)
    99  	case ".zip":
   100  		return Unzip(resp.Body, visit)
   101  	}
   102  
   103  	return errors.New("Unknown destination URL extension " + url)
   104  }
   105  
   106  func (b *Binary) guessExtension(contentType, url string) string {
   107  	switch contentType {
   108  	case "application/zip":
   109  		return ".zip"
   110  	case "application/x-gzip":
   111  		return targz
   112  	}
   113  	url = strings.ToLower(url)
   114  	if strings.HasSuffix(url, targz) {
   115  		return targz
   116  	}
   117  	return filepath.Ext(url)
   118  }
   119  
   120  func (b *Binary) copy(key, dest string, src io.Reader) error {
   121  	_ = key
   122  	data, err := ioutil.ReadAll(src)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	return b.Write(dest, data, 0766)
   127  }