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 }