github.com/andrewhsu/cli/v2@v2.0.1-0.20210910131313-d4b4061f5b89/pkg/cmd/release/shared/fetch.go (about)

     1  package shared
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"reflect"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/andrewhsu/cli/v2/api"
    14  	"github.com/andrewhsu/cli/v2/internal/ghinstance"
    15  	"github.com/andrewhsu/cli/v2/internal/ghrepo"
    16  )
    17  
    18  var ReleaseFields = []string{
    19  	"url",
    20  	"apiUrl",
    21  	"uploadUrl",
    22  	"tarballUrl",
    23  	"zipballUrl",
    24  	"id",
    25  	"tagName",
    26  	"name",
    27  	"body",
    28  	"isDraft",
    29  	"isPrerelease",
    30  	"createdAt",
    31  	"publishedAt",
    32  	"targetCommitish",
    33  	"author",
    34  	"assets",
    35  }
    36  
    37  type Release struct {
    38  	ID           string     `json:"node_id"`
    39  	TagName      string     `json:"tag_name"`
    40  	Name         string     `json:"name"`
    41  	Body         string     `json:"body"`
    42  	IsDraft      bool       `json:"draft"`
    43  	IsPrerelease bool       `json:"prerelease"`
    44  	CreatedAt    time.Time  `json:"created_at"`
    45  	PublishedAt  *time.Time `json:"published_at"`
    46  
    47  	TargetCommitish string `json:"target_commitish"`
    48  
    49  	APIURL     string `json:"url"`
    50  	UploadURL  string `json:"upload_url"`
    51  	TarballURL string `json:"tarball_url"`
    52  	ZipballURL string `json:"zipball_url"`
    53  	URL        string `json:"html_url"`
    54  	Assets     []ReleaseAsset
    55  
    56  	Author struct {
    57  		ID    string `json:"node_id"`
    58  		Login string `json:"login"`
    59  	}
    60  }
    61  
    62  type ReleaseAsset struct {
    63  	ID     string `json:"node_id"`
    64  	Name   string
    65  	Label  string
    66  	Size   int64
    67  	State  string
    68  	APIURL string `json:"url"`
    69  
    70  	CreatedAt          time.Time `json:"created_at"`
    71  	UpdatedAt          time.Time `json:"updated_at"`
    72  	DownloadCount      int       `json:"download_count"`
    73  	ContentType        string    `json:"content_type"`
    74  	BrowserDownloadURL string    `json:"browser_download_url"`
    75  }
    76  
    77  func (rel *Release) ExportData(fields []string) *map[string]interface{} {
    78  	v := reflect.ValueOf(rel).Elem()
    79  	fieldByName := func(v reflect.Value, field string) reflect.Value {
    80  		return v.FieldByNameFunc(func(s string) bool {
    81  			return strings.EqualFold(field, s)
    82  		})
    83  	}
    84  	data := map[string]interface{}{}
    85  
    86  	for _, f := range fields {
    87  		switch f {
    88  		case "author":
    89  			data[f] = map[string]interface{}{
    90  				"id":    rel.Author.ID,
    91  				"login": rel.Author.Login,
    92  			}
    93  		case "assets":
    94  			assets := make([]interface{}, 0, len(rel.Assets))
    95  			for _, a := range rel.Assets {
    96  				assets = append(assets, map[string]interface{}{
    97  					"url":           a.BrowserDownloadURL,
    98  					"apiUrl":        a.APIURL,
    99  					"id":            a.ID,
   100  					"name":          a.Name,
   101  					"label":         a.Label,
   102  					"size":          a.Size,
   103  					"state":         a.State,
   104  					"createdAt":     a.CreatedAt,
   105  					"updatedAt":     a.UpdatedAt,
   106  					"downloadCount": a.DownloadCount,
   107  					"contentType":   a.ContentType,
   108  				})
   109  			}
   110  			data[f] = assets
   111  		default:
   112  			sf := fieldByName(v, f)
   113  			data[f] = sf.Interface()
   114  		}
   115  	}
   116  
   117  	return &data
   118  }
   119  
   120  // FetchRelease finds a repository release by its tagName.
   121  func FetchRelease(httpClient *http.Client, baseRepo ghrepo.Interface, tagName string) (*Release, error) {
   122  	path := fmt.Sprintf("repos/%s/%s/releases/tags/%s", baseRepo.RepoOwner(), baseRepo.RepoName(), tagName)
   123  	url := ghinstance.RESTPrefix(baseRepo.RepoHost()) + path
   124  	req, err := http.NewRequest("GET", url, nil)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	resp, err := httpClient.Do(req)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	defer resp.Body.Close()
   134  
   135  	if resp.StatusCode == 404 {
   136  		return FindDraftRelease(httpClient, baseRepo, tagName)
   137  	}
   138  
   139  	if resp.StatusCode > 299 {
   140  		return nil, api.HandleHTTPError(resp)
   141  	}
   142  
   143  	b, err := ioutil.ReadAll(resp.Body)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	var release Release
   149  	err = json.Unmarshal(b, &release)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	return &release, nil
   155  }
   156  
   157  // FetchLatestRelease finds the latest published release for a repository.
   158  func FetchLatestRelease(httpClient *http.Client, baseRepo ghrepo.Interface) (*Release, error) {
   159  	path := fmt.Sprintf("repos/%s/%s/releases/latest", baseRepo.RepoOwner(), baseRepo.RepoName())
   160  	url := ghinstance.RESTPrefix(baseRepo.RepoHost()) + path
   161  	req, err := http.NewRequest("GET", url, nil)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  
   166  	resp, err := httpClient.Do(req)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	defer resp.Body.Close()
   171  
   172  	if resp.StatusCode > 299 {
   173  		return nil, api.HandleHTTPError(resp)
   174  	}
   175  
   176  	b, err := ioutil.ReadAll(resp.Body)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  
   181  	var release Release
   182  	err = json.Unmarshal(b, &release)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	return &release, nil
   188  }
   189  
   190  // FindDraftRelease returns the latest draft release that matches tagName.
   191  func FindDraftRelease(httpClient *http.Client, baseRepo ghrepo.Interface, tagName string) (*Release, error) {
   192  	path := fmt.Sprintf("repos/%s/%s/releases", baseRepo.RepoOwner(), baseRepo.RepoName())
   193  	url := ghinstance.RESTPrefix(baseRepo.RepoHost()) + path
   194  
   195  	perPage := 100
   196  	page := 1
   197  	for {
   198  		req, err := http.NewRequest("GET", fmt.Sprintf("%s?per_page=%d&page=%d", url, perPage, page), nil)
   199  		if err != nil {
   200  			return nil, err
   201  		}
   202  
   203  		resp, err := httpClient.Do(req)
   204  		if err != nil {
   205  			return nil, err
   206  		}
   207  		defer resp.Body.Close()
   208  
   209  		if resp.StatusCode > 299 {
   210  			return nil, api.HandleHTTPError(resp)
   211  		}
   212  
   213  		b, err := ioutil.ReadAll(resp.Body)
   214  		if err != nil {
   215  			return nil, err
   216  		}
   217  
   218  		var releases []Release
   219  		err = json.Unmarshal(b, &releases)
   220  		if err != nil {
   221  			return nil, err
   222  		}
   223  
   224  		for _, r := range releases {
   225  			if r.IsDraft && r.TagName == tagName {
   226  				return &r, nil
   227  			}
   228  		}
   229  		//nolint:staticcheck
   230  		break
   231  	}
   232  
   233  	return nil, errors.New("release not found")
   234  }