2017-05-13 18:09:42 -03:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
2019-01-17 11:25:57 -02:00
|
|
|
"crypto/tls"
|
2020-07-06 14:48:17 +01:00
|
|
|
"fmt"
|
2019-01-17 11:25:57 -02:00
|
|
|
"net/http"
|
2017-09-23 19:42:07 +02:00
|
|
|
"net/url"
|
2017-05-13 18:09:42 -03:00
|
|
|
"os"
|
2019-08-10 21:35:20 +08:00
|
|
|
"reflect"
|
2019-06-29 16:02:40 +02:00
|
|
|
"strconv"
|
2017-05-13 18:09:42 -03:00
|
|
|
|
2017-06-22 00:09:14 -03:00
|
|
|
"github.com/apex/log"
|
2021-04-21 13:43:59 -03:00
|
|
|
"github.com/google/go-github/v35/github"
|
2019-08-13 20:28:03 +02:00
|
|
|
"github.com/goreleaser/goreleaser/internal/artifact"
|
2018-07-08 20:47:30 -07:00
|
|
|
"github.com/goreleaser/goreleaser/internal/tmpl"
|
2018-08-14 23:50:20 -03:00
|
|
|
"github.com/goreleaser/goreleaser/pkg/config"
|
|
|
|
"github.com/goreleaser/goreleaser/pkg/context"
|
2017-05-13 18:09:42 -03:00
|
|
|
"golang.org/x/oauth2"
|
|
|
|
)
|
|
|
|
|
2020-07-06 14:48:17 +01:00
|
|
|
const DefaultGitHubDownloadURL = "https://github.com"
|
|
|
|
|
2017-05-13 18:09:42 -03:00
|
|
|
type githubClient struct {
|
|
|
|
client *github.Client
|
|
|
|
}
|
|
|
|
|
2020-05-26 00:48:10 -03:00
|
|
|
// NewGitHub returns a github client implementation.
|
2020-07-06 21:12:41 +01:00
|
|
|
func NewGitHub(ctx *context.Context, token string) (Client, error) {
|
2017-05-13 18:09:42 -03:00
|
|
|
ts := oauth2.StaticTokenSource(
|
2020-07-06 21:12:41 +01:00
|
|
|
&oauth2.Token{AccessToken: token},
|
2017-05-13 18:09:42 -03:00
|
|
|
)
|
2019-01-17 11:25:57 -02:00
|
|
|
httpClient := oauth2.NewClient(ctx, ts)
|
2019-01-17 18:03:36 -02:00
|
|
|
base := httpClient.Transport.(*oauth2.Transport).Base
|
2019-08-10 21:35:20 +08:00
|
|
|
if base == nil || reflect.ValueOf(base).IsNil() {
|
2019-01-17 18:03:36 -02:00
|
|
|
base = http.DefaultTransport
|
|
|
|
}
|
2019-01-19 18:14:56 -02:00
|
|
|
// nolint: gosec
|
2019-01-17 18:03:36 -02:00
|
|
|
base.(*http.Transport).TLSClientConfig = &tls.Config{
|
2019-01-17 11:28:10 -02:00
|
|
|
InsecureSkipVerify: ctx.Config.GitHubURLs.SkipTLSVerify,
|
2019-01-17 11:25:57 -02:00
|
|
|
}
|
2020-11-05 05:16:25 -03:00
|
|
|
base.(*http.Transport).Proxy = http.ProxyFromEnvironment
|
2019-01-17 18:03:36 -02:00
|
|
|
httpClient.Transport.(*oauth2.Transport).Base = base
|
2019-01-17 11:25:57 -02:00
|
|
|
client := github.NewClient(httpClient)
|
2017-09-26 18:33:22 -03:00
|
|
|
if ctx.Config.GitHubURLs.API != "" {
|
|
|
|
api, err := url.Parse(ctx.Config.GitHubURLs.API)
|
2017-09-23 19:42:07 +02:00
|
|
|
if err != nil {
|
|
|
|
return &githubClient{}, err
|
|
|
|
}
|
2017-09-26 18:33:22 -03:00
|
|
|
upload, err := url.Parse(ctx.Config.GitHubURLs.Upload)
|
2017-09-23 19:42:07 +02:00
|
|
|
if err != nil {
|
|
|
|
return &githubClient{}, err
|
|
|
|
}
|
2017-09-26 18:33:22 -03:00
|
|
|
client.BaseURL = api
|
|
|
|
client.UploadURL = upload
|
2017-09-23 19:42:07 +02:00
|
|
|
}
|
|
|
|
|
2018-06-19 15:53:14 -03:00
|
|
|
return &githubClient{client: client}, nil
|
2017-05-13 18:09:42 -03:00
|
|
|
}
|
|
|
|
|
2020-07-09 16:40:37 -04:00
|
|
|
// CloseMilestone closes a given milestone.
|
|
|
|
func (c *githubClient) CloseMilestone(ctx *context.Context, repo Repo, title string) error {
|
|
|
|
milestone, err := c.getMilestoneByTitle(ctx, repo, title)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if milestone == nil {
|
|
|
|
return ErrNoMilestoneFound{Title: title}
|
|
|
|
}
|
|
|
|
|
|
|
|
closedState := "closed"
|
|
|
|
milestone.State = &closedState
|
|
|
|
|
|
|
|
_, _, err = c.client.Issues.EditMilestone(
|
|
|
|
ctx,
|
|
|
|
repo.Owner,
|
|
|
|
repo.Name,
|
|
|
|
*milestone.Number,
|
|
|
|
milestone,
|
|
|
|
)
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-05-13 18:09:42 -03:00
|
|
|
func (c *githubClient) CreateFile(
|
|
|
|
ctx *context.Context,
|
2018-02-09 13:35:51 +00:00
|
|
|
commitAuthor config.CommitAuthor,
|
2020-07-06 21:12:41 +01:00
|
|
|
repo Repo,
|
2019-06-26 14:12:33 -03:00
|
|
|
content []byte,
|
2019-03-31 20:11:35 +03:00
|
|
|
path,
|
2018-03-10 14:13:00 -03:00
|
|
|
message string,
|
2018-02-16 10:35:44 -02:00
|
|
|
) error {
|
2017-05-13 18:09:42 -03:00
|
|
|
options := &github.RepositoryContentFileOptions{
|
|
|
|
Committer: &github.CommitAuthor{
|
2018-02-09 13:43:00 +00:00
|
|
|
Name: github.String(commitAuthor.Name),
|
|
|
|
Email: github.String(commitAuthor.Email),
|
2017-05-13 18:09:42 -03:00
|
|
|
},
|
2019-06-26 14:12:33 -03:00
|
|
|
Content: content,
|
2018-03-10 14:13:00 -03:00
|
|
|
Message: github.String(message),
|
2017-05-13 18:09:42 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
file, _, res, err := c.client.Repositories.GetContents(
|
|
|
|
ctx,
|
2018-02-09 13:43:00 +00:00
|
|
|
repo.Owner,
|
|
|
|
repo.Name,
|
2017-05-13 18:09:42 -03:00
|
|
|
path,
|
|
|
|
&github.RepositoryContentGetOptions{},
|
|
|
|
)
|
2018-02-10 13:14:52 +00:00
|
|
|
if err != nil && res.StatusCode != 404 {
|
2018-02-16 10:35:44 -02:00
|
|
|
return err
|
2018-02-10 13:14:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if res.StatusCode == 404 {
|
2017-05-13 18:09:42 -03:00
|
|
|
_, _, err = c.client.Repositories.CreateFile(
|
|
|
|
ctx,
|
2018-02-09 13:43:00 +00:00
|
|
|
repo.Owner,
|
|
|
|
repo.Name,
|
2017-05-13 18:09:42 -03:00
|
|
|
path,
|
|
|
|
options,
|
|
|
|
)
|
2018-02-16 10:35:44 -02:00
|
|
|
return err
|
2017-05-13 18:09:42 -03:00
|
|
|
}
|
2018-02-16 10:35:44 -02:00
|
|
|
options.SHA = file.SHA
|
|
|
|
_, _, err = c.client.Repositories.UpdateFile(
|
|
|
|
ctx,
|
|
|
|
repo.Owner,
|
|
|
|
repo.Name,
|
|
|
|
path,
|
|
|
|
options,
|
|
|
|
)
|
|
|
|
return err
|
2017-05-13 18:09:42 -03:00
|
|
|
}
|
|
|
|
|
2019-06-29 16:02:40 +02:00
|
|
|
func (c *githubClient) CreateRelease(ctx *context.Context, body string) (string, error) {
|
2017-05-13 18:09:42 -03:00
|
|
|
var release *github.RepositoryRelease
|
2018-07-08 20:47:30 -07:00
|
|
|
title, err := tmpl.New(ctx).Apply(ctx.Config.Release.NameTemplate)
|
2017-10-07 04:31:14 -05:00
|
|
|
if err != nil {
|
2019-06-29 16:02:40 +02:00
|
|
|
return "", err
|
2017-10-07 04:31:14 -05:00
|
|
|
}
|
2018-11-29 19:42:14 +01:00
|
|
|
|
2021-04-21 13:43:59 -03:00
|
|
|
data := &github.RepositoryRelease{
|
2021-04-25 14:20:49 -03:00
|
|
|
Name: github.String(title),
|
|
|
|
TagName: github.String(ctx.Git.CurrentTag),
|
|
|
|
Body: github.String(body),
|
|
|
|
Draft: github.Bool(ctx.Config.Release.Draft),
|
|
|
|
Prerelease: github.Bool(ctx.PreRelease),
|
2017-05-13 18:09:42 -03:00
|
|
|
}
|
2021-04-21 15:09:27 -04:00
|
|
|
if ctx.Config.Release.DiscussionCategoryName != "" {
|
|
|
|
data.DiscussionCategoryName = github.String(ctx.Config.Release.DiscussionCategoryName)
|
|
|
|
}
|
2021-04-25 14:20:49 -03:00
|
|
|
|
2017-05-13 18:09:42 -03:00
|
|
|
release, _, err = c.client.Repositories.GetReleaseByTag(
|
|
|
|
ctx,
|
|
|
|
ctx.Config.Release.GitHub.Owner,
|
|
|
|
ctx.Config.Release.GitHub.Name,
|
|
|
|
ctx.Git.CurrentTag,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
release, _, err = c.client.Repositories.CreateRelease(
|
|
|
|
ctx,
|
|
|
|
ctx.Config.Release.GitHub.Owner,
|
|
|
|
ctx.Config.Release.GitHub.Name,
|
|
|
|
data,
|
|
|
|
)
|
|
|
|
} else {
|
2019-01-19 18:35:34 -02:00
|
|
|
// keep the pre-existing release notes
|
|
|
|
if release.GetBody() != "" {
|
|
|
|
data.Body = release.Body
|
|
|
|
}
|
2017-05-13 18:09:42 -03:00
|
|
|
release, _, err = c.client.Repositories.EditRelease(
|
|
|
|
ctx,
|
|
|
|
ctx.Config.Release.GitHub.Owner,
|
|
|
|
ctx.Config.Release.GitHub.Name,
|
|
|
|
release.GetID(),
|
|
|
|
data,
|
|
|
|
)
|
|
|
|
}
|
2017-06-22 10:47:34 -03:00
|
|
|
log.WithField("url", release.GetHTMLURL()).Info("release updated")
|
2019-06-29 16:02:40 +02:00
|
|
|
githubReleaseID := strconv.FormatInt(release.GetID(), 10)
|
|
|
|
return githubReleaseID, err
|
2017-05-13 18:09:42 -03:00
|
|
|
}
|
|
|
|
|
2020-07-06 14:48:17 +01:00
|
|
|
func (c *githubClient) ReleaseURLTemplate(ctx *context.Context) (string, error) {
|
|
|
|
return fmt.Sprintf(
|
|
|
|
"%s/%s/%s/releases/download/{{ .Tag }}/{{ .ArtifactName }}",
|
|
|
|
ctx.Config.GitHubURLs.Download,
|
|
|
|
ctx.Config.Release.GitHub.Owner,
|
|
|
|
ctx.Config.Release.GitHub.Name,
|
|
|
|
), nil
|
|
|
|
}
|
|
|
|
|
2017-05-13 18:09:42 -03:00
|
|
|
func (c *githubClient) Upload(
|
|
|
|
ctx *context.Context,
|
2019-06-29 16:02:40 +02:00
|
|
|
releaseID string,
|
2019-08-13 20:28:03 +02:00
|
|
|
artifact *artifact.Artifact,
|
2017-05-13 18:09:42 -03:00
|
|
|
file *os.File,
|
2018-02-16 10:35:44 -02:00
|
|
|
) error {
|
2019-06-29 16:02:40 +02:00
|
|
|
githubReleaseID, err := strconv.ParseInt(releaseID, 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-03-22 17:03:31 -03:00
|
|
|
_, resp, err := c.client.Repositories.UploadReleaseAsset(
|
2017-05-13 18:09:42 -03:00
|
|
|
ctx,
|
|
|
|
ctx.Config.Release.GitHub.Owner,
|
|
|
|
ctx.Config.Release.GitHub.Name,
|
2019-06-29 16:02:40 +02:00
|
|
|
githubReleaseID,
|
2017-05-13 18:09:42 -03:00
|
|
|
&github.UploadOptions{
|
2019-08-13 20:28:03 +02:00
|
|
|
Name: artifact.Name,
|
2017-05-13 18:09:42 -03:00
|
|
|
},
|
|
|
|
file,
|
|
|
|
)
|
2020-03-31 10:34:06 -03:00
|
|
|
if err == nil {
|
|
|
|
return nil
|
2020-03-22 17:03:31 -03:00
|
|
|
}
|
2020-03-31 10:34:06 -03:00
|
|
|
if resp != nil && resp.StatusCode == 422 {
|
|
|
|
return err
|
2020-03-31 10:30:16 -03:00
|
|
|
}
|
2020-03-31 10:34:06 -03:00
|
|
|
return RetriableError{err}
|
2017-05-13 18:09:42 -03:00
|
|
|
}
|
2020-07-09 16:40:37 -04:00
|
|
|
|
|
|
|
// getMilestoneByTitle returns a milestone by title.
|
|
|
|
func (c *githubClient) getMilestoneByTitle(ctx *context.Context, repo Repo, title string) (*github.Milestone, error) {
|
|
|
|
// The GitHub API/SDK does not provide lookup by title functionality currently.
|
|
|
|
opts := &github.MilestoneListOptions{
|
|
|
|
ListOptions: github.ListOptions{PerPage: 100},
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
milestones, resp, err := c.client.Issues.ListMilestones(
|
|
|
|
ctx,
|
|
|
|
repo.Owner,
|
|
|
|
repo.Name,
|
|
|
|
opts,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, m := range milestones {
|
|
|
|
if m != nil && m.Title != nil && *m.Title == title {
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.NextPage == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
opts.Page = resp.NextPage
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|