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 }