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 }