From fe9e9c763e3a8174c966d55cc14dce197de87d1b Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Mon, 30 May 2022 07:35:00 +0900 Subject: [PATCH] feat: use GitHub GraphQL API --- go.mod | 2 + go.sum | 4 ++ pkg/cli/exec.go | 3 +- pkg/cli/generate.go | 3 +- pkg/cli/install.go | 3 +- pkg/cli/list.go | 3 +- pkg/cli/which.go | 3 +- pkg/controller/generate/generate.go | 46 +++++++++------------- pkg/controller/generate/generate_test.go | 4 +- pkg/controller/wire.go | 24 ++++++------ pkg/controller/wire_gen.go | 50 +++++++++++++++--------- pkg/github/github.go | 21 +++++++--- pkg/github/github_test.go | 2 +- pkg/github/mock_v4.go | 19 +++++++++ pkg/github/v4.go | 48 +++++++++++++++++++++++ 15 files changed, 160 insertions(+), 75 deletions(-) create mode 100644 pkg/github/mock_v4.go create mode 100644 pkg/github/v4.go diff --git a/go.mod b/go.mod index 855b4e085..3022b28cc 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,8 @@ require ( github.com/invopop/jsonschema v0.4.0 github.com/ktr0731/go-fuzzyfinder v0.6.0 github.com/mholt/archiver/v3 v3.5.1 + github.com/shurcooL/githubv4 v0.0.0-20220520033151-0b4e3294ff00 + github.com/shurcooL/graphql v0.0.0-20220520033453-bdb1221e171e // indirect github.com/sirupsen/logrus v1.8.1 github.com/spf13/afero v1.8.2 github.com/suzuki-shunsuke/flute v1.0.1 diff --git a/go.sum b/go.sum index 6feec338b..d452219d7 100644 --- a/go.sum +++ b/go.sum @@ -256,6 +256,10 @@ github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu github.com/scylladb/go-set v1.0.2/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/githubv4 v0.0.0-20220520033151-0b4e3294ff00 h1:fiFvD4lT0aWjuuAb64LlZ/67v87m+Kc9Qsu5cMFNK0w= +github.com/shurcooL/githubv4 v0.0.0-20220520033151-0b4e3294ff00/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= +github.com/shurcooL/graphql v0.0.0-20220520033453-bdb1221e171e h1:dmM59/+RIH6bO/gjmUgaJwdyDhAvZkHgA5OJUcoUyGU= +github.com/shurcooL/graphql v0.0.0-20220520033453-bdb1221e171e/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= diff --git a/pkg/cli/exec.go b/pkg/cli/exec.go index 31ff2aead..d1c7afd17 100644 --- a/pkg/cli/exec.go +++ b/pkg/cli/exec.go @@ -3,7 +3,6 @@ package cli import ( "errors" "fmt" - "net/http" "path/filepath" "github.com/aquaproj/aqua/pkg/config" @@ -41,7 +40,7 @@ func (runner *Runner) execAction(c *cli.Context) error { if err := runner.setParam(c, param); err != nil { return fmt.Errorf("parse the command line arguments: %w", err) } - ctrl := controller.InitializeExecCommandController(c.Context, param, http.DefaultClient, runner.Runtime) + ctrl := controller.InitializeExecCommandController(c.Context, param, runner.Runtime) exeName, args, err := parseExecArgs(c.Args().Slice()) if err != nil { return err diff --git a/pkg/cli/generate.go b/pkg/cli/generate.go index 9f4b24ef9..e1fe4a8a5 100644 --- a/pkg/cli/generate.go +++ b/pkg/cli/generate.go @@ -2,7 +2,6 @@ package cli import ( "fmt" - "net/http" "github.com/aquaproj/aqua/pkg/config" "github.com/aquaproj/aqua/pkg/controller" @@ -106,6 +105,6 @@ func (runner *Runner) generateAction(c *cli.Context) error { if err := runner.setParam(c, param); err != nil { return fmt.Errorf("parse the command line arguments: %w", err) } - ctrl := controller.InitializeGenerateCommandController(c.Context, param, http.DefaultClient) + ctrl := controller.InitializeGenerateCommandController(c.Context, param) return ctrl.Generate(c.Context, runner.LogE, param, c.Args().Slice()...) //nolint:wrapcheck } diff --git a/pkg/cli/install.go b/pkg/cli/install.go index b6cf19536..5d5491632 100644 --- a/pkg/cli/install.go +++ b/pkg/cli/install.go @@ -2,7 +2,6 @@ package cli import ( "fmt" - "net/http" "github.com/aquaproj/aqua/pkg/config" "github.com/aquaproj/aqua/pkg/controller" @@ -54,6 +53,6 @@ func (runner *Runner) installAction(c *cli.Context) error { if err := runner.setParam(c, param); err != nil { return fmt.Errorf("parse the command line arguments: %w", err) } - ctrl := controller.InitializeInstallCommandController(c.Context, param, http.DefaultClient, runner.Runtime) + ctrl := controller.InitializeInstallCommandController(c.Context, param, runner.Runtime) return ctrl.Install(c.Context, param, runner.LogE) //nolint:wrapcheck } diff --git a/pkg/cli/list.go b/pkg/cli/list.go index 2fb1b2c8b..6337f3206 100644 --- a/pkg/cli/list.go +++ b/pkg/cli/list.go @@ -2,7 +2,6 @@ package cli import ( "fmt" - "net/http" "github.com/aquaproj/aqua/pkg/config" "github.com/aquaproj/aqua/pkg/controller" @@ -32,6 +31,6 @@ func (runner *Runner) listAction(c *cli.Context) error { if err := runner.setParam(c, param); err != nil { return fmt.Errorf("parse the command line arguments: %w", err) } - ctrl := controller.InitializeListCommandController(c.Context, param, http.DefaultClient) + ctrl := controller.InitializeListCommandController(c.Context, param) return ctrl.List(c.Context, param, runner.LogE) //nolint:wrapcheck } diff --git a/pkg/cli/which.go b/pkg/cli/which.go index 8be61fe57..4b9a6f2b3 100644 --- a/pkg/cli/which.go +++ b/pkg/cli/which.go @@ -2,7 +2,6 @@ package cli import ( "fmt" - "net/http" "os" "github.com/aquaproj/aqua/pkg/config" @@ -39,7 +38,7 @@ func (runner *Runner) whichAction(c *cli.Context) error { if err := runner.setParam(c, param); err != nil { return fmt.Errorf("parse the command line arguments: %w", err) } - ctrl := controller.InitializeWhichCommandController(c.Context, param, http.DefaultClient, runner.Runtime) + ctrl := controller.InitializeWhichCommandController(c.Context, param, runner.Runtime) exeName, _, err := parseExecArgs(c.Args().Slice()) if err != nil { return err diff --git a/pkg/controller/generate/generate.go b/pkg/controller/generate/generate.go index 3a15ddb94..02e49a522 100644 --- a/pkg/controller/generate/generate.go +++ b/pkg/controller/generate/generate.go @@ -28,6 +28,7 @@ type Controller struct { stdin io.Reader stdout io.Writer gitHubRepositoryService githubSvc.RepositoryService + githubV4 githubSvc.GraphQL registryInstaller registry.Installer configFinder finder.ConfigFinder configReader reader.ConfigReader @@ -35,7 +36,7 @@ type Controller struct { fs afero.Fs } -func New(configFinder finder.ConfigFinder, configReader reader.ConfigReader, registInstaller registry.Installer, gh githubSvc.RepositoryService, fs afero.Fs, fuzzyFinder FuzzyFinder) *Controller { +func New(configFinder finder.ConfigFinder, configReader reader.ConfigReader, registInstaller registry.Installer, gh githubSvc.RepositoryService, fs afero.Fs, fuzzyFinder FuzzyFinder, githubV4 githubSvc.GraphQL) *Controller { return &Controller{ stdin: os.Stdin, stdout: os.Stdout, @@ -43,6 +44,7 @@ func New(configFinder finder.ConfigFinder, configReader reader.ConfigReader, reg configReader: configReader, registryInstaller: registInstaller, gitHubRepositoryService: gh, + githubV4: githubV4, fs: fs, fuzzyFinder: fuzzyFinder, } @@ -235,35 +237,26 @@ func (ctrl *Controller) listAndGetTagName(ctx context.Context, pkgInfo *config.P func (ctrl *Controller) listAndGetTagNameFromTag(ctx context.Context, pkgInfo *config.PackageInfo, logE *logrus.Entry) string { repoOwner := pkgInfo.RepoOwner repoName := pkgInfo.RepoName - opt := &github.ListOptions{ - PerPage: 30, //nolint:gomnd - } versionFilter, err := constraint.CompileVersionFilter(*pkgInfo.VersionFilter) if err != nil { return "" } - for { - tags, _, err := ctrl.gitHubRepositoryService.ListTags(ctx, repoOwner, repoName, opt) - if err != nil { - logerr.WithError(logE, err).WithFields(logrus.Fields{ - "repo_owner": repoOwner, - "repo_name": repoName, - }).Warn("list releases") - return "" - } - for _, tag := range tags { - tagName := tag.GetName() - f, err := constraint.EvaluateVersionFilter(versionFilter, tagName) - if err != nil || !f { - continue - } - return tagName - } - if len(tags) != opt.PerPage { - return "" + tags, err := ctrl.githubV4.ListTags(ctx, repoOwner, repoName) + if err != nil { + logerr.WithError(logE, err).WithFields(logrus.Fields{ + "repo_owner": repoOwner, + "repo_name": repoName, + }).Warn("list releases") + return "" + } + for _, tag := range tags { + f, err := constraint.EvaluateVersionFilter(versionFilter, tag) + if err != nil || !f { + continue } - opt.Page++ + return tag } + return "" } func (ctrl *Controller) getOutputtedGitHubPkgFromTag(ctx context.Context, outputPkg *config.Package, pkgInfo *config.PackageInfo, logE *logrus.Entry) { @@ -273,7 +266,7 @@ func (ctrl *Controller) getOutputtedGitHubPkgFromTag(ctx context.Context, output if pkgInfo.VersionFilter != nil { tagName = ctrl.listAndGetTagNameFromTag(ctx, pkgInfo, logE) } else { - tags, _, err := ctrl.gitHubRepositoryService.ListTags(ctx, repoOwner, repoName, nil) + tags, err := ctrl.githubV4.ListTags(ctx, repoOwner, repoName) if err != nil { logerr.WithError(logE, err).WithFields(logrus.Fields{ "repo_owner": repoOwner, @@ -284,8 +277,7 @@ func (ctrl *Controller) getOutputtedGitHubPkgFromTag(ctx context.Context, output if len(tags) == 0 { return } - tag := tags[0] - tagName = tag.GetName() + tagName = tags[0] } if pkgName := pkgInfo.GetName(); pkgName == repoOwner+"/"+repoName || strings.HasPrefix(pkgName, repoOwner+"/"+repoName+"/") { diff --git a/pkg/controller/generate/generate_test.go b/pkg/controller/generate/generate_test.go index 7128bc7c5..c1061cc22 100644 --- a/pkg/controller/generate/generate_test.go +++ b/pkg/controller/generate/generate_test.go @@ -37,6 +37,7 @@ func Test_controller_Generate(t *testing.T) { //nolint:funlen idxs []int fuzzyFinderErr error releases []*github.RepositoryRelease + tags []string }{ { name: "normal", @@ -234,11 +235,12 @@ packages: } configFinder := finder.NewConfigFinder(fs) gh := githubSvc.NewMock(d.releases, nil, "") + v4Client := githubSvc.NewMockGraphQL(d.tags, nil) downloader := download.NewRegistryDownloader(gh, download.NewHTTPDownloader(http.DefaultClient)) registryInstaller := registry.New(d.param, downloader, fs) configReader := reader.New(fs) fuzzyFinder := generate.NewMockFuzzyFinder(d.idxs, d.fuzzyFinderErr) - ctrl := generate.New(configFinder, configReader, registryInstaller, gh, fs, fuzzyFinder) + ctrl := generate.New(configFinder, configReader, registryInstaller, gh, fs, fuzzyFinder, v4Client) if err := ctrl.Generate(ctx, logE, d.param, d.args...); err != nil { if d.isErr { return diff --git a/pkg/controller/wire.go b/pkg/controller/wire.go index 7e3238ed2..36a356a96 100644 --- a/pkg/controller/wire.go +++ b/pkg/controller/wire.go @@ -5,7 +5,6 @@ package controller import ( "context" - "net/http" "github.com/aquaproj/aqua/pkg/config" finder "github.com/aquaproj/aqua/pkg/config-finder" @@ -24,36 +23,37 @@ import ( "github.com/aquaproj/aqua/pkg/link" "github.com/aquaproj/aqua/pkg/runtime" "github.com/google/wire" + "github.com/shurcooL/githubv4" "github.com/spf13/afero" "github.com/suzuki-shunsuke/go-osenv/osenv" ) -func InitializeListCommandController(ctx context.Context, param *config.Param, httpClient *http.Client) *list.Controller { - wire.Build(list.NewController, finder.NewConfigFinder, github.New, registry.New, download.NewRegistryDownloader, reader.New, afero.NewOsFs, download.NewHTTPDownloader) +func InitializeListCommandController(ctx context.Context, param *config.Param) *list.Controller { + wire.Build(list.NewController, finder.NewConfigFinder, github.New, github.NewHTTPClient, github.NewAccessToken, registry.New, download.NewRegistryDownloader, reader.New, afero.NewOsFs, download.NewHTTPDownloader) return &list.Controller{} } func InitializeInitCommandController(ctx context.Context, param *config.Param) *initcmd.Controller { - wire.Build(initcmd.New, github.New, afero.NewOsFs) + wire.Build(initcmd.New, github.New, github.NewHTTPClient, github.NewAccessToken, afero.NewOsFs) return &initcmd.Controller{} } -func InitializeGenerateCommandController(ctx context.Context, param *config.Param, httpClient *http.Client) *generate.Controller { - wire.Build(generate.New, finder.NewConfigFinder, github.New, registry.New, download.NewRegistryDownloader, reader.New, afero.NewOsFs, generate.NewFuzzyFinder, download.NewHTTPDownloader) +func InitializeGenerateCommandController(ctx context.Context, param *config.Param) *generate.Controller { + wire.Build(generate.New, finder.NewConfigFinder, github.New, github.NewHTTPClient, github.NewAccessToken, github.NewGraphQL, githubv4.NewClient, registry.New, download.NewRegistryDownloader, reader.New, afero.NewOsFs, generate.NewFuzzyFinder, download.NewHTTPDownloader) return &generate.Controller{} } -func InitializeInstallCommandController(ctx context.Context, param *config.Param, httpClient *http.Client, rt *runtime.Runtime) *install.Controller { - wire.Build(install.New, finder.NewConfigFinder, github.New, registry.New, download.NewRegistryDownloader, reader.New, installpackage.New, download.NewPackageDownloader, afero.NewOsFs, link.New, download.NewHTTPDownloader, exec.New) +func InitializeInstallCommandController(ctx context.Context, param *config.Param, rt *runtime.Runtime) *install.Controller { + wire.Build(install.New, finder.NewConfigFinder, github.New, github.NewHTTPClient, github.NewAccessToken, registry.New, download.NewRegistryDownloader, reader.New, installpackage.New, download.NewPackageDownloader, afero.NewOsFs, link.New, download.NewHTTPDownloader, exec.New) return &install.Controller{} } -func InitializeWhichCommandController(ctx context.Context, param *config.Param, httpClient *http.Client, rt *runtime.Runtime) which.Controller { - wire.Build(which.New, finder.NewConfigFinder, github.New, registry.New, download.NewRegistryDownloader, reader.New, osenv.New, afero.NewOsFs, download.NewHTTPDownloader, link.New) +func InitializeWhichCommandController(ctx context.Context, param *config.Param, rt *runtime.Runtime) which.Controller { + wire.Build(which.New, finder.NewConfigFinder, github.New, github.NewHTTPClient, github.NewAccessToken, registry.New, download.NewRegistryDownloader, reader.New, osenv.New, afero.NewOsFs, download.NewHTTPDownloader, link.New) return nil } -func InitializeExecCommandController(ctx context.Context, param *config.Param, httpClient *http.Client, rt *runtime.Runtime) *cexec.Controller { - wire.Build(cexec.New, finder.NewConfigFinder, download.NewPackageDownloader, installpackage.New, github.New, registry.New, download.NewRegistryDownloader, reader.New, which.New, exec.New, osenv.New, afero.NewOsFs, link.New, download.NewHTTPDownloader) +func InitializeExecCommandController(ctx context.Context, param *config.Param, rt *runtime.Runtime) *cexec.Controller { + wire.Build(cexec.New, finder.NewConfigFinder, download.NewPackageDownloader, installpackage.New, github.New, github.NewHTTPClient, github.NewAccessToken, registry.New, download.NewRegistryDownloader, reader.New, which.New, exec.New, osenv.New, afero.NewOsFs, link.New, download.NewHTTPDownloader) return &cexec.Controller{} } diff --git a/pkg/controller/wire_gen.go b/pkg/controller/wire_gen.go index c797d30c0..1d8ab9f48 100644 --- a/pkg/controller/wire_gen.go +++ b/pkg/controller/wire_gen.go @@ -24,19 +24,21 @@ import ( "github.com/aquaproj/aqua/pkg/installpackage" "github.com/aquaproj/aqua/pkg/link" "github.com/aquaproj/aqua/pkg/runtime" + "github.com/shurcooL/githubv4" "github.com/spf13/afero" "github.com/suzuki-shunsuke/go-osenv/osenv" - "net/http" ) // Injectors from wire.go: -func InitializeListCommandController(ctx context.Context, param *config.Param, httpClient *http.Client) *list.Controller { +func InitializeListCommandController(ctx context.Context, param *config.Param) *list.Controller { fs := afero.NewOsFs() configFinder := finder.NewConfigFinder(fs) configReader := reader.New(fs) - repositoryService := github.New(ctx) - httpDownloader := download.NewHTTPDownloader(httpClient) + accessToken := github.NewAccessToken() + client := github.NewHTTPClient(ctx, accessToken) + repositoryService := github.New(client) + httpDownloader := download.NewHTTPDownloader(client) registryDownloader := download.NewRegistryDownloader(repositoryService, httpDownloader) installer := registry.New(param, registryDownloader, fs) controller := list.NewController(configFinder, configReader, installer) @@ -44,31 +46,39 @@ func InitializeListCommandController(ctx context.Context, param *config.Param, h } func InitializeInitCommandController(ctx context.Context, param *config.Param) *initcmd.Controller { - repositoryService := github.New(ctx) + accessToken := github.NewAccessToken() + client := github.NewHTTPClient(ctx, accessToken) + repositoryService := github.New(client) fs := afero.NewOsFs() controller := initcmd.New(repositoryService, fs) return controller } -func InitializeGenerateCommandController(ctx context.Context, param *config.Param, httpClient *http.Client) *generate.Controller { +func InitializeGenerateCommandController(ctx context.Context, param *config.Param) *generate.Controller { fs := afero.NewOsFs() configFinder := finder.NewConfigFinder(fs) configReader := reader.New(fs) - repositoryService := github.New(ctx) - httpDownloader := download.NewHTTPDownloader(httpClient) + accessToken := github.NewAccessToken() + client := github.NewHTTPClient(ctx, accessToken) + repositoryService := github.New(client) + httpDownloader := download.NewHTTPDownloader(client) registryDownloader := download.NewRegistryDownloader(repositoryService, httpDownloader) installer := registry.New(param, registryDownloader, fs) fuzzyFinder := generate.NewFuzzyFinder() - controller := generate.New(configFinder, configReader, installer, repositoryService, fs, fuzzyFinder) + githubv4Client := githubv4.NewClient(client) + graphQL := github.NewGraphQL(githubv4Client) + controller := generate.New(configFinder, configReader, installer, repositoryService, fs, fuzzyFinder, graphQL) return controller } -func InitializeInstallCommandController(ctx context.Context, param *config.Param, httpClient *http.Client, rt *runtime.Runtime) *install.Controller { +func InitializeInstallCommandController(ctx context.Context, param *config.Param, rt *runtime.Runtime) *install.Controller { fs := afero.NewOsFs() configFinder := finder.NewConfigFinder(fs) configReader := reader.New(fs) - repositoryService := github.New(ctx) - httpDownloader := download.NewHTTPDownloader(httpClient) + accessToken := github.NewAccessToken() + client := github.NewHTTPClient(ctx, accessToken) + repositoryService := github.New(client) + httpDownloader := download.NewHTTPDownloader(client) registryDownloader := download.NewRegistryDownloader(repositoryService, httpDownloader) installer := registry.New(param, registryDownloader, fs) packageDownloader := download.NewPackageDownloader(repositoryService, rt, httpDownloader) @@ -79,12 +89,14 @@ func InitializeInstallCommandController(ctx context.Context, param *config.Param return controller } -func InitializeWhichCommandController(ctx context.Context, param *config.Param, httpClient *http.Client, rt *runtime.Runtime) which.Controller { +func InitializeWhichCommandController(ctx context.Context, param *config.Param, rt *runtime.Runtime) which.Controller { fs := afero.NewOsFs() configFinder := finder.NewConfigFinder(fs) configReader := reader.New(fs) - repositoryService := github.New(ctx) - httpDownloader := download.NewHTTPDownloader(httpClient) + accessToken := github.NewAccessToken() + client := github.NewHTTPClient(ctx, accessToken) + repositoryService := github.New(client) + httpDownloader := download.NewHTTPDownloader(client) registryDownloader := download.NewRegistryDownloader(repositoryService, httpDownloader) installer := registry.New(param, registryDownloader, fs) osEnv := osenv.New() @@ -93,9 +105,11 @@ func InitializeWhichCommandController(ctx context.Context, param *config.Param, return controller } -func InitializeExecCommandController(ctx context.Context, param *config.Param, httpClient *http.Client, rt *runtime.Runtime) *exec2.Controller { - repositoryService := github.New(ctx) - httpDownloader := download.NewHTTPDownloader(httpClient) +func InitializeExecCommandController(ctx context.Context, param *config.Param, rt *runtime.Runtime) *exec2.Controller { + accessToken := github.NewAccessToken() + client := github.NewHTTPClient(ctx, accessToken) + repositoryService := github.New(client) + httpDownloader := download.NewHTTPDownloader(client) packageDownloader := download.NewPackageDownloader(repositoryService, rt, httpDownloader) fs := afero.NewOsFs() linker := link.New() diff --git a/pkg/github/github.go b/pkg/github/github.go index 514e2e479..38ccb6675 100644 --- a/pkg/github/github.go +++ b/pkg/github/github.go @@ -16,11 +16,20 @@ type RepositoryService interface { GetReleaseByTag(ctx context.Context, owner, repoName, version string) (*github.RepositoryRelease, *github.Response, error) DownloadReleaseAsset(ctx context.Context, owner, repoName string, assetID int64, httpClient *http.Client) (io.ReadCloser, string, error) ListReleases(ctx context.Context, owner, repo string, opts *github.ListOptions) ([]*github.RepositoryRelease, *github.Response, error) - ListTags(ctx context.Context, owner string, repo string, opts *github.ListOptions) ([]*github.RepositoryTag, *github.Response, error) } -func New(ctx context.Context) RepositoryService { - return github.NewClient(getHTTPClientForGitHub(ctx, getGitHubToken())).Repositories +func New(httpClient *http.Client) RepositoryService { + return github.NewClient(httpClient).Repositories +} + +type AccessToken struct { + token string +} + +func NewAccessToken() *AccessToken { + return &AccessToken{ + token: getGitHubToken(), + } } func getGitHubToken() string { @@ -30,11 +39,11 @@ func getGitHubToken() string { return os.Getenv("GITHUB_TOKEN") } -func getHTTPClientForGitHub(ctx context.Context, token string) *http.Client { - if token == "" { +func NewHTTPClient(ctx context.Context, token *AccessToken) *http.Client { + if token == nil || token.token == "" { return http.DefaultClient } return oauth2.NewClient(ctx, oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: token}, + &oauth2.Token{AccessToken: token.token}, )) } diff --git a/pkg/github/github_test.go b/pkg/github/github_test.go index 2ab6f8792..4eb70601c 100644 --- a/pkg/github/github_test.go +++ b/pkg/github/github_test.go @@ -9,7 +9,7 @@ import ( func TestNew(t *testing.T) { t.Parallel() - if client := github.New(context.Background()); client == nil { + if client := github.New(github.NewHTTPClient(context.Background(), github.NewAccessToken())); client == nil { t.Fatal("client must not be nil") } } diff --git a/pkg/github/mock_v4.go b/pkg/github/mock_v4.go new file mode 100644 index 000000000..97e1e7708 --- /dev/null +++ b/pkg/github/mock_v4.go @@ -0,0 +1,19 @@ +package github + +import "context" + +func NewMockGraphQL(tags []string, err error) GraphQL { + return &mockV4{ + tags: tags, + err: err, + } +} + +type mockV4 struct { + tags []string + err error +} + +func (m *mockV4) ListTags(ctx context.Context, owner string, repo string) ([]string, error) { + return m.tags, m.err +} diff --git a/pkg/github/v4.go b/pkg/github/v4.go new file mode 100644 index 000000000..88eeab376 --- /dev/null +++ b/pkg/github/v4.go @@ -0,0 +1,48 @@ +package github + +import ( + "context" + "fmt" + + "github.com/shurcooL/githubv4" +) + +type GraphQL interface { + ListTags(ctx context.Context, owner string, repo string) ([]string, error) +} + +func NewGraphQL(v4Client *githubv4.Client) GraphQL { + return &graphQL{ + client: v4Client, + } +} + +type graphQL struct { + client *githubv4.Client +} + +func (cl *graphQL) ListTags(ctx context.Context, owner string, repo string) ([]string, error) { + // Pagination isn't supported + var q struct { + Repository struct { + Refs struct { + Nodes []struct { + Name string + } + } `graphql:"refs(refPrefix: \"refs/tags/\", first:30, orderBy:{direction:DESC, field:TAG_COMMIT_DATE})"` + } `graphql:"repository(owner: $owner, name: $name)"` + } + variables := map[string]interface{}{ + "owner": githubv4.String(owner), + "name": githubv4.String(repo), + } + + if err := cl.client.Query(ctx, &q, variables); err != nil { + return nil, fmt.Errorf("list tags by GitHub GraphQL API: %w", err) + } + tags := make([]string, len(q.Repository.Refs.Nodes)) + for i, node := range q.Repository.Refs.Nodes { + tags[i] = node.Name + } + return tags, nil +}