github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/ui/versionmanager/github.go (about) 1 /* 2 * Copyright (C) 2021 The "MysteriumNetwork/node" Authors. 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 package versionmanager 19 20 import ( 21 "fmt" 22 "net/http" 23 "net/url" 24 "time" 25 26 "github.com/mysteriumnetwork/node/requests" 27 ) 28 29 const ( 30 nodeUIAssetName = "dist.tar.gz" 31 compatibilityAssetName = "compatibility.json" 32 apiURI = "https://api.github.com" 33 nodeUIPath = "repos/mysteriumnetwork/dvpn-web" 34 ) 35 36 type httpClient interface { 37 Do(req *http.Request) (*http.Response, error) 38 DoRequestAndParseResponse(req *http.Request, resp interface{}) error 39 } 40 41 type github struct { 42 http httpClient 43 } 44 45 func newGithub(httpClient httpClient) *github { 46 return &github{http: httpClient} 47 } 48 49 func (g *github) nodeUIReleases(perPage int, page int) ([]GitHubRelease, error) { 50 req, err := requests.NewGetRequest(apiURI, fmt.Sprintf("%s/releases", nodeUIPath), nil) 51 if err != nil { 52 return nil, fmt.Errorf("failed to create NodeUI releases fetch request: %w", err) 53 } 54 55 q := req.URL.Query() 56 if perPage != 0 { 57 q.Add("per_page", fmt.Sprint(perPage)) 58 } 59 60 if page != 0 { 61 q.Add("page", fmt.Sprint(page)) 62 } 63 req.URL.RawQuery = q.Encode() 64 65 res, err := g.http.Do(req) 66 if err != nil { 67 return nil, fmt.Errorf("failed to fetch NodeUI releases: %w", err) 68 } 69 70 if err := requests.ParseResponseError(res); err != nil { 71 return nil, fmt.Errorf("response error: %w", err) 72 } 73 74 var releases []GitHubRelease 75 err = requests.ParseResponseJSON(res, &releases) 76 if err != nil { 77 return nil, fmt.Errorf("failed to parse response: %w", err) 78 } 79 80 return releases, nil 81 } 82 83 func (g *github) nodeUIReleaseByVersion(name string) (GitHubRelease, error) { 84 req, err := requests.NewGetRequest(apiURI, fmt.Sprintf("%s/releases/tags/%s", nodeUIPath, name), nil) 85 if err != nil { 86 return GitHubRelease{}, fmt.Errorf("could not create request: %w", err) 87 } 88 89 var release GitHubRelease 90 err = g.http.DoRequestAndParseResponse(req, &release) 91 if err != nil { 92 return GitHubRelease{}, fmt.Errorf("could not fetch version tagged %q: %w", name, err) 93 } 94 95 return release, nil 96 } 97 98 func (g *github) nodeUIDownloadURL(versionName string) (*url.URL, error) { 99 r, err := g.nodeUIReleaseByVersion(versionName) 100 if err != nil { 101 return nil, err 102 } 103 104 req, err := requests.NewGetRequest(apiURI, fmt.Sprintf("%s/releases/%d/assets", nodeUIPath, r.Id), nil) 105 if err != nil { 106 return nil, fmt.Errorf("failed to create NodeUI assets ID %d, versionName %q: %w", r.Id, r.Name, err) 107 } 108 109 var assets []GithubAsset 110 err = g.http.DoRequestAndParseResponse(req, &assets) 111 if err != nil { 112 return nil, fmt.Errorf("failed to fetch NodeUI releases: %w", err) 113 } 114 115 uiDist, ok := findNodeUIDist(assets) 116 if !ok { 117 return nil, fmt.Errorf("could not find nodeUI dist asset for release ID %d version %s", r.Id, versionName) 118 } 119 return url.Parse(uiDist.BrowserDownloadUrl) 120 } 121 122 func findNodeUIDist(assets []GithubAsset) (GithubAsset, bool) { 123 for _, ass := range assets { 124 if ass.Name == nodeUIAssetName { 125 return ass, true 126 } 127 } 128 return GithubAsset{}, false 129 } 130 131 // GitHubRelease github release resource 132 type GitHubRelease struct { 133 Url string `json:"url"` 134 AssetsUrl string `json:"assets_url"` 135 UploadUrl string `json:"upload_url"` 136 HtmlUrl string `json:"html_url"` 137 Id int `json:"id"` 138 Author struct { 139 Login string `json:"login"` 140 Id int `json:"id"` 141 NodeId string `json:"node_id"` 142 AvatarUrl string `json:"avatar_url"` 143 GravatarId string `json:"gravatar_id"` 144 Url string `json:"url"` 145 HtmlUrl string `json:"html_url"` 146 FollowersUrl string `json:"followers_url"` 147 FollowingUrl string `json:"following_url"` 148 GistsUrl string `json:"gists_url"` 149 StarredUrl string `json:"starred_url"` 150 SubscriptionsUrl string `json:"subscriptions_url"` 151 OrganizationsUrl string `json:"organizations_url"` 152 ReposUrl string `json:"repos_url"` 153 EventsUrl string `json:"events_url"` 154 ReceivedEventsUrl string `json:"received_events_url"` 155 Type string `json:"type"` 156 SiteAdmin bool `json:"site_admin"` 157 } `json:"author"` 158 NodeId string `json:"node_id"` 159 TagName string `json:"tag_name"` 160 TargetCommitish string `json:"target_commitish"` 161 Name string `json:"name"` 162 Draft bool `json:"draft"` 163 Prerelease bool `json:"prerelease"` 164 CreatedAt time.Time `json:"created_at"` 165 PublishedAt time.Time `json:"published_at"` 166 Assets []struct { 167 Url string `json:"url"` 168 Id int `json:"id"` 169 NodeId string `json:"node_id"` 170 Name string `json:"name"` 171 Label string `json:"label"` 172 Uploader struct { 173 Login string `json:"login"` 174 Id int `json:"id"` 175 NodeId string `json:"node_id"` 176 AvatarUrl string `json:"avatar_url"` 177 GravatarId string `json:"gravatar_id"` 178 Url string `json:"url"` 179 HtmlUrl string `json:"html_url"` 180 FollowersUrl string `json:"followers_url"` 181 FollowingUrl string `json:"following_url"` 182 GistsUrl string `json:"gists_url"` 183 StarredUrl string `json:"starred_url"` 184 SubscriptionsUrl string `json:"subscriptions_url"` 185 OrganizationsUrl string `json:"organizations_url"` 186 ReposUrl string `json:"repos_url"` 187 EventsUrl string `json:"events_url"` 188 ReceivedEventsUrl string `json:"received_events_url"` 189 Type string `json:"type"` 190 SiteAdmin bool `json:"site_admin"` 191 } `json:"uploader"` 192 ContentType string `json:"content_type"` 193 State string `json:"state"` 194 Size int `json:"size"` 195 DownloadCount int `json:"download_count"` 196 CreatedAt time.Time `json:"created_at"` 197 UpdatedAt time.Time `json:"updated_at"` 198 BrowserDownloadUrl string `json:"browser_download_url"` 199 } `json:"assets"` 200 TarballUrl string `json:"tarball_url"` 201 ZipballUrl string `json:"zipball_url"` 202 Body string `json:"body"` 203 } 204 205 // GithubAsset github asset resource 206 type GithubAsset struct { 207 Url string `json:"url"` 208 Id int `json:"id"` 209 NodeId string `json:"node_id"` 210 Name string `json:"name"` 211 Label string `json:"label"` 212 Uploader struct { 213 Login string `json:"login"` 214 Id int `json:"id"` 215 NodeId string `json:"node_id"` 216 AvatarUrl string `json:"avatar_url"` 217 GravatarId string `json:"gravatar_id"` 218 Url string `json:"url"` 219 HtmlUrl string `json:"html_url"` 220 FollowersUrl string `json:"followers_url"` 221 FollowingUrl string `json:"following_url"` 222 GistsUrl string `json:"gists_url"` 223 StarredUrl string `json:"starred_url"` 224 SubscriptionsUrl string `json:"subscriptions_url"` 225 OrganizationsUrl string `json:"organizations_url"` 226 ReposUrl string `json:"repos_url"` 227 EventsUrl string `json:"events_url"` 228 ReceivedEventsUrl string `json:"received_events_url"` 229 Type string `json:"type"` 230 SiteAdmin bool `json:"site_admin"` 231 } `json:"uploader"` 232 ContentType string `json:"content_type"` 233 State string `json:"state"` 234 Size int `json:"size"` 235 DownloadCount int `json:"download_count"` 236 CreatedAt time.Time `json:"created_at"` 237 UpdatedAt time.Time `json:"updated_at"` 238 BrowserDownloadUrl string `json:"browser_download_url"` 239 }