Skip to content

Commit db1161c

Browse files
committed
feat: Support for github enterprise urls added (custom domains)
By default github.com is being used to register the runners, we have certain cases where github enterprise is used with a custom domain. - Option added in the config to provide custom enterprise urls - Ability added to skip tls verification in case of self signed certificates
1 parent 330b0df commit db1161c

6 files changed

Lines changed: 230 additions & 95 deletions

File tree

helper/github/client.go

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package github
22

33
import (
4+
"crypto/tls"
45
"net/http"
56

67
"github.com/bradleyfalzon/ghinstallation"
@@ -10,26 +11,62 @@ import (
1011
// Client is a wrapper around GitHub client that supports GitHub App authentication for multiple installations.
1112
type Client struct {
1213
*github.Client
13-
14-
transport *ghinstallation.AppsTransport
14+
transport *ghinstallation.AppsTransport
15+
skipTLSVerify bool
1516
}
1617

17-
// NewClient creates a new Client.
18-
func NewClient(appID int64, appPrivateKey string) (*Client, error) {
19-
transport, err := ghinstallation.NewAppsTransport(http.DefaultTransport, appID, []byte(appPrivateKey))
18+
// NewClient creates a new GitHub App client that supports both GitHub.com and GitHub Enterprise API URLs.
19+
// githubEnterpriseApiUrl should be a full API base URL (e.g. https://api.githubenterprise.example.com/).
20+
func NewClient(appID int64, appPrivateKey string, githubEnterpriseApiUrl string, skipTLSVerify bool) (*Client, error) {
21+
baseTransport := http.DefaultTransport.(*http.Transport).Clone()
22+
if skipTLSVerify {
23+
if baseTransport.TLSClientConfig == nil {
24+
baseTransport.TLSClientConfig = &tls.Config{}
25+
}
26+
baseTransport.TLSClientConfig.InsecureSkipVerify = true
27+
// Prefer TLS 1.2+ even when skipping verification.
28+
baseTransport.TLSClientConfig.MinVersion = tls.VersionTLS12
29+
}
30+
31+
transport, err := ghinstallation.NewAppsTransport(baseTransport, appID, []byte(appPrivateKey))
2032
if err != nil {
2133
return nil, err
2234
}
2335

24-
client := &Client{
25-
Client: github.NewClient(&http.Client{Transport: transport}),
26-
transport: transport,
36+
httpClient := &http.Client{Transport: transport}
37+
ghClient := github.NewClient(httpClient)
38+
39+
if githubEnterpriseApiUrl != "" {
40+
ghClient, err = ghClient.WithEnterpriseURLs(githubEnterpriseApiUrl, "")
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
transport.BaseURL = githubEnterpriseApiUrl
2746
}
2847

29-
return client, nil
48+
return &Client{
49+
Client: ghClient,
50+
transport: transport,
51+
skipTLSVerify: skipTLSVerify,
52+
}, nil
3053
}
3154

32-
// Installation returns a new GitHub client for the given installation ID.
3355
func (c *Client) Installation(installationID int64) *github.Client {
34-
return github.NewClient(&http.Client{Transport: ghinstallation.NewFromAppsTransport(c.transport, installationID)})
56+
installationTransport := ghinstallation.NewFromAppsTransport(c.transport, installationID)
57+
httpClient := &http.Client{Transport: installationTransport}
58+
59+
installationClient := github.NewClient(httpClient)
60+
61+
// Detect if we're using GitHub Enterprise (since DefaultBaseURL is no longer exported)
62+
if c.Client.BaseURL != nil && c.Client.BaseURL.String() != "https://api.github.com/" {
63+
if enterpriseClient, err := installationClient.WithEnterpriseURLs(
64+
c.Client.BaseURL.String(),
65+
c.Client.UploadURL.String(),
66+
); err == nil {
67+
return enterpriseClient
68+
}
69+
}
70+
71+
return installationClient
3572
}

helper/github/client_test.go

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,67 @@ import (
77
"github.com/stretchr/testify/assert"
88
)
99

10-
func TestNewClient_Success(t *testing.T) {
10+
func TestNewClient_Success_GitHubCom(t *testing.T) {
1111
key, err := os.ReadFile("testdata/test.key")
1212
if err != nil {
1313
t.Fatal(err)
1414
}
1515

16-
client, err := NewClient(12345, string(key))
16+
client, err := NewClient(12345, string(key), "", false)
1717
assert.NoError(t, err)
1818
assert.NotNil(t, client)
19+
assert.NotNil(t, client.Client)
20+
assert.NotNil(t, client.transport)
21+
22+
// Should default to GitHub.com
23+
assert.Contains(t, client.Client.BaseURL.String(), "https://api.github.com/")
24+
}
25+
26+
func TestNewClient_Success_GitHubCom_TLS(t *testing.T) {
27+
key, err := os.ReadFile("testdata/test.key")
28+
if err != nil {
29+
t.Fatal(err)
30+
}
31+
32+
client, err := NewClient(12345, string(key), "", true)
33+
assert.NoError(t, err)
34+
assert.NotNil(t, client)
35+
}
36+
37+
func TestNewClient_Success_Enterprise(t *testing.T) {
38+
key, err := os.ReadFile("testdata/test.key")
39+
if err != nil {
40+
t.Fatal(err)
41+
}
42+
43+
fakeEnterpriseURL := "https://api.githubenterprise.example.com/"
44+
45+
client, err := NewClient(12345, string(key), fakeEnterpriseURL, false)
46+
assert.NoError(t, err)
47+
assert.NotNil(t, client)
48+
assert.NotNil(t, client.transport)
49+
50+
// Ensure the Enterprise base URL was applied
51+
assert.Contains(t, client.Client.BaseURL.String(), fakeEnterpriseURL)
52+
assert.Equal(t, client.transport.BaseURL, fakeEnterpriseURL)
53+
}
54+
55+
func TestNewClient_Success_Enterprise_TLS(t *testing.T) {
56+
key, err := os.ReadFile("testdata/test.key")
57+
if err != nil {
58+
t.Fatal(err)
59+
}
60+
61+
fakeEnterpriseURL := "https://api.githubenterprise.example.com/"
62+
63+
client, err := NewClient(12345, string(key), fakeEnterpriseURL, true)
64+
assert.NoError(t, err)
65+
assert.NotNil(t, client)
66+
assert.Contains(t, client.Client.BaseURL.String(), fakeEnterpriseURL)
1967
}
2068

2169
func TestNewClient_Failure(t *testing.T) {
22-
client, err := NewClient(12345, "")
70+
client, err := NewClient(12345, "", "", false)
2371
assert.Error(t, err)
2472
assert.Nil(t, client)
2573
}
@@ -30,9 +78,32 @@ func TestClientInstallation(t *testing.T) {
3078
t.Fatal(err)
3179
}
3280

33-
client, err := NewClient(12345, string(key))
81+
client, err := NewClient(12345, string(key), "", false)
3482
assert.NoError(t, err)
83+
assert.NotNil(t, client)
3584

3685
installation := client.Installation(12345)
3786
assert.NotNil(t, installation)
87+
88+
// The installation client should still have a valid BaseURL
89+
assert.Contains(t, installation.BaseURL.String(), "https://api.github.com/")
90+
}
91+
92+
func TestClientInstallation_Enterprise(t *testing.T) {
93+
key, err := os.ReadFile("testdata/test.key")
94+
if err != nil {
95+
t.Fatal(err)
96+
}
97+
98+
fakeEnterpriseURL := "https://api.githubenterprise.example.com/"
99+
100+
client, err := NewClient(12345, string(key), fakeEnterpriseURL, true)
101+
assert.NoError(t, err)
102+
assert.NotNil(t, client)
103+
104+
installation := client.Installation(12345)
105+
assert.NotNil(t, installation)
106+
107+
// Should retain enterprise base URL
108+
assert.Contains(t, installation.BaseURL.String(), fakeEnterpriseURL)
38109
}

server/config.go

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,23 @@ type MetricsConfig struct {
2828
}
2929

3030
type GitHubConfig struct {
31-
AppPrivateKey string `yaml:"app_private_key" validate:"required"`
32-
AppID int64 `yaml:"app_id" validate:"required"`
31+
AppPrivateKey string `yaml:"app_private_key" validate:"required"`
32+
AppID int64 `yaml:"app_id" validate:"required"`
33+
EnterpriseApiUrl string `yaml:"enterprise_api_url" validate:"omitempty,url"`
34+
SkipTLSVerify bool `yaml:"skip_tls_verify" validate:"skiptls_if_enterprise"`
3335
}
3436

3537
type RunnerConfig struct {
3638
Name string `yaml:"name" validate:"required"`
37-
ImagePullPolicy string `yaml:"image_pull_policy" validate:"required,oneof=always never ifnotpresent"`
39+
ImagePullPolicy string `yaml:"image_pull_policy" validate:"required,oneof=Always Never IfNotPresent"`
3840
Image string `yaml:"image" validate:"required"`
3941
Organization string `yaml:"organization" validate:"required"`
4042
GroupID int64 `yaml:"group_id" validate:"required"`
4143
Labels []string `yaml:"labels" validate:"required"`
4244
}
4345

4446
type FirecrackerConfig struct {
45-
BinaryPath string `yaml:"binary_path" `
47+
BinaryPath string `yaml:"binary_path"`
4648
KernelImagePath string `yaml:"kernel_image_path"`
4749
KernelArgs string `yaml:"kernel_args"`
4850
MachineConfig FirecrackerMachineConfig `yaml:"machine_config"`
@@ -56,32 +58,33 @@ type FirecrackerMachineConfig struct {
5658

5759
// DefaultConfig creates a new Config with default values.
5860
func DefaultConfig() *Config {
59-
c := &Config{
61+
return &Config{
6062
BindAddress: ":8080",
6163
Metrics: &MetricsConfig{Enabled: true, Address: ":8081"},
6264
BasicAuthEnabled: false,
6365
BasicAuthUsers: map[string]string{},
64-
GitHub: &GitHubConfig{AppPrivateKey: "", AppID: 0},
65-
Pools: []*PoolConfig{},
66-
LogLevel: "debug",
67-
Debug: false,
66+
GitHub: &GitHubConfig{
67+
AppPrivateKey: "",
68+
AppID: 0,
69+
EnterpriseApiUrl: "", // empty = GitHub.com
70+
SkipTLSVerify: false, // default: do not skip TLS
71+
},
72+
Pools: []*PoolConfig{},
73+
LogLevel: "debug",
74+
Debug: false,
6875
}
69-
70-
return c
7176
}
7277

7378
// NewConfigFromFile creates a new Config from a file.
7479
func NewConfig(path string) (*Config, error) {
7580
c := DefaultConfig()
7681
c.path = path
7782

78-
err := c.Load()
79-
if err != nil {
83+
if err := c.Load(); err != nil {
8084
return nil, err
8185
}
8286

83-
err = c.Validate()
84-
if err != nil {
87+
if err := c.Validate(); err != nil {
8588
return nil, fmt.Errorf("validate: %w", err)
8689
}
8790

@@ -94,13 +97,24 @@ func (c *Config) Load() error {
9497
if err != nil {
9598
return fmt.Errorf("open file: %w", err)
9699
}
97-
98100
defer file.Close()
99101

100102
return yaml.NewDecoder(file).Decode(c)
101103
}
102104

103105
// Validate validates the configuration.
104106
func (c *Config) Validate() error {
105-
return validator.New().Struct(c)
107+
v := validator.New()
108+
109+
// Custom validation: SkipTLSVerify can only be true if EnterpriseApiUrl is set
110+
_ = v.RegisterValidation("skiptls_if_enterprise", func(fl validator.FieldLevel) bool {
111+
cfg := fl.Parent().Interface().(GitHubConfig)
112+
if cfg.SkipTLSVerify && cfg.EnterpriseApiUrl == "" {
113+
return false
114+
}
115+
return true
116+
})
117+
118+
// Apply struct validation
119+
return v.Struct(c)
106120
}

server/config_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,15 @@ import (
99
func TestNewConfig(t *testing.T) {
1010
config, err := NewConfig("testdata/config1.yaml")
1111
if err != nil {
12-
t.Errorf("unexpected error: %v", err)
12+
t.Fatalf("unexpected error: %v", err)
1313
}
1414

1515
assert.Equal(t, "testdata/config1.yaml", config.path)
16+
17+
// Check GitHub config values
18+
assert.NotNil(t, config.GitHub)
19+
assert.NotEmpty(t, config.GitHub.AppPrivateKey)
20+
assert.NotZero(t, config.GitHub.AppID)
21+
assert.Equal(t, "https://api.githubenterprise.example.com/api/v3", config.GitHub.EnterpriseApiUrl)
22+
assert.True(t, config.GitHub.SkipTLSVerify)
1623
}

0 commit comments

Comments
 (0)