Skip to content

Commit

Permalink
Merge pull request #128 from whywaita/refactor/shared-cache-transport
Browse files Browse the repository at this point in the history
Use shared cache transport
  • Loading branch information
whywaita authored Jan 17, 2022
2 parents f62d487 + 16654b6 commit 1089ac1
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 123 deletions.
7 changes: 5 additions & 2 deletions cmd/server/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import (
"strings"
"time"

"github.com/whywaita/myshoes/pkg/logger"

"github.com/whywaita/myshoes/internal/config"
"github.com/whywaita/myshoes/pkg/datastore"
"github.com/whywaita/myshoes/pkg/datastore/mysql"
"github.com/whywaita/myshoes/pkg/gh"
"github.com/whywaita/myshoes/pkg/logger"
"github.com/whywaita/myshoes/pkg/runner"
"github.com/whywaita/myshoes/pkg/starter"
"github.com/whywaita/myshoes/pkg/starter/safety/unlimited"
Expand All @@ -22,6 +22,9 @@ import (

func init() {
config.Load()
if err := gh.InitializeCache(config.Config.GitHub.AppID, config.Config.GitHub.PEMByte); err != nil {
log.Panicf("failed to create a cache: %+v", err)
}
}

func main() {
Expand Down
105 changes: 56 additions & 49 deletions pkg/gh/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@ import (
"path"
"strings"
"sync"
"time"

"github.com/bradleyfalzon/ghinstallation/v2"
"github.com/google/go-github/v35/github"
"github.com/gregjones/httpcache"
"github.com/patrickmn/go-cache"
"github.com/whywaita/myshoes/internal/config"
"github.com/whywaita/myshoes/pkg/logger"
"golang.org/x/oauth2"
)
Expand All @@ -24,33 +21,41 @@ var (
// ErrNotFound is error for not found
ErrNotFound = fmt.Errorf("not found")

// ResponseCache is cache variable
responseCache *cache.Cache

// rateLimitRemain is remaining of Rate limit, for metrics
rateLimitRemain = sync.Map{}
// rateLimitLimit is limit of Rate limit, for metrics
rateLimitLimit = sync.Map{}
)

func init() {
c := cache.New(5*time.Minute, 10*time.Minute)
responseCache = c
// httpCache is shareable response cache
httpCache = httpcache.NewMemoryCache()
// appTransport is transport for GitHub Apps
appTransport = ghinstallation.AppsTransport{}
// installationTransports is map of ghinstallation.Transport for cache token of installation.
// key: installationID, value: ghinstallation.Transport
installationTransports = sync.Map{}
)

rateLimitRemain = sync.Map{}
rateLimitLimit = sync.Map{}
// InitializeCache create a cache
func InitializeCache(appID int64, appPEM []byte) error {
tr := httpcache.NewTransport(httpCache)
itr, err := ghinstallation.NewAppsTransport(tr, appID, appPEM)
if err != nil {
return fmt.Errorf("failed to create Apps transport: %w", err)
}
appTransport = *itr
return nil
}

// NewClient create a client of GitHub
func NewClient(ctx context.Context, personalToken, gheDomain string) (*github.Client, error) {
func NewClient(token, gheDomain string) (*github.Client, error) {
oauth2Transport := &oauth2.Transport{
Source: oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: personalToken},
&oauth2.Token{AccessToken: token},
),
}
transport := &httpcache.Transport{
Transport: oauth2Transport,
Cache: httpcache.NewMemoryCache(),
Cache: httpCache,
MarkCachedResponses: true,
}

Expand All @@ -64,35 +69,28 @@ func NewClient(ctx context.Context, personalToken, gheDomain string) (*github.Cl
// NewClientGitHubApps create a client of GitHub using Private Key from GitHub Apps
// header is "Authorization: Bearer YOUR_JWT"
// docs: https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#authenticating-as-a-github-app
func NewClientGitHubApps(gheDomain string, appID int64, appPEM []byte) (*github.Client, error) {
tr := httpcache.NewMemoryCacheTransport()
itr, err := ghinstallation.NewAppsTransport(tr, appID, appPEM)
if err != nil {
return nil, fmt.Errorf("failed to create Apps transport: %w", err)
}

func NewClientGitHubApps(gheDomain string) (*github.Client, error) {
if gheDomain == "" {
return github.NewClient(&http.Client{Transport: itr}), nil
return github.NewClient(&http.Client{Transport: &appTransport}), nil
}

apiEndpoint, err := getAPIEndpoint(gheDomain)
if err != nil {
return nil, fmt.Errorf("failed to get GitHub API Endpoint: %w", err)
}

itr := appTransport
itr.BaseURL = apiEndpoint.String()
return github.NewEnterpriseClient(gheDomain, gheDomain, &http.Client{Transport: itr})
return github.NewEnterpriseClient(gheDomain, gheDomain, &http.Client{Transport: &appTransport})
}

// NewClientInstallation create a client of GitHub using installation ID from GitHub Apps
// header is "Authorization: token YOUR_INSTALLATION_ACCESS_TOKEN"
// docs: https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#authenticating-as-an-installation
func NewClientInstallation(gheDomain string, installationID int64, appID int64, appPEM []byte) (*github.Client, error) {
tr := httpcache.NewMemoryCacheTransport()
itr, err := ghinstallation.New(tr, appID, installationID, appPEM)
if err != nil {
return nil, fmt.Errorf("failed to create Apps transport: %w", err)
}
func NewClientInstallation(gheDomain string, installationID int64) (*github.Client, error) {
itr := getInstallationTransport(installationID)

if gheDomain == "" {
if strings.EqualFold(gheDomain, "") || strings.EqualFold(gheDomain, "https://github.com") {
return github.NewClient(&http.Client{Transport: itr}), nil
}
apiEndpoint, err := getAPIEndpoint(gheDomain)
Expand All @@ -103,22 +101,40 @@ func NewClientInstallation(gheDomain string, installationID int64, appID int64,
return github.NewEnterpriseClient(gheDomain, gheDomain, &http.Client{Transport: itr})
}

func setInstallationTransport(installationID int64, itr ghinstallation.Transport) {
installationTransports.Store(installationID, itr)
}

func getInstallationTransport(installationID int64) *ghinstallation.Transport {
got, found := installationTransports.Load(installationID)
if !found {
return generateInstallationTransport(installationID)
}

itr, ok := got.(ghinstallation.Transport)
if !ok {
return generateInstallationTransport(installationID)
}
return &itr
}

func generateInstallationTransport(installationID int64) *ghinstallation.Transport {
itr := ghinstallation.NewFromAppsTransport(&appTransport, installationID)
setInstallationTransport(installationID, *itr)
return itr
}

// CheckSignature check trust installation id from event.
func CheckSignature(installationID int64) error {
appID := config.Config.GitHub.AppID
pem := config.Config.GitHub.PEMByte

tr := httpcache.NewMemoryCacheTransport()
_, err := ghinstallation.New(tr, appID, installationID, pem)
if err != nil {
return fmt.Errorf("failed to create GitHub installation: %w", err)
if itr := ghinstallation.NewFromAppsTransport(&appTransport, installationID); itr == nil {
return fmt.Errorf("failed to create GitHub installation")
}

return nil
}

// ExistGitHubRepository check exist of GitHub repository
func ExistGitHubRepository(scope, gheDomain string, githubPersonalToken string) error {
func ExistGitHubRepository(scope, gheDomain string, accessToken string) error {
repoURL, err := getRepositoryURL(scope, gheDomain)
if err != nil {
return fmt.Errorf("failed to get repository url: %w", err)
Expand All @@ -129,7 +145,7 @@ func ExistGitHubRepository(scope, gheDomain string, githubPersonalToken string)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Add("Authorization", fmt.Sprintf("token %s", githubPersonalToken))
req.Header.Add("Authorization", fmt.Sprintf("token %s", accessToken))

resp, err := client.Do(req)
if err != nil {
Expand Down Expand Up @@ -163,10 +179,6 @@ func ExistGitHubRunner(ctx context.Context, client *github.Client, owner, repo,

// ListRunners get runners that registered repository or org
func ListRunners(ctx context.Context, client *github.Client, owner, repo string) ([]*github.Runner, error) {
if cachedRs, found := responseCache.Get(getCacheKey(owner, repo)); found {
return cachedRs.([]*github.Runner), nil
}

var opts = &github.ListOptions{
Page: 0,
PerPage: 10,
Expand All @@ -188,16 +200,11 @@ func ListRunners(ctx context.Context, client *github.Client, owner, repo string)
opts.Page = resp.NextPage
}

responseCache.Set(getCacheKey(owner, repo), rs, 1*time.Second)
logger.Logf(true, "found %d runners in GitHub", len(rs))

return rs, nil
}

func getCacheKey(owner, repo string) string {
return fmt.Sprintf("owner-%s-repo-%s", owner, repo)
}

func listRunners(ctx context.Context, client *github.Client, owner, repo string, opts *github.ListOptions) (*github.Runners, *github.Response, error) {
if repo == "" {
runners, resp, err := client.Actions.ListOrganizationRunners(ctx, owner, opts)
Expand Down
63 changes: 12 additions & 51 deletions pkg/gh/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import (
"strings"
"time"

"github.com/whywaita/myshoes/internal/config"

"github.com/whywaita/myshoes/pkg/logger"

"github.com/google/go-github/v35/github"
Expand All @@ -17,7 +15,6 @@ import (
var (
GHlistInstallations = listInstallations
GHlistAppsInstalledRepo = listAppsInstalledRepo
GHNewClientGitHubApps = NewClientGitHubApps
)

// GenerateGitHubAppsToken generate token of GitHub Apps using private key
Expand All @@ -31,39 +28,10 @@ func GenerateGitHubAppsToken(ctx context.Context, clientApps *github.Client, ins
return *token.Token, token.ExpiresAt, nil
}

// GenerateRunnerRegistrationToken generate token for register runner
func GenerateRunnerRegistrationToken(ctx context.Context, gheDomain string, installationID int64, scope string) (string, *time.Time, error) {
client, err := NewClientInstallation(gheDomain, installationID, config.Config.GitHub.AppID, config.Config.GitHub.PEMByte)
if err != nil {
return "", nil, fmt.Errorf("failed to create NewClientInstallation: %w", err)
}

switch DetectScope(scope) {
case Organization:
token, _, err := client.Actions.CreateOrganizationRegistrationToken(ctx, scope)
if err != nil {
return "", nil, fmt.Errorf("failed to generate registration token for organization (scope: %s): %w", scope, err)
}
return *token.Token, &token.ExpiresAt.Time, nil
case Repository:
owner, repo := DivideScope(scope)
token, _, err := client.Actions.CreateRegistrationToken(ctx, owner, repo)
if err != nil {
return "", nil, fmt.Errorf("failed to generate registration token for repository (scope: %s): %w", scope, err)
}
return *token.Token, &token.ExpiresAt.Time, nil
default:
return "", nil, fmt.Errorf("failed to detect scope (scope: %s)", scope)
}
}

// IsInstalledGitHubApp check installed GitHub Apps in gheDomain + inputScope
// clientApps needs to response of `NewClientGitHubApps()`
func IsInstalledGitHubApp(ctx context.Context, gheDomain, inputScope string) (int64, error) {
clientApps, err := GHNewClientGitHubApps(gheDomain, config.Config.GitHub.AppID, config.Config.GitHub.PEMByte)
if err != nil {
return -1, fmt.Errorf("failed to create client from GitHub Apps: %w", err)
}
installations, err := GHlistInstallations(ctx, clientApps)
installations, err := GHlistInstallations(ctx, gheDomain)
if err != nil {
return -1, fmt.Errorf("failed to get list of installations: %w", err)
}
Expand Down Expand Up @@ -98,7 +66,7 @@ func IsInstalledGitHubApp(ctx context.Context, gheDomain, inputScope string) (in
}

func isInstalledGitHubAppSelected(ctx context.Context, gheDomain, inputScope string, installationID int64) error {
lr, err := GHlistAppsInstalledRepo(ctx, gheDomain, installationID, inputScope)
lr, err := GHlistAppsInstalledRepo(ctx, gheDomain, installationID)
if err != nil {
return fmt.Errorf("failed to get list of installed repositories: %w", err)
}
Expand All @@ -124,29 +92,22 @@ func isInstalledGitHubAppSelected(ctx context.Context, gheDomain, inputScope str
return fmt.Errorf("not found")
}

func listAppsInstalledRepo(ctx context.Context, gheDomain string, installationID int64, inputScope string) (*github.ListRepositories, error) {
clientApps, err := NewClientGitHubApps(gheDomain, config.Config.GitHub.AppID, config.Config.GitHub.PEMByte)
if err != nil {
return nil, fmt.Errorf("failed to create github.Client from installationID: %w", err)
}
token, _, err := GenerateGitHubAppsToken(ctx, clientApps, installationID, inputScope)
if err != nil {
return nil, fmt.Errorf("failed to generate GitHub Apps Token: %w", err)
}
client, err := NewClient(ctx, token, gheDomain)
if err != nil {
return nil, fmt.Errorf("failed to NewClient: %w", err)
}

lr, _, err := client.Apps.ListRepos(ctx, nil)
func listAppsInstalledRepo(ctx context.Context, gheDomain string, installationID int64) (*github.ListRepositories, error) {
clientInstallation, err := NewClientInstallation(gheDomain, installationID)
lr, _, err := clientInstallation.Apps.ListRepos(ctx, nil) // TODO: paging
if err != nil {
return nil, fmt.Errorf("failed to get installed repositories: %w", err)
}

return lr, nil
}

func listInstallations(ctx context.Context, clientApps *github.Client) ([]*github.Installation, error) {
func listInstallations(ctx context.Context, gheDomain string) ([]*github.Installation, error) {
clientApps, err := NewClientGitHubApps(gheDomain)
if err != nil {
return nil, fmt.Errorf("failed to create a client Apps: %w", err)
}

var opts = &github.ListOptions{
Page: 0,
PerPage: 10,
Expand Down
8 changes: 2 additions & 6 deletions pkg/gh/jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

func setStubFunctions() {
GHlistInstallations = func(ctx context.Context, clientApps *github.Client) ([]*github.Installation, error) {
GHlistInstallations = func(ctx context.Context, gheDomain string) ([]*github.Installation, error) {
i10 := int64(10)
i11 := int64(11)
i12 := int64(12)
Expand Down Expand Up @@ -49,7 +49,7 @@ func setStubFunctions() {
}, nil
}

GHlistAppsInstalledRepo = func(ctx context.Context, gheDomain string, installationID int64, scope string) (*github.ListRepositories, error) {
GHlistAppsInstalledRepo = func(ctx context.Context, gheDomain string, installationID int64) (*github.ListRepositories, error) {
total := 1
fullName1 := "example-selected/sample-registered"
return &github.ListRepositories{
Expand All @@ -61,10 +61,6 @@ func setStubFunctions() {
},
}, nil
}

GHNewClientGitHubApps = func(gheDomain string, appID int64, appPEM []byte) (*github.Client, error) {
return &github.Client{}, nil
}
}

func Test_IsInstalledGitHubApp(t *testing.T) {
Expand Down
Loading

0 comments on commit 1089ac1

Please sign in to comment.