Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 48 additions & 11 deletions helper/github/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package github

import (
"crypto/tls"
"net/http"

"github.com/bradleyfalzon/ghinstallation"
Expand All @@ -10,26 +11,62 @@ import (
// Client is a wrapper around GitHub client that supports GitHub App authentication for multiple installations.
type Client struct {
*github.Client

transport *ghinstallation.AppsTransport
transport *ghinstallation.AppsTransport
skipTLSVerify bool
}

// NewClient creates a new Client.
func NewClient(appID int64, appPrivateKey string) (*Client, error) {
transport, err := ghinstallation.NewAppsTransport(http.DefaultTransport, appID, []byte(appPrivateKey))
// NewClient creates a new GitHub App client that supports both GitHub.com and GitHub Enterprise API URLs.
// githubEnterpriseApiUrl should be a full API base URL (e.g. https://api.githubenterprise.example.com/).
func NewClient(appID int64, appPrivateKey string, githubEnterpriseApiUrl string, skipTLSVerify bool) (*Client, error) {
baseTransport := http.DefaultTransport.(*http.Transport).Clone()
if skipTLSVerify {
if baseTransport.TLSClientConfig == nil {
baseTransport.TLSClientConfig = &tls.Config{}
}
baseTransport.TLSClientConfig.InsecureSkipVerify = true
// Prefer TLS 1.2+ even when skipping verification.
baseTransport.TLSClientConfig.MinVersion = tls.VersionTLS12
}

transport, err := ghinstallation.NewAppsTransport(baseTransport, appID, []byte(appPrivateKey))
if err != nil {
return nil, err
}

client := &Client{
Client: github.NewClient(&http.Client{Transport: transport}),
transport: transport,
httpClient := &http.Client{Transport: transport}
ghClient := github.NewClient(httpClient)

if githubEnterpriseApiUrl != "" {
ghClient, err = ghClient.WithEnterpriseURLs(githubEnterpriseApiUrl, "")
if err != nil {
return nil, err
}

transport.BaseURL = githubEnterpriseApiUrl
}

return client, nil
return &Client{
Client: ghClient,
transport: transport,
skipTLSVerify: skipTLSVerify,
}, nil
}

// Installation returns a new GitHub client for the given installation ID.
func (c *Client) Installation(installationID int64) *github.Client {
return github.NewClient(&http.Client{Transport: ghinstallation.NewFromAppsTransport(c.transport, installationID)})
installationTransport := ghinstallation.NewFromAppsTransport(c.transport, installationID)
httpClient := &http.Client{Transport: installationTransport}

installationClient := github.NewClient(httpClient)

// Detect if we're using GitHub Enterprise (since DefaultBaseURL is no longer exported)
if c.Client.BaseURL != nil && c.Client.BaseURL.String() != "https://api.github.com/" {
if enterpriseClient, err := installationClient.WithEnterpriseURLs(
c.Client.BaseURL.String(),
c.Client.UploadURL.String(),
); err == nil {
return enterpriseClient
}
}

return installationClient
}
79 changes: 75 additions & 4 deletions helper/github/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,67 @@ import (
"github.com/stretchr/testify/assert"
)

func TestNewClient_Success(t *testing.T) {
func TestNewClient_Success_GitHubCom(t *testing.T) {
key, err := os.ReadFile("testdata/test.key")
if err != nil {
t.Fatal(err)
}

client, err := NewClient(12345, string(key))
client, err := NewClient(12345, string(key), "", false)
assert.NoError(t, err)
assert.NotNil(t, client)
assert.NotNil(t, client.Client)
assert.NotNil(t, client.transport)

// Should default to GitHub.com
assert.Contains(t, client.Client.BaseURL.String(), "https://api.github.com/")
}

func TestNewClient_Success_GitHubCom_TLS(t *testing.T) {
key, err := os.ReadFile("testdata/test.key")
if err != nil {
t.Fatal(err)
}

client, err := NewClient(12345, string(key), "", true)
assert.NoError(t, err)
assert.NotNil(t, client)
}

func TestNewClient_Success_Enterprise(t *testing.T) {
key, err := os.ReadFile("testdata/test.key")
if err != nil {
t.Fatal(err)
}

fakeEnterpriseURL := "https://api.githubenterprise.example.com/"

client, err := NewClient(12345, string(key), fakeEnterpriseURL, false)
assert.NoError(t, err)
assert.NotNil(t, client)
assert.NotNil(t, client.transport)

// Ensure the Enterprise base URL was applied
assert.Contains(t, client.Client.BaseURL.String(), fakeEnterpriseURL)
assert.Equal(t, client.transport.BaseURL, fakeEnterpriseURL)
}

func TestNewClient_Success_Enterprise_TLS(t *testing.T) {
key, err := os.ReadFile("testdata/test.key")
if err != nil {
t.Fatal(err)
}

fakeEnterpriseURL := "https://api.githubenterprise.example.com/"

client, err := NewClient(12345, string(key), fakeEnterpriseURL, true)
assert.NoError(t, err)
assert.NotNil(t, client)
assert.Contains(t, client.Client.BaseURL.String(), fakeEnterpriseURL)
}

func TestNewClient_Failure(t *testing.T) {
client, err := NewClient(12345, "")
client, err := NewClient(12345, "", "", false)
assert.Error(t, err)
assert.Nil(t, client)
}
Expand All @@ -30,9 +78,32 @@ func TestClientInstallation(t *testing.T) {
t.Fatal(err)
}

client, err := NewClient(12345, string(key))
client, err := NewClient(12345, string(key), "", false)
assert.NoError(t, err)
assert.NotNil(t, client)

installation := client.Installation(12345)
assert.NotNil(t, installation)

// The installation client should still have a valid BaseURL
assert.Contains(t, installation.BaseURL.String(), "https://api.github.com/")
}

func TestClientInstallation_Enterprise(t *testing.T) {
key, err := os.ReadFile("testdata/test.key")
if err != nil {
t.Fatal(err)
}

fakeEnterpriseURL := "https://api.githubenterprise.example.com/"

client, err := NewClient(12345, string(key), fakeEnterpriseURL, true)
assert.NoError(t, err)
assert.NotNil(t, client)

installation := client.Installation(12345)
assert.NotNil(t, installation)

// Should retain enterprise base URL
assert.Contains(t, installation.BaseURL.String(), fakeEnterpriseURL)
}
Loading