Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Github Enterprise server support #1624

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 12 additions & 6 deletions doghouse/appengine/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ type GitHubHandler struct {
integrationID int
}

func NewGitHubHandler(clientID, clientSecret string, c *cookieman.CookieMan, privateKey []byte, integrationID int) *GitHubHandler {
func NewGitHubHandler(clientID, clientSecret string, c *cookieman.CookieMan, privateKey []byte, integrationID int, repoTokenStorage storage.GitHubRepositoryTokenStore) *GitHubHandler {
return &GitHubHandler{
clientID: clientID,
clientSecret: clientSecret,
tokenStore: c.NewCookieStore("github-token", nil),
redirURLStore: c.NewCookieStore("github-redirect-url", nil),
authStateStore: c.NewCookieStore("github-auth-state", nil),
repoTokenStore: &storage.GitHubRepoTokenDatastore{},
repoTokenStore: repoTokenStorage,
integrationID: integrationID,
privateKey: privateKey,
}
Expand Down Expand Up @@ -93,9 +93,10 @@ func (g *GitHubHandler) buildGithubAuthURL(r *http.Request, state string) string
redirURL.Path = "/gh/_auth/callback"
redirURL.RawQuery = ""
redirURL.Fragment = ""
const baseURL = "https://github.com/login/oauth/authorize"
authURL := fmt.Sprintf("%s?client_id=%s&redirect_url=%s&state=%s",
baseURL, g.clientID, redirURL.RequestURI(), state)

var ghHostUrl = server.GetGithubHostUrl()
authURL := fmt.Sprintf("%s/login/oauth/authorize?client_id=%s&redirect_url=%s&state=%s",
ghHostUrl, g.clientID, redirURL.RequestURI(), state)
return authURL
}

Expand Down Expand Up @@ -170,7 +171,8 @@ func securerandom(n int) string {
// https://developer.github.com/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps/#2-users-are-redirected-back-to-your-site-by-github
// POST https://github.com/login/oauth/access_token
func (g *GitHubHandler) requestAccessToken(ctx context.Context, code, state string) (string, error) {
const u = "https://github.com/login/oauth/access_token"
var ghHostUrl = server.GetGithubHostUrl()
var u = fmt.Sprintf("%s/login/oauth/access_token", ghHostUrl)
cli := &http.Client{}
data := url.Values{}
data.Set("client_id", g.clientID)
Expand Down Expand Up @@ -231,6 +233,10 @@ func (g *GitHubHandler) HandleGitHubTop(w http.ResponseWriter, r *http.Request)
&oauth2.Token{AccessToken: token},
)
ghcli := github.NewClient(NewAuthClient(ctx, http.DefaultTransport, ts))
if server.IsGithubEnterpriseApi() {
githubApiUrl := server.GetGithubApiUrl()
ghcli, _ = ghcli.WithEnterpriseURLs(githubApiUrl, githubApiUrl)
}

// /gh/{owner}/{repo}
paths := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
Expand Down
40 changes: 33 additions & 7 deletions doghouse/appengine/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"github.com/reviewdog/reviewdog/doghouse/server"
"log"
"net/http"
"os"
Expand All @@ -13,6 +14,7 @@ import (
"go.opencensus.io/plugin/ochttp"
"go.opencensus.io/trace"

"github.com/philippgille/gokv/bbolt"
"github.com/reviewdog/reviewdog/doghouse/server/cookieman"
"github.com/reviewdog/reviewdog/doghouse/server/storage"
)
Expand All @@ -27,13 +29,18 @@ func mustCookieMan() *cookieman.CookieMan {
c := cookieman.CookieOption{
Cookie: http.Cookie{
HttpOnly: true,
Secure: true,
Secure: secureCookie(),
Path: "/",
},
}
return cookieman.New(cipher, c)
}

// To test server locally, set REVIEWDOG_INSECURE_COOKIE=true
func secureCookie() bool {
return os.Getenv("REVIEWDOG_INSECURE_COOKIE") != "true"
}

func mustGitHubAppsPrivateKey() []byte {
// Private keys https://github.com/settings/apps/reviewdog
githubAppsPrivateKey, err := os.ReadFile(mustGetenv("GITHUB_PRIVATE_KEY_FILE"))
Expand Down Expand Up @@ -64,28 +71,47 @@ func mustIntEnv(name string) int {
}

func main() {
configureTrace()
if os.Getenv("REVIEWDOG_TRACING_DISABLED") != "true" {
configureTrace()
}
initTemplates()

integrationID := mustIntEnv("GITHUB_INTEGRATION_ID")
ghPrivateKey := mustGitHubAppsPrivateKey()

ghInstStore := storage.GitHubInstallationDatastore{}
ghRepoTokenStore := storage.GitHubRepoTokenDatastore{}
var ghInstStore storage.GitHubInstallationStore
var ghRepoTokenStore storage.GitHubRepositoryTokenStore

if server.IsGithubEnterpriseApi() {
newStore, err := bbolt.NewStore(bbolt.Options{})
if err != nil {
log.Fatal(err)
}
ghInstStore = &storage.LocalKVGitHubInstallationStore{
KvStore: newStore,
}
ghRepoTokenStore = storage.LocalKVGitHubRepoTokenStore{
KvStore: newStore,
}
} else {
ghInstStore = &storage.GoogleGitHubInstallationDatastore{}
ghRepoTokenStore = &storage.GoogleGitHubRepoTokenDatastore{}
}

ghHandler := NewGitHubHandler(
mustGetenv("GITHUB_CLIENT_ID"),
mustGetenv("GITHUB_CLIENT_SECRET"),
mustCookieMan(),
ghPrivateKey,
integrationID,
ghRepoTokenStore,
)

ghChecker := githubChecker{
privateKey: ghPrivateKey,
integrationID: integrationID,
ghInstStore: &ghInstStore,
ghRepoTokenStore: &ghRepoTokenStore,
ghInstStore: ghInstStore,
ghRepoTokenStore: ghRepoTokenStore,
tr: &ochttp.Transport{
// Use Google Cloud propagation format.
Propagation: &propagation.HTTPFormat{},
Expand All @@ -94,7 +120,7 @@ func main() {

ghWebhookHandler := githubWebhookHandler{
secret: []byte(mustGetenv("GITHUB_WEBHOOK_SECRET")),
ghInstStore: &ghInstStore,
ghInstStore: ghInstStore,
}

mu := http.NewServeMux()
Expand Down
46 changes: 43 additions & 3 deletions doghouse/server/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"os"

"github.com/bradleyfalzon/ghinstallation/v2"
"github.com/google/go-github/v57/github"
Expand Down Expand Up @@ -34,18 +35,28 @@ func NewGitHubClient(ctx context.Context, opt *NewGitHubClientOption) (*github.C
}

client.Transport = itr
return github.NewClient(client), nil

ghcli := github.NewClient(client)
if IsGithubEnterpriseApi() {
url := GetGithubHostUrl()
ghcli, err = ghcli.WithEnterpriseURLs(url, url)
}
return ghcli, nil
}

func githubAppTransport(ctx context.Context, client *http.Client, opt *NewGitHubClientOption) (http.RoundTripper, error) {
if opt.RepoOwner == "" {
return ghinstallation.NewAppsTransport(getTransport(client), int64(opt.IntegrationID), opt.PrivateKey)
transport, err := ghinstallation.NewAppsTransport(getTransport(client), int64(opt.IntegrationID), opt.PrivateKey)
transport.BaseURL = GetGithubApiUrl()
return transport, err
}
installationID, err := findInstallationID(ctx, opt)
if err != nil {
return nil, err
}
return ghinstallation.New(getTransport(client), int64(opt.IntegrationID), installationID, opt.PrivateKey)
transport, err := ghinstallation.New(getTransport(client), int64(opt.IntegrationID), installationID, opt.PrivateKey)
transport.BaseURL = GetGithubApiUrl()
return transport, err
}

func getTransport(client *http.Client) http.RoundTripper {
Expand All @@ -71,3 +82,32 @@ func findInstallationID(ctx context.Context, opt *NewGitHubClientOption) (int64,
}
return inst.GetID(), nil
}

func getBaseEnterpriseUrl() string {
return os.Getenv("GITHUB_ENTERPRISE_BASE_URL")
}

func IsGithubEnterpriseApi() bool {
return getBaseEnterpriseUrl() != ""
}

// GetGithubHostUrl Used for login methods, that not directly related to GitHub API.
func GetGithubHostUrl() string {
enterpriseUrl := getBaseEnterpriseUrl()

if enterpriseUrl != "" {
return enterpriseUrl
} else {
return "https://github.com"
}
}

func GetGithubApiUrl() string {
enterpriseUrl := getBaseEnterpriseUrl()

if enterpriseUrl != "" {
return enterpriseUrl + "/api/v3"
} else {
return "https://api.github.com"
}
}
39 changes: 34 additions & 5 deletions doghouse/server/storage/installation.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"

"cloud.google.com/go/datastore"

"github.com/philippgille/gokv"
)

// GitHubInstallation represents GitHub Apps Installation data.
Expand All @@ -26,17 +28,44 @@ type GitHubInstallationStore interface {
Get(ctx context.Context, accountName string) (ok bool, inst *GitHubInstallation, err error)
}

// GitHubInstallationDatastore is store of GitHubInstallation by Datastore of
// GoogleGitHubInstallationDatastore is store of GitHubInstallation by Datastore of
// Google Appengine.
type GitHubInstallationDatastore struct{}
type GoogleGitHubInstallationDatastore struct{}

type LocalKVGitHubInstallationStore struct {
KvStore gokv.Store
}

func (l LocalKVGitHubInstallationStore) Put(ctx context.Context, inst *GitHubInstallation) error {

err := l.KvStore.Set(inst.AccountName, inst)

if err != nil {
return err
}

return nil
}

func (l LocalKVGitHubInstallationStore) Get(ctx context.Context, accountName string) (ok bool, inst *GitHubInstallation, err error) {

inst = &GitHubInstallation{} // Initialize inst

get, err := l.KvStore.Get(accountName, inst)
if err != nil {
return false, nil, err
}

return get, inst, nil
}

func (g *GitHubInstallationDatastore) newKey(accountName string) *datastore.Key {
func (g *GoogleGitHubInstallationDatastore) newKey(accountName string) *datastore.Key {
const kind = "GitHubInstallation"
return datastore.NameKey(kind, accountName, nil)
}

// Put save GitHubInstallation. It reduces datastore write call as much as possible.
func (g *GitHubInstallationDatastore) Put(ctx context.Context, inst *GitHubInstallation) error {
func (g *GoogleGitHubInstallationDatastore) Put(ctx context.Context, inst *GitHubInstallation) error {
d, err := datastoreClient(ctx)
if err != nil {
return err
Expand All @@ -61,7 +90,7 @@ func (g *GitHubInstallationDatastore) Put(ctx context.Context, inst *GitHubInsta
return err
}

func (g *GitHubInstallationDatastore) Get(ctx context.Context, accountName string) (ok bool, inst *GitHubInstallation, err error) {
func (g *GoogleGitHubInstallationDatastore) Get(ctx context.Context, accountName string) (ok bool, inst *GitHubInstallation, err error) {
key := g.newKey(accountName)
inst = new(GitHubInstallation)
d, err := datastoreClient(ctx)
Expand Down
38 changes: 33 additions & 5 deletions doghouse/server/storage/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package storage
import (
"context"
"fmt"
"github.com/philippgille/gokv"

"cloud.google.com/go/datastore"
)
Expand All @@ -28,17 +29,44 @@ type GitHubRepositoryTokenStore interface {
Get(ctx context.Context, owner, repo string) (ok bool, token *GitHubRepositoryToken, err error)
}

// GitHubRepoTokenDatastore is store of GitHubRepositoryToken by Datastore of
// GoogleGitHubRepoTokenDatastore is kvStore of GitHubRepositoryToken by Datastore of
// Google Appengine.
type GitHubRepoTokenDatastore struct{}
type GoogleGitHubRepoTokenDatastore struct{}

func (g *GitHubRepoTokenDatastore) newKey(owner, repo string) *datastore.Key {
type LocalKVGitHubRepoTokenStore struct {
KvStore gokv.Store
}

func (l LocalKVGitHubRepoTokenStore) Put(ctx context.Context, token *GitHubRepositoryToken) error {

err := l.KvStore.Set(token.RepositoryOwner+"/"+token.RepositoryName, token)

if err != nil {
return err
}

return nil
}

func (l LocalKVGitHubRepoTokenStore) Get(ctx context.Context, owner, repo string) (ok bool, token *GitHubRepositoryToken, err error) {

token = &GitHubRepositoryToken{}

get, err := l.KvStore.Get(owner+"/"+repo, token)
if err != nil {
return false, nil, err
}

return get, token, nil
}

func (g *GoogleGitHubRepoTokenDatastore) newKey(owner, repo string) *datastore.Key {
kind := "GitHubRepositoryToken"
return datastore.NameKey(kind, fmt.Sprintf("%s/%s", owner, repo), nil)
}

// Put upserts GitHubRepositoryToken.
func (g *GitHubRepoTokenDatastore) Put(ctx context.Context, token *GitHubRepositoryToken) error {
func (g *GoogleGitHubRepoTokenDatastore) Put(ctx context.Context, token *GitHubRepositoryToken) error {
key := g.newKey(token.RepositoryOwner, token.RepositoryName)
d, err := datastoreClient(ctx)
if err != nil {
Expand All @@ -48,7 +76,7 @@ func (g *GitHubRepoTokenDatastore) Put(ctx context.Context, token *GitHubReposit
return err
}

func (g *GitHubRepoTokenDatastore) Get(ctx context.Context, owner, repo string) (ok bool, token *GitHubRepositoryToken, err error) {
func (g *GoogleGitHubRepoTokenDatastore) Get(ctx context.Context, owner, repo string) (ok bool, token *GitHubRepositoryToken, err error) {
key := g.newKey(owner, repo)
token = new(GitHubRepositoryToken)
d, err := datastoreClient(ctx)
Expand Down
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,13 @@ require (
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/philippgille/gokv/encoding v0.0.0-20191011213304-eb77f15b9c61 // indirect
github.com/philippgille/gokv/util v0.0.0-20191011213304-eb77f15b9c61 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/prometheus v2.5.0+incompatible // indirect
github.com/stretchr/objx v0.5.0 // indirect
go.etcd.io/bbolt v1.3.3 // indirect
golang.org/x/crypto v0.16.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
Expand All @@ -69,4 +73,7 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect
google.golang.org/grpc v1.57.1 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
github.com/philippgille/gokv v0.6.0
github.com/philippgille/gokv/bbolt v0.6.0
)