1*9bb1b549SSpandan Das// Copyright 2021 The Bazel Authors. All rights reserved. 2*9bb1b549SSpandan Das// 3*9bb1b549SSpandan Das// Licensed under the Apache License, Version 2.0 (the "License"); 4*9bb1b549SSpandan Das// you may not use this file except in compliance with the License. 5*9bb1b549SSpandan Das// You may obtain a copy of the License at 6*9bb1b549SSpandan Das// 7*9bb1b549SSpandan Das// http://www.apache.org/licenses/LICENSE-2.0 8*9bb1b549SSpandan Das// 9*9bb1b549SSpandan Das// Unless required by applicable law or agreed to in writing, software 10*9bb1b549SSpandan Das// distributed under the License is distributed on an "AS IS" BASIS, 11*9bb1b549SSpandan Das// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*9bb1b549SSpandan Das// See the License for the specific language governing permissions and 13*9bb1b549SSpandan Das// limitations under the License. 14*9bb1b549SSpandan Das 15*9bb1b549SSpandan Daspackage main 16*9bb1b549SSpandan Das 17*9bb1b549SSpandan Dasimport ( 18*9bb1b549SSpandan Das "bytes" 19*9bb1b549SSpandan Das "context" 20*9bb1b549SSpandan Das "errors" 21*9bb1b549SSpandan Das "fmt" 22*9bb1b549SSpandan Das "os" 23*9bb1b549SSpandan Das "strings" 24*9bb1b549SSpandan Das 25*9bb1b549SSpandan Das "github.com/google/go-github/v36/github" 26*9bb1b549SSpandan Das) 27*9bb1b549SSpandan Das 28*9bb1b549SSpandan Dastype githubClient struct { 29*9bb1b549SSpandan Das *github.Client 30*9bb1b549SSpandan Das} 31*9bb1b549SSpandan Das 32*9bb1b549SSpandan Dasfunc (gh *githubClient) listTags(ctx context.Context, org, repo string) (_ []*github.RepositoryTag, err error) { 33*9bb1b549SSpandan Das defer func() { 34*9bb1b549SSpandan Das if err != nil { 35*9bb1b549SSpandan Das err = fmt.Errorf("listing tags in github.com/%s/%s: %w", org, repo, err) 36*9bb1b549SSpandan Das } 37*9bb1b549SSpandan Das }() 38*9bb1b549SSpandan Das 39*9bb1b549SSpandan Das var allTags []*github.RepositoryTag 40*9bb1b549SSpandan Das err = gh.listPages(func(opts *github.ListOptions) (*github.Response, error) { 41*9bb1b549SSpandan Das tags, resp, err := gh.Repositories.ListTags(ctx, org, repo, opts) 42*9bb1b549SSpandan Das if err != nil { 43*9bb1b549SSpandan Das return nil, err 44*9bb1b549SSpandan Das } 45*9bb1b549SSpandan Das allTags = append(allTags, tags...) 46*9bb1b549SSpandan Das return resp, nil 47*9bb1b549SSpandan Das }) 48*9bb1b549SSpandan Das if err != nil { 49*9bb1b549SSpandan Das return nil, err 50*9bb1b549SSpandan Das } 51*9bb1b549SSpandan Das return allTags, nil 52*9bb1b549SSpandan Das} 53*9bb1b549SSpandan Das 54*9bb1b549SSpandan Dasfunc (gh *githubClient) listReleases(ctx context.Context, org, repo string) (_ []*github.RepositoryRelease, err error) { 55*9bb1b549SSpandan Das defer func() { 56*9bb1b549SSpandan Das if err != nil { 57*9bb1b549SSpandan Das err = fmt.Errorf("listing releases in github.com/%s/%s: %w", org, repo, err) 58*9bb1b549SSpandan Das } 59*9bb1b549SSpandan Das }() 60*9bb1b549SSpandan Das 61*9bb1b549SSpandan Das var allReleases []*github.RepositoryRelease 62*9bb1b549SSpandan Das err = gh.listPages(func(opts *github.ListOptions) (*github.Response, error) { 63*9bb1b549SSpandan Das releases, resp, err := gh.Repositories.ListReleases(ctx, org, repo, opts) 64*9bb1b549SSpandan Das if err != nil { 65*9bb1b549SSpandan Das return nil, err 66*9bb1b549SSpandan Das } 67*9bb1b549SSpandan Das allReleases = append(allReleases, releases...) 68*9bb1b549SSpandan Das return resp, nil 69*9bb1b549SSpandan Das }) 70*9bb1b549SSpandan Das if err != nil { 71*9bb1b549SSpandan Das return nil, err 72*9bb1b549SSpandan Das } 73*9bb1b549SSpandan Das return allReleases, nil 74*9bb1b549SSpandan Das} 75*9bb1b549SSpandan Das 76*9bb1b549SSpandan Das// getReleaseByTagIncludingDraft is like 77*9bb1b549SSpandan Das// github.RepositoriesService.GetReleaseByTag, but it also considers draft 78*9bb1b549SSpandan Das// releases that aren't tagged yet. 79*9bb1b549SSpandan Dasfunc (gh *githubClient) getReleaseByTagIncludingDraft(ctx context.Context, org, repo, tag string) (*github.RepositoryRelease, error) { 80*9bb1b549SSpandan Das releases, err := gh.listReleases(ctx, org, repo) 81*9bb1b549SSpandan Das if err != nil { 82*9bb1b549SSpandan Das return nil, err 83*9bb1b549SSpandan Das } 84*9bb1b549SSpandan Das for _, release := range releases { 85*9bb1b549SSpandan Das if release.GetTagName() == tag { 86*9bb1b549SSpandan Das return release, nil 87*9bb1b549SSpandan Das } 88*9bb1b549SSpandan Das } 89*9bb1b549SSpandan Das return nil, errReleaseNotFound 90*9bb1b549SSpandan Das} 91*9bb1b549SSpandan Das 92*9bb1b549SSpandan Dasvar errReleaseNotFound = errors.New("release not found") 93*9bb1b549SSpandan Das 94*9bb1b549SSpandan Das// githubListPages calls fn repeatedly to get all pages of a large result. 95*9bb1b549SSpandan Das// This is useful for fetching all tags or all comments or something similar. 96*9bb1b549SSpandan Dasfunc (gh *githubClient) listPages(fn func(opt *github.ListOptions) (*github.Response, error)) error { 97*9bb1b549SSpandan Das opt := &github.ListOptions{PerPage: 50} 98*9bb1b549SSpandan Das for { 99*9bb1b549SSpandan Das resp, err := fn(opt) 100*9bb1b549SSpandan Das if err != nil { 101*9bb1b549SSpandan Das return err 102*9bb1b549SSpandan Das } 103*9bb1b549SSpandan Das if resp.NextPage == 0 { 104*9bb1b549SSpandan Das return nil 105*9bb1b549SSpandan Das } 106*9bb1b549SSpandan Das opt.Page = resp.NextPage 107*9bb1b549SSpandan Das } 108*9bb1b549SSpandan Das} 109*9bb1b549SSpandan Das 110*9bb1b549SSpandan Das// githubTokenFlag is used to find a GitHub personal access token on the 111*9bb1b549SSpandan Das// command line. It accepts a raw token or a path to a file containing a token. 112*9bb1b549SSpandan Dastype githubTokenFlag string 113*9bb1b549SSpandan Das 114*9bb1b549SSpandan Dasfunc (f *githubTokenFlag) Set(v string) error { 115*9bb1b549SSpandan Das if strings.HasPrefix(v, "ghp_") { 116*9bb1b549SSpandan Das *(*string)(f) = v 117*9bb1b549SSpandan Das return nil 118*9bb1b549SSpandan Das } 119*9bb1b549SSpandan Das data, err := os.ReadFile(v) 120*9bb1b549SSpandan Das if err != nil { 121*9bb1b549SSpandan Das return fmt.Errorf("reading GitHub token: %w", err) 122*9bb1b549SSpandan Das } 123*9bb1b549SSpandan Das *(*string)(f) = string(bytes.TrimSpace(data)) 124*9bb1b549SSpandan Das return nil 125*9bb1b549SSpandan Das} 126*9bb1b549SSpandan Das 127*9bb1b549SSpandan Dasfunc (f *githubTokenFlag) String() string { 128*9bb1b549SSpandan Das if f == nil { 129*9bb1b549SSpandan Das return "" 130*9bb1b549SSpandan Das } 131*9bb1b549SSpandan Das return string(*f) 132*9bb1b549SSpandan Das} 133