Skip to content

Commit f381361

Browse files
authored
Refactor and cleanup github package (#32)
1 parent 2470d9e commit f381361

26 files changed

+909
-372
lines changed

cmd/find_pr.go

Lines changed: 18 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,23 @@ import (
99
"encoding/json"
1010
"errors"
1111
"fmt"
12-
"log"
13-
"os"
1412

15-
gb "github.com/elastic/elastic-agent-changelog-tool/internal/github"
16-
"github.com/google/go-github/v32/github"
13+
"github.com/elastic/elastic-agent-changelog-tool/internal/github"
1714
"github.com/spf13/afero"
1815
"github.com/spf13/cobra"
19-
"golang.org/x/oauth2"
2016
)
2117

2218
var errListPRCmdMissingCommitHash = errors.New("find-pr requires commit hash argument")
2319

2420
const defaultOwner = "elastic"
2521
const defaultRepo = "beats"
2622

27-
const repoFlagName = "repo"
28-
const repoFlagDescription = "target repository"
2923
const findPRLongDescription = `Use this command to find the original PR that included the commit in the repository.
3024
3125
argument with commit hash is required
3226
--repo flag is optional and will default to elastic/beats if left unspecified.`
3327

34-
type PRInfo struct {
35-
CommitHash string `json:"commit"`
36-
PullRequestID string `json:"pull-request"`
37-
}
38-
39-
func prToDomain(commitHash string, pr *github.PullRequest) PRInfo {
40-
return PRInfo{
41-
CommitHash: commitHash,
42-
PullRequestID: fmt.Sprintf("%d", *pr.Number),
43-
}
44-
}
45-
46-
func FindPRCommand() *cobra.Command {
28+
func FindPRCommand(appFs afero.Fs) *cobra.Command {
4729
findPRCommand := &cobra.Command{
4830
Use: "find-pr",
4931
Long: findPRLongDescription,
@@ -55,66 +37,34 @@ func FindPRCommand() *cobra.Command {
5537
return nil
5638
},
5739
RunE: func(cmd *cobra.Command, args []string) error {
58-
authToken := gb.NewAuthToken(&afero.OsFs{})
59-
60-
var GithubClient *gb.GithubClient
61-
62-
githubAccessToken, err := authToken.AuthToken()
63-
switch {
64-
case errors.Is(err, os.ErrNotExist):
65-
// Github authorization token is not found
66-
// we continue using an unauthorized github client
67-
GithubClient = gb.NewUnauthorizedClient()
68-
69-
case err != nil:
70-
// If github token is found but couldn't read it's content
71-
log.Fatal(err)
72-
73-
default:
74-
// Github token is found and read successfully
75-
GithubClient, err = gb.NewClient(gb.NewWrapper(github.NewClient(oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(
76-
&oauth2.Token{
77-
AccessToken: githubAccessToken,
78-
}),
79-
))))
80-
if err != nil {
81-
log.Fatal(err)
82-
}
40+
hc, err := github.GetHTTPClient(appFs)
41+
if err != nil {
42+
return fmt.Errorf("cannot initialize http client: %w", err)
8343
}
8444

85-
var commit string
45+
c := github.NewClient(hc)
8646

87-
repo, err := cmd.Flags().GetString(repoFlagName)
47+
repo, err := cmd.Flags().GetString("repo")
8848
if err != nil {
8949
return fmt.Errorf("repo flag malformed: %w", err)
9050
}
9151

92-
commit = args[0]
93-
94-
if repo == "" {
95-
repo = defaultRepo
96-
}
97-
98-
prs, _, err := GithubClient.ListPullRequestsWithCommit(context.Background(), defaultOwner, repo, commit, nil)
52+
owner, err := cmd.Flags().GetString("owner")
9953
if err != nil {
100-
return fmt.Errorf("failed listing prs with commit: %w", err)
101-
}
102-
103-
type resp struct {
104-
Items []PRInfo `json:"items"`
54+
return fmt.Errorf("owner flag malformed: %w", err)
10555
}
10656

107-
respData := resp{
108-
Items: make([]PRInfo, len(prs)),
109-
}
57+
commit := args[0]
58+
ctx := context.Background()
11059

111-
for i, pr := range prs {
112-
respData.Items[i] = prToDomain(commit, pr)
60+
res, err := github.FindPR(ctx, c, owner, repo, commit)
61+
if err != nil {
62+
return fmt.Errorf("failed listing prs with commit: %w", err)
11363
}
11464

115-
respJSON, err := json.Marshal(respData)
65+
respJSON, err := json.Marshal(res)
11666
if err != nil {
117-
return fmt.Errorf("failed listing prs with commit: %w", err)
67+
return fmt.Errorf("failed marshalling JSON output: %w", err)
11868
}
11969

12070
cmd.Println(string(respJSON))
@@ -123,7 +73,8 @@ func FindPRCommand() *cobra.Command {
12373
},
12474
}
12575

126-
findPRCommand.Flags().String(repoFlagName, "", repoFlagDescription)
76+
findPRCommand.Flags().String("repo", defaultRepo, "target repository")
77+
findPRCommand.Flags().String("owner", defaultOwner, "target repository owner")
12778

12879
return findPRCommand
12980
}

cmd/find_pr_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License 2.0;
3+
// you may not use this file except in compliance with the Elastic License 2.0.
4+
5+
package cmd_test
6+
7+
import (
8+
"bytes"
9+
"testing"
10+
11+
"github.com/elastic/elastic-agent-changelog-tool/cmd"
12+
"github.com/spf13/afero"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func TestFindPrCmd_noArgs(t *testing.T) {
17+
fs := afero.NewMemMapFs()
18+
cmd := cmd.FindPRCommand(fs)
19+
20+
err := cmd.Execute()
21+
require.Error(t, err)
22+
}
23+
24+
func TestFindPrCmd_oneArg(t *testing.T) {
25+
fs := afero.NewMemMapFs()
26+
cmd := cmd.FindPRCommand(fs)
27+
28+
b := new(bytes.Buffer)
29+
cmd.SetOut(b)
30+
31+
cmd.SetArgs([]string{"--repo", "elastic-agent-changelog-tool", "fe56b2e"})
32+
33+
err := cmd.Execute()
34+
require.Nil(t, err)
35+
expected := `{"items":[{"commit":"fe56b2e","pull-request":17}]}
36+
`
37+
require.Equal(t, expected, b.String())
38+
39+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ require (
1515

1616
require (
1717
github.com/davecgh/go-spew v1.1.1 // indirect
18+
github.com/dnaeon/go-vcr v1.2.0
1819
github.com/fsnotify/fsnotify v1.5.1 // indirect
1920
github.com/golang/protobuf v1.5.2 // indirect
2021
github.com/google/go-querystring v1.0.0 // indirect
@@ -27,7 +28,6 @@ require (
2728
github.com/spf13/cast v1.4.1 // indirect
2829
github.com/spf13/jwalterweatherman v1.1.0 // indirect
2930
github.com/spf13/pflag v1.0.5 // indirect
30-
github.com/stretchr/objx v0.1.1 // indirect
3131
github.com/subosito/gotenv v1.2.0 // indirect
3232
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
3333
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
105105
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
106106
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
107107
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
108+
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
109+
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
108110
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
109111
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
110112
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -331,6 +333,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
331333
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
332334
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
333335
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
336+
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
334337
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
335338
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
336339
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=

internal/github/auth.go

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,59 +6,51 @@ package github
66

77
import (
88
"fmt"
9+
"log"
910
"os"
10-
"path/filepath"
1111
"strings"
1212

1313
"github.com/spf13/afero"
1414
)
1515

1616
const (
17-
envAuth = "GITHUB_TOKEN"
18-
authTokenFile = ".elastic/github.token"
17+
envAuth = "GITHUB_TOKEN"
1918
)
2019

21-
// EnsureAuthConfigured method ensures that GitHub auth token is available.
22-
func (f *AuthToken) EnsureAuthConfigured() error {
23-
if _, err := f.AuthToken(); err != nil {
24-
return fmt.Errorf("GitHub authorization token is missing. Please use either environment variable %s or ~/%s: %w",
25-
envAuth, authTokenFile, err)
26-
}
27-
28-
return nil
29-
}
20+
type envProvider func(env string) string
3021

3122
type AuthToken struct {
32-
fs *afero.Afero
23+
fs afero.Fs
24+
location string
25+
26+
envProvider envProvider
3327
}
3428

35-
func NewAuthToken(fs afero.Fs) *AuthToken {
36-
return &AuthToken{
37-
fs: &afero.Afero{
38-
Fs: fs,
39-
},
29+
func NewAuthToken(fs afero.Fs, tkLoc string) AuthToken {
30+
return AuthToken{
31+
fs: fs,
32+
location: tkLoc,
33+
envProvider: os.Getenv,
4034
}
4135
}
4236

43-
// AuthToken method finds and returns the GitHub authorization token.
44-
func (f *AuthToken) AuthToken() (string, error) {
45-
githubTokenVar := os.Getenv(envAuth)
37+
func NewTestAuthToken(fs afero.Fs, tkLoc string, ep envProvider) AuthToken {
38+
at := NewAuthToken(fs, tkLoc)
39+
at.envProvider = ep
4640

47-
if githubTokenVar != "" {
48-
fmt.Println("Using GitHub token from environment variable.")
49-
return githubTokenVar, nil
50-
}
41+
return at
42+
}
5143

52-
homeDir, err := os.UserHomeDir()
53-
if err != nil {
54-
return "", fmt.Errorf("reading user home directory failed: %w", err)
44+
// AuthToken method finds and returns the GitHub authorization token.
45+
func (f AuthToken) AuthToken() (string, error) {
46+
if githubTokenVar := f.envProvider(envAuth); githubTokenVar != "" {
47+
log.Println("Using GitHub token from environment variable")
48+
return githubTokenVar, nil
5549
}
5650

57-
githubTokenPath := filepath.Join(homeDir, authTokenFile)
58-
59-
token, err := f.fs.ReadFile(githubTokenPath)
51+
token, err := afero.ReadFile(f.fs, f.location)
6052
if err != nil {
61-
return "", fmt.Errorf("reading Github token file failed (path: %s): %w", githubTokenPath, err)
53+
return "", fmt.Errorf("cannot read Github token file failed (path: %s): %w", f.location, err)
6254
}
6355

6456
return strings.TrimSpace(string(token)), nil

internal/github/auth_test.go

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,54 @@
22
// or more contributor license agreements. Licensed under the Elastic License 2.0;
33
// you may not use this file except in compliance with the Elastic License 2.0.
44

5-
package github
5+
package github_test
66

77
import (
88
"io/fs"
9-
"os"
10-
"path/filepath"
119
"testing"
1210

11+
"github.com/elastic/elastic-agent-changelog-tool/internal/github"
1312
"github.com/spf13/afero"
1413
"github.com/stretchr/testify/require"
1514
)
1615

1716
func TestAuthToken(t *testing.T) {
1817
expectedToken := "ghp_tuQprmeVXWdaMhatQiw8pJdEXPxHWm9tkTJb"
18+
testFs := afero.MemMapFs{}
1919

20-
AuthToken := NewAuthToken(&afero.MemMapFs{})
21-
22-
homeDir, err := os.UserHomeDir()
20+
tokenLocation, err := github.TokenLocation()
21+
require.NoError(t, err, "cannot get token location")
22+
err = afero.WriteFile(&testFs, tokenLocation, []byte(expectedToken), fs.ModeAppend)
2323
require.NoError(t, err)
2424

25-
githubTokenPath := filepath.Join(homeDir, authTokenFile)
26-
27-
err = AuthToken.fs.WriteFile(githubTokenPath, []byte(expectedToken), fs.ModeAppend)
28-
require.NoError(t, err)
25+
tk := github.NewAuthToken(&testFs, tokenLocation)
2926

30-
token, err := AuthToken.AuthToken()
27+
token, err := tk.AuthToken()
3128
require.NoError(t, err)
3229
require.NotEmpty(t, token)
3330
require.Equal(t, expectedToken, token)
3431
}
3532

36-
func TestEnsureAuthConfigured(t *testing.T) {
33+
func TestAuthToken_fromEnv(t *testing.T) {
3734
expectedToken := "ghp_tuQprmeVXWdaMhatQiw8pJdEXPxHWm9tkTJb"
35+
testFs := afero.MemMapFs{}
3836

39-
homeDir, err := os.UserHomeDir()
40-
require.NoError(t, err)
37+
tk := github.NewTestAuthToken(&testFs, "using env", func(key string) string {
38+
return expectedToken
39+
})
4140

42-
githubTokenPath := filepath.Join(homeDir, authTokenFile)
41+
token, err := tk.AuthToken()
42+
require.NoError(t, err)
43+
require.NotEmpty(t, token)
44+
require.Equal(t, expectedToken, token)
45+
}
4346

44-
AuthToken := NewAuthToken(&afero.MemMapFs{})
47+
func TestAuthToken_readFailure(t *testing.T) {
48+
testFs := afero.MemMapFs{}
4549

46-
err = AuthToken.fs.WriteFile(githubTokenPath, []byte(expectedToken), fs.ModeAppend)
47-
require.NoError(t, err)
50+
tk := github.NewAuthToken(&testFs, "foobar")
4851

49-
err = AuthToken.EnsureAuthConfigured()
50-
require.NoError(t, err)
52+
token, err := tk.AuthToken()
53+
require.Error(t, err)
54+
require.Empty(t, token)
5155
}

0 commit comments

Comments
 (0)