11package downloader
22
33import (
4+ "bytes"
45 "crypto/sha256"
56 "errors"
67 "fmt"
78 "io"
89 "net/http"
910 "os"
11+ "os/exec"
12+ "path"
1013 "path/filepath"
1114 "strings"
1215 "time"
@@ -38,6 +41,7 @@ type Result struct {
3841
3942type options struct {
4043 cacheDir string // default: empty (disables caching)
44+ decompress bool // default: false (keep compression)
4145 description string // default: url
4246 expectedDigest digest.Digest
4347}
@@ -73,6 +77,14 @@ func WithDescription(description string) Opt {
7377 }
7478}
7579
80+ // WithDecompress decompress the download from the cache.
81+ func WithDecompress (decompress bool ) Opt {
82+ return func (o * options ) error {
83+ o .decompress = decompress
84+ return nil
85+ }
86+ }
87+
7688// WithExpectedDigest is used to validate the downloaded file against the expected digest.
7789//
7890// The digest is not verified in the following cases:
@@ -142,8 +154,9 @@ func Download(local, remote string, opts ...Opt) (*Result, error) {
142154 }
143155 }
144156
157+ ext := path .Ext (remote )
145158 if IsLocal (remote ) {
146- if err := copyLocal (localPath , remote , o .description , o .expectedDigest ); err != nil {
159+ if err := copyLocal (localPath , remote , ext , o . decompress , o .description , o .expectedDigest ); err != nil {
147160 return nil , err
148161 }
149162 res := & Result {
@@ -183,11 +196,11 @@ func Download(local, remote string, opts ...Opt) (*Result, error) {
183196 if o .expectedDigest .String () != shadDigestS {
184197 return nil , fmt .Errorf ("expected digest %q does not match the cached digest %q" , o .expectedDigest .String (), shadDigestS )
185198 }
186- if err := copyLocal (localPath , shadData , "" , "" ); err != nil {
199+ if err := copyLocal (localPath , shadData , ext , o . decompress , "" , "" ); err != nil {
187200 return nil , err
188201 }
189202 } else {
190- if err := copyLocal (localPath , shadData , o .description , o .expectedDigest ); err != nil {
203+ if err := copyLocal (localPath , shadData , ext , o . decompress , o .description , o .expectedDigest ); err != nil {
191204 return nil , err
192205 }
193206 }
@@ -212,7 +225,7 @@ func Download(local, remote string, opts ...Opt) (*Result, error) {
212225 return nil , err
213226 }
214227 // no need to pass the digest to copyLocal(), as we already verified the digest
215- if err := copyLocal (localPath , shadData , "" , "" ); err != nil {
228+ if err := copyLocal (localPath , shadData , ext , o . decompress , "" , "" ); err != nil {
216229 return nil , err
217230 }
218231 if shadDigest != "" && o .expectedDigest != "" {
@@ -253,7 +266,7 @@ func canonicalLocalPath(s string) (string, error) {
253266 return localpathutil .Expand (s )
254267}
255268
256- func copyLocal (dst , src string , description string , expectedDigest digest.Digest ) error {
269+ func copyLocal (dst , src , ext string , decompress bool , description string , expectedDigest digest.Digest ) error {
257270 srcPath , err := canonicalLocalPath (src )
258271 if err != nil {
259272 return err
@@ -274,9 +287,60 @@ func copyLocal(dst, src string, description string, expectedDigest digest.Digest
274287 if description != "" {
275288 // TODO: progress bar for copy
276289 }
290+ if _ , ok := Decompressor (ext ); ok && decompress {
291+ return decompressLocal (dstPath , srcPath , ext )
292+ }
277293 return fs .CopyFile (dstPath , srcPath )
278294}
279295
296+ func Decompressor (ext string ) ([]string , bool ) {
297+ var program string
298+ switch ext {
299+ case ".gz" :
300+ program = "gzip"
301+ case ".bz2" :
302+ program = "bzip2"
303+ case ".xz" :
304+ program = "xz"
305+ case ".zst" :
306+ program = "zstd"
307+ default :
308+ return nil , false
309+ }
310+ // -d --decompress
311+ return []string {program , "-d" }, true
312+ }
313+
314+ func decompressLocal (dst , src , ext string ) error {
315+ command , found := Decompressor (ext )
316+ if ! found {
317+ return fmt .Errorf ("decompressLocal: unknown extension %s" , ext )
318+ }
319+ logrus .Infof ("decompressing %s with %v" , ext , command )
320+ in , err := os .Open (src )
321+ if err != nil {
322+ return err
323+ }
324+ defer in .Close ()
325+ out , err := os .OpenFile (dst , os .O_CREATE | os .O_WRONLY , 0644 )
326+ if err != nil {
327+ return err
328+ }
329+ defer out .Close ()
330+ buf := new (bytes.Buffer )
331+ cmd := exec .Command (command [0 ], command [1 :]... )
332+ cmd .Stdin = in
333+ cmd .Stdout = out
334+ cmd .Stderr = buf
335+ err = cmd .Run ()
336+ if err != nil {
337+ if ee , ok := err .(* exec.ExitError ); ok {
338+ ee .Stderr = buf .Bytes ()
339+ }
340+ }
341+ return err
342+ }
343+
280344func validateLocalFileDigest (localPath string , expectedDigest digest.Digest ) error {
281345 if localPath == "" {
282346 return fmt .Errorf ("validateLocalFileDigest: got empty localPath" )
0 commit comments