github.com/joselitofilho/goreleaser@v0.155.1-0.20210123221854-e4891856c593/internal/client/github.go (about)

     1  package client
     2  
     3  import (
     4  	"crypto/tls"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"os"
     9  	"reflect"
    10  	"strconv"
    11  
    12  	"github.com/apex/log"
    13  	"github.com/google/go-github/v28/github"
    14  	"github.com/goreleaser/goreleaser/internal/artifact"
    15  	"github.com/goreleaser/goreleaser/internal/tmpl"
    16  	"github.com/goreleaser/goreleaser/pkg/config"
    17  	"github.com/goreleaser/goreleaser/pkg/context"
    18  	"golang.org/x/oauth2"
    19  )
    20  
    21  const DefaultGitHubDownloadURL = "https://github.com"
    22  
    23  type githubClient struct {
    24  	client *github.Client
    25  }
    26  
    27  // NewGitHub returns a github client implementation.
    28  func NewGitHub(ctx *context.Context, token string) (Client, error) {
    29  	ts := oauth2.StaticTokenSource(
    30  		&oauth2.Token{AccessToken: token},
    31  	)
    32  	httpClient := oauth2.NewClient(ctx, ts)
    33  	base := httpClient.Transport.(*oauth2.Transport).Base
    34  	if base == nil || reflect.ValueOf(base).IsNil() {
    35  		base = http.DefaultTransport
    36  	}
    37  	// nolint: gosec
    38  	base.(*http.Transport).TLSClientConfig = &tls.Config{
    39  		InsecureSkipVerify: ctx.Config.GitHubURLs.SkipTLSVerify,
    40  	}
    41  	base.(*http.Transport).Proxy = http.ProxyFromEnvironment
    42  	httpClient.Transport.(*oauth2.Transport).Base = base
    43  	client := github.NewClient(httpClient)
    44  	if ctx.Config.GitHubURLs.API != "" {
    45  		api, err := url.Parse(ctx.Config.GitHubURLs.API)
    46  		if err != nil {
    47  			return &githubClient{}, err
    48  		}
    49  		upload, err := url.Parse(ctx.Config.GitHubURLs.Upload)
    50  		if err != nil {
    51  			return &githubClient{}, err
    52  		}
    53  		client.BaseURL = api
    54  		client.UploadURL = upload
    55  	}
    56  
    57  	return &githubClient{client: client}, nil
    58  }
    59  
    60  // CloseMilestone closes a given milestone.
    61  func (c *githubClient) CloseMilestone(ctx *context.Context, repo Repo, title string) error {
    62  	milestone, err := c.getMilestoneByTitle(ctx, repo, title)
    63  
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	if milestone == nil {
    69  		return ErrNoMilestoneFound{Title: title}
    70  	}
    71  
    72  	closedState := "closed"
    73  	milestone.State = &closedState
    74  
    75  	_, _, err = c.client.Issues.EditMilestone(
    76  		ctx,
    77  		repo.Owner,
    78  		repo.Name,
    79  		*milestone.Number,
    80  		milestone,
    81  	)
    82  
    83  	return err
    84  }
    85  
    86  func (c *githubClient) CreateFile(
    87  	ctx *context.Context,
    88  	commitAuthor config.CommitAuthor,
    89  	repo Repo,
    90  	content []byte,
    91  	path,
    92  	message string,
    93  ) error {
    94  	options := &github.RepositoryContentFileOptions{
    95  		Committer: &github.CommitAuthor{
    96  			Name:  github.String(commitAuthor.Name),
    97  			Email: github.String(commitAuthor.Email),
    98  		},
    99  		Content: content,
   100  		Message: github.String(message),
   101  	}
   102  
   103  	file, _, res, err := c.client.Repositories.GetContents(
   104  		ctx,
   105  		repo.Owner,
   106  		repo.Name,
   107  		path,
   108  		&github.RepositoryContentGetOptions{},
   109  	)
   110  	if err != nil && res.StatusCode != 404 {
   111  		return err
   112  	}
   113  
   114  	if res.StatusCode == 404 {
   115  		_, _, err = c.client.Repositories.CreateFile(
   116  			ctx,
   117  			repo.Owner,
   118  			repo.Name,
   119  			path,
   120  			options,
   121  		)
   122  		return err
   123  	}
   124  	options.SHA = file.SHA
   125  	_, _, err = c.client.Repositories.UpdateFile(
   126  		ctx,
   127  		repo.Owner,
   128  		repo.Name,
   129  		path,
   130  		options,
   131  	)
   132  	return err
   133  }
   134  
   135  func (c *githubClient) CreateRelease(ctx *context.Context, body string) (string, error) {
   136  	var release *github.RepositoryRelease
   137  	title, err := tmpl.New(ctx).Apply(ctx.Config.Release.NameTemplate)
   138  	if err != nil {
   139  		return "", err
   140  	}
   141  
   142  	var data = &github.RepositoryRelease{
   143  		Name:       github.String(title),
   144  		TagName:    github.String(ctx.Git.CurrentTag),
   145  		Body:       github.String(body),
   146  		Draft:      github.Bool(ctx.Config.Release.Draft),
   147  		Prerelease: github.Bool(ctx.PreRelease),
   148  	}
   149  	release, _, err = c.client.Repositories.GetReleaseByTag(
   150  		ctx,
   151  		ctx.Config.Release.GitHub.Owner,
   152  		ctx.Config.Release.GitHub.Name,
   153  		ctx.Git.CurrentTag,
   154  	)
   155  	if err != nil {
   156  		release, _, err = c.client.Repositories.CreateRelease(
   157  			ctx,
   158  			ctx.Config.Release.GitHub.Owner,
   159  			ctx.Config.Release.GitHub.Name,
   160  			data,
   161  		)
   162  	} else {
   163  		// keep the pre-existing release notes
   164  		if release.GetBody() != "" {
   165  			data.Body = release.Body
   166  		}
   167  		release, _, err = c.client.Repositories.EditRelease(
   168  			ctx,
   169  			ctx.Config.Release.GitHub.Owner,
   170  			ctx.Config.Release.GitHub.Name,
   171  			release.GetID(),
   172  			data,
   173  		)
   174  	}
   175  	log.WithField("url", release.GetHTMLURL()).Info("release updated")
   176  	githubReleaseID := strconv.FormatInt(release.GetID(), 10)
   177  	return githubReleaseID, err
   178  }
   179  
   180  func (c *githubClient) ReleaseURLTemplate(ctx *context.Context) (string, error) {
   181  	return fmt.Sprintf(
   182  		"%s/%s/%s/releases/download/{{ .Tag }}/{{ .ArtifactName }}",
   183  		ctx.Config.GitHubURLs.Download,
   184  		ctx.Config.Release.GitHub.Owner,
   185  		ctx.Config.Release.GitHub.Name,
   186  	), nil
   187  }
   188  
   189  func (c *githubClient) Upload(
   190  	ctx *context.Context,
   191  	releaseID string,
   192  	artifact *artifact.Artifact,
   193  	file *os.File,
   194  ) error {
   195  	githubReleaseID, err := strconv.ParseInt(releaseID, 10, 64)
   196  	if err != nil {
   197  		return err
   198  	}
   199  	_, resp, err := c.client.Repositories.UploadReleaseAsset(
   200  		ctx,
   201  		ctx.Config.Release.GitHub.Owner,
   202  		ctx.Config.Release.GitHub.Name,
   203  		githubReleaseID,
   204  		&github.UploadOptions{
   205  			Name: artifact.Name,
   206  		},
   207  		file,
   208  	)
   209  	if err == nil {
   210  		return nil
   211  	}
   212  	if resp != nil && resp.StatusCode == 422 {
   213  		return err
   214  	}
   215  	return RetriableError{err}
   216  }
   217  
   218  // getMilestoneByTitle returns a milestone by title.
   219  func (c *githubClient) getMilestoneByTitle(ctx *context.Context, repo Repo, title string) (*github.Milestone, error) {
   220  	// The GitHub API/SDK does not provide lookup by title functionality currently.
   221  	opts := &github.MilestoneListOptions{
   222  		ListOptions: github.ListOptions{PerPage: 100},
   223  	}
   224  
   225  	for {
   226  		milestones, resp, err := c.client.Issues.ListMilestones(
   227  			ctx,
   228  			repo.Owner,
   229  			repo.Name,
   230  			opts,
   231  		)
   232  
   233  		if err != nil {
   234  			return nil, err
   235  		}
   236  
   237  		for _, m := range milestones {
   238  			if m != nil && m.Title != nil && *m.Title == title {
   239  				return m, nil
   240  			}
   241  		}
   242  
   243  		if resp.NextPage == 0 {
   244  			break
   245  		}
   246  
   247  		opts.Page = resp.NextPage
   248  	}
   249  
   250  	return nil, nil
   251  }