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  }