github.com/ahmet2mir/goreleaser@v0.180.3-0.20210927151101-8e5ee5a9b8c5/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/v35/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  
    33  	httpClient := oauth2.NewClient(ctx, ts)
    34  	base := httpClient.Transport.(*oauth2.Transport).Base
    35  	if base == nil || reflect.ValueOf(base).IsNil() {
    36  		base = http.DefaultTransport
    37  	}
    38  	// nolint: gosec
    39  	base.(*http.Transport).TLSClientConfig = &tls.Config{
    40  		InsecureSkipVerify: ctx.Config.GitHubURLs.SkipTLSVerify,
    41  	}
    42  	base.(*http.Transport).Proxy = http.ProxyFromEnvironment
    43  	httpClient.Transport.(*oauth2.Transport).Base = base
    44  
    45  	client := github.NewClient(httpClient)
    46  	err := overrideGitHubClientAPI(ctx, client)
    47  	if err != nil {
    48  		return &githubClient{}, err
    49  	}
    50  
    51  	return &githubClient{client: client}, nil
    52  }
    53  
    54  // CloseMilestone closes a given milestone.
    55  func (c *githubClient) CloseMilestone(ctx *context.Context, repo Repo, title string) error {
    56  	milestone, err := c.getMilestoneByTitle(ctx, repo, title)
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	if milestone == nil {
    62  		return ErrNoMilestoneFound{Title: title}
    63  	}
    64  
    65  	closedState := "closed"
    66  	milestone.State = &closedState
    67  
    68  	_, _, err = c.client.Issues.EditMilestone(
    69  		ctx,
    70  		repo.Owner,
    71  		repo.Name,
    72  		*milestone.Number,
    73  		milestone,
    74  	)
    75  
    76  	return err
    77  }
    78  
    79  func (c *githubClient) CreateFile(
    80  	ctx *context.Context,
    81  	commitAuthor config.CommitAuthor,
    82  	repo Repo,
    83  	content []byte,
    84  	path,
    85  	message string,
    86  ) error {
    87  	options := &github.RepositoryContentFileOptions{
    88  		Committer: &github.CommitAuthor{
    89  			Name:  github.String(commitAuthor.Name),
    90  			Email: github.String(commitAuthor.Email),
    91  		},
    92  		Content: content,
    93  		Message: github.String(message),
    94  	}
    95  
    96  	file, _, res, err := c.client.Repositories.GetContents(
    97  		ctx,
    98  		repo.Owner,
    99  		repo.Name,
   100  		path,
   101  		&github.RepositoryContentGetOptions{},
   102  	)
   103  	if err != nil && res.StatusCode != 404 {
   104  		return err
   105  	}
   106  
   107  	if res.StatusCode == 404 {
   108  		_, _, err = c.client.Repositories.CreateFile(
   109  			ctx,
   110  			repo.Owner,
   111  			repo.Name,
   112  			path,
   113  			options,
   114  		)
   115  		return err
   116  	}
   117  	options.SHA = file.SHA
   118  	_, _, err = c.client.Repositories.UpdateFile(
   119  		ctx,
   120  		repo.Owner,
   121  		repo.Name,
   122  		path,
   123  		options,
   124  	)
   125  	return err
   126  }
   127  
   128  func (c *githubClient) CreateRelease(ctx *context.Context, body string) (string, error) {
   129  	var release *github.RepositoryRelease
   130  	title, err := tmpl.New(ctx).Apply(ctx.Config.Release.NameTemplate)
   131  	if err != nil {
   132  		return "", err
   133  	}
   134  
   135  	data := &github.RepositoryRelease{
   136  		Name:       github.String(title),
   137  		TagName:    github.String(ctx.Git.CurrentTag),
   138  		Body:       github.String(body),
   139  		Draft:      github.Bool(ctx.Config.Release.Draft),
   140  		Prerelease: github.Bool(ctx.PreRelease),
   141  	}
   142  	if ctx.Config.Release.DiscussionCategoryName != "" {
   143  		data.DiscussionCategoryName = github.String(ctx.Config.Release.DiscussionCategoryName)
   144  	}
   145  
   146  	release, _, err = c.client.Repositories.GetReleaseByTag(
   147  		ctx,
   148  		ctx.Config.Release.GitHub.Owner,
   149  		ctx.Config.Release.GitHub.Name,
   150  		ctx.Git.CurrentTag,
   151  	)
   152  	if err != nil {
   153  		release, _, err = c.client.Repositories.CreateRelease(
   154  			ctx,
   155  			ctx.Config.Release.GitHub.Owner,
   156  			ctx.Config.Release.GitHub.Name,
   157  			data,
   158  		)
   159  	} else {
   160  		// keep the pre-existing release notes
   161  		if release.GetBody() != "" {
   162  			data.Body = release.Body
   163  		}
   164  		release, _, err = c.client.Repositories.EditRelease(
   165  			ctx,
   166  			ctx.Config.Release.GitHub.Owner,
   167  			ctx.Config.Release.GitHub.Name,
   168  			release.GetID(),
   169  			data,
   170  		)
   171  	}
   172  	log.WithField("url", release.GetHTMLURL()).Info("release updated")
   173  	githubReleaseID := strconv.FormatInt(release.GetID(), 10)
   174  	return githubReleaseID, err
   175  }
   176  
   177  func (c *githubClient) ReleaseURLTemplate(ctx *context.Context) (string, error) {
   178  	downloadURL, err := tmpl.New(ctx).Apply(ctx.Config.GitHubURLs.Download)
   179  	if err != nil {
   180  		return "", fmt.Errorf("templating GitHub download URL: %w", err)
   181  	}
   182  
   183  	return fmt.Sprintf(
   184  		"%s/%s/%s/releases/download/{{ .Tag }}/{{ .ArtifactName }}",
   185  		downloadURL,
   186  		ctx.Config.Release.GitHub.Owner,
   187  		ctx.Config.Release.GitHub.Name,
   188  	), nil
   189  }
   190  
   191  func (c *githubClient) Upload(
   192  	ctx *context.Context,
   193  	releaseID string,
   194  	artifact *artifact.Artifact,
   195  	file *os.File,
   196  ) error {
   197  	githubReleaseID, err := strconv.ParseInt(releaseID, 10, 64)
   198  	if err != nil {
   199  		return err
   200  	}
   201  	_, resp, err := c.client.Repositories.UploadReleaseAsset(
   202  		ctx,
   203  		ctx.Config.Release.GitHub.Owner,
   204  		ctx.Config.Release.GitHub.Name,
   205  		githubReleaseID,
   206  		&github.UploadOptions{
   207  			Name: artifact.Name,
   208  		},
   209  		file,
   210  	)
   211  	if err == nil {
   212  		return nil
   213  	}
   214  	if resp != nil && resp.StatusCode == 422 {
   215  		return err
   216  	}
   217  	return RetriableError{err}
   218  }
   219  
   220  // getMilestoneByTitle returns a milestone by title.
   221  func (c *githubClient) getMilestoneByTitle(ctx *context.Context, repo Repo, title string) (*github.Milestone, error) {
   222  	// The GitHub API/SDK does not provide lookup by title functionality currently.
   223  	opts := &github.MilestoneListOptions{
   224  		ListOptions: github.ListOptions{PerPage: 100},
   225  	}
   226  
   227  	for {
   228  		milestones, resp, err := c.client.Issues.ListMilestones(
   229  			ctx,
   230  			repo.Owner,
   231  			repo.Name,
   232  			opts,
   233  		)
   234  		if err != nil {
   235  			return nil, err
   236  		}
   237  
   238  		for _, m := range milestones {
   239  			if m != nil && m.Title != nil && *m.Title == title {
   240  				return m, nil
   241  			}
   242  		}
   243  
   244  		if resp.NextPage == 0 {
   245  			break
   246  		}
   247  
   248  		opts.Page = resp.NextPage
   249  	}
   250  
   251  	return nil, nil
   252  }
   253  
   254  func overrideGitHubClientAPI(ctx *context.Context, client *github.Client) error {
   255  	if ctx.Config.GitHubURLs.API == "" {
   256  		return nil
   257  	}
   258  
   259  	apiURL, err := tmpl.New(ctx).Apply(ctx.Config.GitHubURLs.API)
   260  	if err != nil {
   261  		return fmt.Errorf("templating GitHub API URL: %w", err)
   262  	}
   263  	api, err := url.Parse(apiURL)
   264  	if err != nil {
   265  		return err
   266  	}
   267  
   268  	uploadURL, err := tmpl.New(ctx).Apply(ctx.Config.GitHubURLs.Upload)
   269  	if err != nil {
   270  		return fmt.Errorf("templating GitHub upload URL: %w", err)
   271  	}
   272  	upload, err := url.Parse(uploadURL)
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	client.BaseURL = api
   278  	client.UploadURL = upload
   279  
   280  	return nil
   281  }