code.vegaprotocol.io/vega@v0.79.0/visor/github/assets_fetcher.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package github
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"io/ioutil"
    22  	"net/http"
    23  	"os"
    24  	"path"
    25  	"path/filepath"
    26  	"time"
    27  
    28  	"code.vegaprotocol.io/vega/visor/utils"
    29  
    30  	"github.com/google/go-github/v50/github"
    31  	"golang.org/x/sync/errgroup"
    32  )
    33  
    34  const zipMimeType = "application/zip"
    35  
    36  type AssetsFetcher struct {
    37  	repositoryOwner string
    38  	repository      string
    39  
    40  	assetNames map[string]struct{}
    41  
    42  	*github.Client
    43  }
    44  
    45  func NewAssetsFetcher(
    46  	repositoryOwner, repository string,
    47  	assetsNames []string,
    48  ) *AssetsFetcher {
    49  	return &AssetsFetcher{
    50  		repositoryOwner: repositoryOwner,
    51  		repository:      repository,
    52  		assetNames:      utils.ToLookupMap(assetsNames),
    53  		Client:          github.NewClient(nil),
    54  	}
    55  }
    56  
    57  func (af *AssetsFetcher) GetReleaseID(ctx context.Context, releaseTag string) (int64, error) {
    58  	nextPage := 1
    59  	opts := &github.ListOptions{
    60  		PerPage: 100,
    61  		Page:    nextPage,
    62  	}
    63  
    64  	for nextPage > 0 {
    65  		opts.Page = nextPage
    66  		releases, resp, err := af.Client.Repositories.ListReleases(ctx, af.repositoryOwner, af.repository, opts)
    67  		if err != nil {
    68  			return 0, err
    69  		}
    70  		nextPage = resp.NextPage
    71  		for _, r := range releases {
    72  			if *r.TagName == releaseTag {
    73  				return r.GetID(), nil
    74  			}
    75  		}
    76  	}
    77  	return 0, fmt.Errorf("release tag %q not found", releaseTag)
    78  }
    79  
    80  func (af *AssetsFetcher) GetAssets(ctx context.Context, releaseID int64) ([]*github.ReleaseAsset, error) {
    81  	assets, _, err := af.Client.Repositories.ListReleaseAssets(ctx, af.repositoryOwner, af.repository, releaseID, nil)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	var filteredAssets []*github.ReleaseAsset
    87  	for _, asset := range assets {
    88  		if _, ok := af.assetNames[asset.GetName()]; ok {
    89  			filteredAssets = append(filteredAssets, asset)
    90  		}
    91  	}
    92  
    93  	return filteredAssets, nil
    94  }
    95  
    96  func (af *AssetsFetcher) DownloadAsset(ctx context.Context, assetID int64, assetType, path string) error {
    97  	followClient := &http.Client{Timeout: time.Second * 120}
    98  
    99  	ra, _, err := af.Client.Repositories.DownloadReleaseAsset(ctx, af.repositoryOwner, af.repository, assetID, followClient)
   100  	if err != nil {
   101  		return fmt.Errorf("failed to download release asset: %w", err)
   102  	}
   103  	defer ra.Close()
   104  
   105  	all, err := ioutil.ReadAll(ra)
   106  	if err != nil {
   107  		return fmt.Errorf("failed to read  %q: %w", path, err)
   108  	}
   109  
   110  	if err := os.WriteFile(path, all, 0o770); err != nil {
   111  		return fmt.Errorf("failed to write to %q: %w", path, err)
   112  	}
   113  
   114  	if assetType == zipMimeType {
   115  		if err := utils.UnzipSource(path, filepath.Dir(path)); err != nil {
   116  			return fmt.Errorf("failed to unzip asset: %w", err)
   117  		}
   118  	}
   119  
   120  	return nil
   121  }
   122  
   123  func (af *AssetsFetcher) Download(ctx context.Context, releaseTag, downloadDir string) error {
   124  	releaseID, err := af.GetReleaseID(ctx, releaseTag)
   125  	if err != nil {
   126  		return fmt.Errorf("failed to get release ID for tag %q: %q", releaseTag, err)
   127  	}
   128  
   129  	assetIDs, err := af.GetAssets(ctx, releaseID)
   130  	if err != nil {
   131  		return fmt.Errorf("failed to get assets ID for tag %q: %q", releaseTag, err)
   132  	}
   133  
   134  	eg, ctx := errgroup.WithContext(ctx)
   135  	for _, asset := range assetIDs {
   136  		assetID := asset.GetID()
   137  		assetName := asset.GetName()
   138  		assetType := asset.GetContentType()
   139  
   140  		eg.Go(func() error {
   141  			if err := af.DownloadAsset(ctx, assetID, assetType, path.Join(downloadDir, assetName)); err != nil {
   142  				return fmt.Errorf("failed to download asset %q of type %q for tag %q: %w", assetName, releaseTag, assetType, err)
   143  			}
   144  			return nil
   145  		})
   146  	}
   147  
   148  	return eg.Wait()
   149  }