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 }