When using GITHUB_TOKEN in Actions, the permissions on a repository are null and therefore we can't check whether the viewer has push access or not. The solution is to unconditionally check for draft releases instead of trying to be smart about it. Draft releases are going to be on top, so we don't have to paginate through all releases in a repository.
234 lines
5.3 KiB
Go
234 lines
5.3 KiB
Go
package shared
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cli/cli/api"
|
|
"github.com/cli/cli/internal/ghinstance"
|
|
"github.com/cli/cli/internal/ghrepo"
|
|
)
|
|
|
|
var ReleaseFields = []string{
|
|
"url",
|
|
"apiUrl",
|
|
"uploadUrl",
|
|
"tarballUrl",
|
|
"zipballUrl",
|
|
"id",
|
|
"tagName",
|
|
"name",
|
|
"body",
|
|
"isDraft",
|
|
"isPrerelease",
|
|
"createdAt",
|
|
"publishedAt",
|
|
"targetCommitish",
|
|
"author",
|
|
"assets",
|
|
}
|
|
|
|
type Release struct {
|
|
ID string `json:"node_id"`
|
|
TagName string `json:"tag_name"`
|
|
Name string `json:"name"`
|
|
Body string `json:"body"`
|
|
IsDraft bool `json:"draft"`
|
|
IsPrerelease bool `json:"prerelease"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
PublishedAt *time.Time `json:"published_at"`
|
|
|
|
TargetCommitish string `json:"target_commitish"`
|
|
|
|
APIURL string `json:"url"`
|
|
UploadURL string `json:"upload_url"`
|
|
TarballURL string `json:"tarball_url"`
|
|
ZipballURL string `json:"zipball_url"`
|
|
URL string `json:"html_url"`
|
|
Assets []ReleaseAsset
|
|
|
|
Author struct {
|
|
ID string `json:"node_id"`
|
|
Login string `json:"login"`
|
|
}
|
|
}
|
|
|
|
type ReleaseAsset struct {
|
|
ID string `json:"node_id"`
|
|
Name string
|
|
Label string
|
|
Size int64
|
|
State string
|
|
APIURL string `json:"url"`
|
|
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
DownloadCount int `json:"download_count"`
|
|
ContentType string `json:"content_type"`
|
|
BrowserDownloadURL string `json:"browser_download_url"`
|
|
}
|
|
|
|
func (rel *Release) ExportData(fields []string) *map[string]interface{} {
|
|
v := reflect.ValueOf(rel).Elem()
|
|
fieldByName := func(v reflect.Value, field string) reflect.Value {
|
|
return v.FieldByNameFunc(func(s string) bool {
|
|
return strings.EqualFold(field, s)
|
|
})
|
|
}
|
|
data := map[string]interface{}{}
|
|
|
|
for _, f := range fields {
|
|
switch f {
|
|
case "author":
|
|
data[f] = map[string]interface{}{
|
|
"id": rel.Author.ID,
|
|
"login": rel.Author.Login,
|
|
}
|
|
case "assets":
|
|
assets := make([]interface{}, 0, len(rel.Assets))
|
|
for _, a := range rel.Assets {
|
|
assets = append(assets, map[string]interface{}{
|
|
"url": a.BrowserDownloadURL,
|
|
"apiUrl": a.APIURL,
|
|
"id": a.ID,
|
|
"name": a.Name,
|
|
"label": a.Label,
|
|
"size": a.Size,
|
|
"state": a.State,
|
|
"createdAt": a.CreatedAt,
|
|
"updatedAt": a.UpdatedAt,
|
|
"downloadCount": a.DownloadCount,
|
|
"contentType": a.ContentType,
|
|
})
|
|
}
|
|
data[f] = assets
|
|
default:
|
|
sf := fieldByName(v, f)
|
|
data[f] = sf.Interface()
|
|
}
|
|
}
|
|
|
|
return &data
|
|
}
|
|
|
|
// FetchRelease finds a repository release by its tagName.
|
|
func FetchRelease(httpClient *http.Client, baseRepo ghrepo.Interface, tagName string) (*Release, error) {
|
|
path := fmt.Sprintf("repos/%s/%s/releases/tags/%s", baseRepo.RepoOwner(), baseRepo.RepoName(), tagName)
|
|
url := ghinstance.RESTPrefix(baseRepo.RepoHost()) + path
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == 404 {
|
|
return FindDraftRelease(httpClient, baseRepo, tagName)
|
|
}
|
|
|
|
if resp.StatusCode > 299 {
|
|
return nil, api.HandleHTTPError(resp)
|
|
}
|
|
|
|
b, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var release Release
|
|
err = json.Unmarshal(b, &release)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &release, nil
|
|
}
|
|
|
|
// FetchLatestRelease finds the latest published release for a repository.
|
|
func FetchLatestRelease(httpClient *http.Client, baseRepo ghrepo.Interface) (*Release, error) {
|
|
path := fmt.Sprintf("repos/%s/%s/releases/latest", baseRepo.RepoOwner(), baseRepo.RepoName())
|
|
url := ghinstance.RESTPrefix(baseRepo.RepoHost()) + path
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode > 299 {
|
|
return nil, api.HandleHTTPError(resp)
|
|
}
|
|
|
|
b, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var release Release
|
|
err = json.Unmarshal(b, &release)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &release, nil
|
|
}
|
|
|
|
// FindDraftRelease returns the latest draft release that matches tagName.
|
|
func FindDraftRelease(httpClient *http.Client, baseRepo ghrepo.Interface, tagName string) (*Release, error) {
|
|
path := fmt.Sprintf("repos/%s/%s/releases", baseRepo.RepoOwner(), baseRepo.RepoName())
|
|
url := ghinstance.RESTPrefix(baseRepo.RepoHost()) + path
|
|
|
|
perPage := 100
|
|
page := 1
|
|
for {
|
|
req, err := http.NewRequest("GET", fmt.Sprintf("%s?per_page=%d&page=%d", url, perPage, page), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode > 299 {
|
|
return nil, api.HandleHTTPError(resp)
|
|
}
|
|
|
|
b, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var releases []Release
|
|
err = json.Unmarshal(b, &releases)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, r := range releases {
|
|
if r.IsDraft && r.TagName == tagName {
|
|
return &r, nil
|
|
}
|
|
}
|
|
//nolint:staticcheck
|
|
break
|
|
}
|
|
|
|
return nil, errors.New("release not found")
|
|
}
|