forked from TrueCloudLab/restic
ff7ef5007e
The ioutil functions are deprecated since Go 1.17 and only wrap another library function. Thus directly call the underlying function. This commit only mechanically replaces the function calls.
166 lines
3.8 KiB
Go
166 lines
3.8 KiB
Go
package selfupdate
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Release collects data about a single release on GitHub.
|
|
type Release struct {
|
|
Name string `json:"name"`
|
|
TagName string `json:"tag_name"`
|
|
Draft bool `json:"draft"`
|
|
PreRelease bool `json:"prerelease"`
|
|
PublishedAt time.Time `json:"published_at"`
|
|
Assets []Asset `json:"assets"`
|
|
|
|
Version string `json:"-"` // set manually in the code
|
|
}
|
|
|
|
// Asset is a file uploaded and attached to a release.
|
|
type Asset struct {
|
|
ID int `json:"id"`
|
|
Name string `json:"name"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
func (r Release) String() string {
|
|
return fmt.Sprintf("%v %v, %d assets",
|
|
r.TagName,
|
|
r.PublishedAt.Local().Format("2006-01-02 15:04:05"),
|
|
len(r.Assets))
|
|
}
|
|
|
|
const githubAPITimeout = 30 * time.Second
|
|
|
|
// githubError is returned by the GitHub API, e.g. for rate-limiting.
|
|
type githubError struct {
|
|
Message string
|
|
}
|
|
|
|
// GitHubLatestRelease uses the GitHub API to get information about the latest
|
|
// release of a repository.
|
|
func GitHubLatestRelease(ctx context.Context, owner, repo string) (Release, error) {
|
|
ctx, cancel := context.WithTimeout(ctx, githubAPITimeout)
|
|
defer cancel()
|
|
|
|
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo)
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return Release{}, err
|
|
}
|
|
|
|
// pin API version 3
|
|
req.Header.Set("Accept", "application/vnd.github.v3+json")
|
|
|
|
res, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return Release{}, err
|
|
}
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
content := res.Header.Get("Content-Type")
|
|
if strings.Contains(content, "application/json") {
|
|
// try to decode error message
|
|
var msg githubError
|
|
jerr := json.NewDecoder(res.Body).Decode(&msg)
|
|
if jerr == nil {
|
|
return Release{}, fmt.Errorf("unexpected status %v (%v) returned, message:\n %v", res.StatusCode, res.Status, msg.Message)
|
|
}
|
|
}
|
|
|
|
_ = res.Body.Close()
|
|
return Release{}, fmt.Errorf("unexpected status %v (%v) returned", res.StatusCode, res.Status)
|
|
}
|
|
|
|
buf, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
_ = res.Body.Close()
|
|
return Release{}, err
|
|
}
|
|
|
|
err = res.Body.Close()
|
|
if err != nil {
|
|
return Release{}, err
|
|
}
|
|
|
|
var release Release
|
|
err = json.Unmarshal(buf, &release)
|
|
if err != nil {
|
|
return Release{}, err
|
|
}
|
|
|
|
if release.TagName == "" {
|
|
return Release{}, errors.New("tag name for latest release is empty")
|
|
}
|
|
|
|
if !strings.HasPrefix(release.TagName, "v") {
|
|
return Release{}, errors.Errorf("tag name %q is invalid, does not start with 'v'", release.TagName)
|
|
}
|
|
|
|
release.Version = release.TagName[1:]
|
|
|
|
return release, nil
|
|
}
|
|
|
|
func getGithubData(ctx context.Context, url string) ([]byte, error) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// request binary data
|
|
req.Header.Set("Accept", "application/octet-stream")
|
|
|
|
res, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("unexpected status %v (%v) returned", res.StatusCode, res.Status)
|
|
}
|
|
|
|
buf, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
_ = res.Body.Close()
|
|
return nil, err
|
|
}
|
|
|
|
err = res.Body.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return buf, nil
|
|
}
|
|
|
|
func getGithubDataFile(ctx context.Context, assets []Asset, suffix string, printf func(string, ...interface{})) (filename string, data []byte, err error) {
|
|
var url string
|
|
for _, a := range assets {
|
|
if strings.HasSuffix(a.Name, suffix) {
|
|
url = a.URL
|
|
filename = a.Name
|
|
break
|
|
}
|
|
}
|
|
|
|
if url == "" {
|
|
return "", nil, fmt.Errorf("unable to find file with suffix %v", suffix)
|
|
}
|
|
|
|
printf("download %v\n", filename)
|
|
data, err = getGithubData(ctx, url)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
return filename, data, nil
|
|
}
|