github.com/bitrise-io/go-steputils/v2@v2.0.0-alpha.30/cache/network/download.go (about) 1 package network 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net/http" 8 "time" 9 10 "github.com/bitrise-io/go-utils/retry" 11 "github.com/bitrise-io/go-utils/v2/log" 12 "github.com/bitrise-io/go-utils/v2/retryhttp" 13 "github.com/hashicorp/go-retryablehttp" 14 "github.com/melbahja/got" 15 ) 16 17 // DownloadParams ... 18 type DownloadParams struct { 19 APIBaseURL string 20 Token string 21 CacheKeys []string 22 DownloadPath string 23 NumFullRetries int 24 } 25 26 // ErrCacheNotFound ... 27 var ErrCacheNotFound = errors.New("no cache archive found for the provided keys") 28 29 // Download archive from the cache API based on the provided keys in params. 30 // If there is no match for any of the keys, the error is ErrCacheNotFound. 31 func Download(ctx context.Context, params DownloadParams, logger log.Logger) (string, error) { 32 retryableHTTPClient := retryhttp.NewClient(logger) 33 return downloadWithClient(ctx, retryableHTTPClient, params, logger) 34 } 35 36 func downloadWithClient(ctx context.Context, httpClient *retryablehttp.Client, params DownloadParams, logger log.Logger) (string, error) { 37 if params.APIBaseURL == "" { 38 return "", fmt.Errorf("API base URL is empty") 39 } 40 41 if params.Token == "" { 42 return "", fmt.Errorf("API token is empty") 43 } 44 45 if len(params.CacheKeys) == 0 { 46 return "", fmt.Errorf("cache key list is empty") 47 } 48 49 matchedKey := "" 50 err := retry.Times(uint(params.NumFullRetries)).Wait(5 * time.Second).TryWithAbort(func(attempt uint) (error, bool) { 51 if attempt != 0 { 52 logger.Debugf("Retrying archive download... (attempt %d)", attempt+1) 53 } 54 55 client := newAPIClient(httpClient, params.APIBaseURL, params.Token, logger) 56 57 logger.Debugf("Fetching download URL...") 58 restoreResponse, err := client.restore(params.CacheKeys) 59 if err != nil { 60 if errors.Is(err, ErrCacheNotFound) { 61 return err, true // Do not retry if cache key not found 62 } 63 64 logger.Debugf("Failed to get download URL: %s", err) 65 return fmt.Errorf("failed to get download URL: %w", err), false 66 } 67 68 logger.Debugf("Downloading archive...") 69 downloadErr := downloadFile(ctx, httpClient.StandardClient(), restoreResponse.URL, params.DownloadPath) 70 if downloadErr != nil { 71 logger.Debugf("Failed to download archive: %s", downloadErr) 72 return fmt.Errorf("failed to download archive: %w", downloadErr), false 73 } 74 75 matchedKey = restoreResponse.MatchedKey 76 return nil, false 77 }) 78 79 return matchedKey, err 80 } 81 82 func downloadFile(ctx context.Context, client *http.Client, url string, dest string) error { 83 downloader := got.New() 84 downloader.Client = client 85 86 gDownload := got.NewDownload(ctx, url, dest) 87 // Client has to be set on "Download" as well, 88 // as depending on how downloader is called 89 // either the Client from the downloader or from the Download will be used. 90 gDownload.Client = client 91 92 err := downloader.Do(gDownload) 93 94 return err 95 }