55 "fmt"
66 "net/http"
77 "net/url"
8- "path"
98 "regexp"
109 "strings"
1110 "time"
@@ -19,7 +18,8 @@ import (
1918type Config struct {
2019 Token string
2120 Owner string
22- BaseURL string
21+ BaseURL * url.URL
22+ IsGHES bool
2323 Insecure bool
2424 WriteDelay time.Duration
2525 ReadDelay time.Duration
@@ -38,12 +38,27 @@ type Owner struct {
3838 IsOrganization bool
3939}
4040
41- // DotComHost is the hostname for GitHub.com API.
42- const DotComHost = "api.github.com"
41+ const (
42+ // DotComAPIURL is the base API URL for github.com.
43+ DotComAPIURL = "https://api.github.com/"
44+ // DotComHost is the hostname for github.com.
45+ DotComHost = "github.com"
46+ // DotComAPIHost is the API hostname for github.com.
47+ DotComAPIHost = "api.github.com"
48+ // DotComRESTAPISuffix is the GraphQL suffix for github.com.
49+ DotComGraphQLPath = "graphql/"
50+ // GHESRESTAPISuffix is the rest api suffix for GitHub Enterprise Server.
51+ GHESRESTAPIPath = "api/v3/"
52+ // GHESGraphQLSuffix is the GraphQL suffix for GitHub Enterprise Server.
53+ GHESGraphQLPath = "api/graphql/"
54+ )
4355
44- // GHECDataResidencyHostMatch is a regex to match a GitHub Enterprise Cloud data residency host:
45- // https://[hostname].ghe.com/ instances expect paths that behave similar to GitHub.com, not GitHub Enterprise Server.
46- var GHECDataResidencyHostMatch = regexp .MustCompile (`^[a-zA-Z0-9.\-]+\.ghe\.com\/?$` )
56+ var (
57+ // GHECHostMatch is a regex to match GitHub Enterprise Cloud hosts.
58+ GHECHostMatch = regexp .MustCompile (`\.ghe\.com$` )
59+ // GHECAPIHostMatch is a regex to match GitHub Enterprise Cloud API hosts.
60+ GHECAPIHostMatch = regexp .MustCompile (`^api\.[a-zA-Z0-9-]+\.ghe\.com$` )
61+ )
4762
4863func RateLimitedHTTPClient (client * http.Client , writeDelay , readDelay , retryDelay time.Duration , parallelRequests bool , retryableErrors map [int ]bool , maxRetries int ) * http.Client {
4964 client .Transport = NewEtagTransport (client .Transport )
@@ -81,38 +96,24 @@ func (c *Config) AnonymousHTTPClient() *http.Client {
8196}
8297
8398func (c * Config ) NewGraphQLClient (client * http.Client ) (* githubv4.Client , error ) {
84- uv4 , err := url .Parse (c .BaseURL )
85- if err != nil {
86- return nil , err
87- }
88-
89- hostname := uv4 .Hostname ()
90- if hostname != DotComHost && ! GHECDataResidencyHostMatch .MatchString (hostname ) {
91- uv4 .Path = path .Join (uv4 .Path , "api/graphql/" )
99+ var path string
100+ if c .IsGHES {
101+ path = GHESGraphQLPath
92102 } else {
93- uv4 . Path = path . Join ( uv4 . Path , "graphql" )
103+ path = DotComGraphQLPath
94104 }
95105
96- return githubv4 .NewEnterpriseClient (uv4 .String (), client ), nil
106+ return githubv4 .NewEnterpriseClient (c . BaseURL . JoinPath ( path ) .String (), client ), nil
97107}
98108
99109func (c * Config ) NewRESTClient (client * http.Client ) (* github.Client , error ) {
100- uv3 , err := url . Parse ( c . BaseURL )
101- if err != nil {
102- return nil , err
110+ path := ""
111+ if c . IsGHES {
112+ path = GHESRESTAPIPath
103113 }
104114
105- hostname := uv3 .Hostname ()
106- if hostname != DotComHost && ! GHECDataResidencyHostMatch .MatchString (hostname ) {
107- uv3 .Path = fmt .Sprintf ("%s/" , path .Join (uv3 .Path , "api/v3" ))
108- }
109-
110- v3client , err := github .NewClient (client ).WithEnterpriseURLs (uv3 .String (), "" )
111- if err != nil {
112- return nil , err
113- }
114-
115- v3client .BaseURL = uv3
115+ v3client := github .NewClient (client )
116+ v3client .BaseURL = c .BaseURL .JoinPath (path )
116117
117118 return v3client , nil
118119}
@@ -199,3 +200,45 @@ func (injector *previewHeaderInjectorTransport) RoundTrip(req *http.Request) (*h
199200 }
200201 return injector .rt .RoundTrip (req )
201202}
203+
204+ // getBaseURL returns a correctly configured base URL and a bool as to if this is GitHub Enterprise Server.
205+ func getBaseURL (s string ) (* url.URL , bool , error ) {
206+ if len (s ) == 0 {
207+ s = DotComAPIURL
208+ }
209+
210+ u , err := url .Parse (s )
211+ if err != nil {
212+ return nil , false , err
213+ }
214+
215+ if ! u .IsAbs () {
216+ return nil , false , fmt .Errorf ("base url must be absolute" )
217+ }
218+
219+ u = u .JoinPath ("/" )
220+
221+ switch {
222+ case u .Host == DotComAPIHost :
223+ case u .Host == DotComHost :
224+ u .Host = DotComAPIHost
225+ case GHECAPIHostMatch .MatchString (u .Host ):
226+ case GHECHostMatch .MatchString (u .Host ):
227+ u .Host = fmt .Sprintf ("api.%s" , u .Host )
228+ default :
229+ u .Path = strings .TrimSuffix (u .Path , GHESRESTAPIPath )
230+ return u , true , nil
231+ }
232+
233+ if u .Scheme != "https" {
234+ return nil , false , fmt .Errorf ("base url for github.com or ghe.com must use the https scheme" )
235+ }
236+
237+ if len (u .Path ) > 1 {
238+ return nil , false , fmt .Errorf ("base url for github.com or ghe.com must not contain a path, got %s" , u .Path )
239+ }
240+
241+ u .Path = "/"
242+
243+ return u , false , nil
244+ }
0 commit comments