Skip to content

Commit 4fb14b2

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 4fb14b2

6 files changed

Lines changed: 227 additions & 95 deletions

File tree

helper/github/client.go

Lines changed: 45 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,59 @@ 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
22+
if skipTLSVerify && githubEnterpriseApiUrl != "" {
23+
baseTransport = &http.Transport{
24+
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
25+
}
26+
}
27+
28+
transport, err := ghinstallation.NewAppsTransport(baseTransport, appID, []byte(appPrivateKey))
2029
if err != nil {
2130
return nil, err
2231
}
2332

24-
client := &Client{
25-
Client: github.NewClient(&http.Client{Transport: transport}),
26-
transport: transport,
33+
httpClient := &http.Client{Transport: transport}
34+
ghClient := github.NewClient(httpClient)
35+
36+
if githubEnterpriseApiUrl != "" {
37+
ghClient, err = ghClient.WithEnterpriseURLs(githubEnterpriseApiUrl, "")
38+
if err != nil {
39+
return nil, err
40+
}
41+
42+
transport.BaseURL = githubEnterpriseApiUrl
2743
}
2844

29-
return client, nil
45+
return &Client{
46+
Client: ghClient,
47+
transport: transport,
48+
skipTLSVerify: skipTLSVerify,
49+
}, nil
3050
}
3151

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

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:""`
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)