diff --git a/github/apps.go b/github/apps.go index 8db62999cd..b35350b748 100644 --- a/github/apps.go +++ b/github/apps.go @@ -9,7 +9,6 @@ import ( "io" "net/http" "net/url" - "path" "time" "github.com/go-jose/go-jose/v3" @@ -18,13 +17,13 @@ import ( // GenerateOAuthTokenFromApp generates a GitHub OAuth access token from a set of valid GitHub App credentials. // The returned token can be used to interact with both GitHub's REST and GraphQL APIs. -func GenerateOAuthTokenFromApp(baseURL *url.URL, appID, appInstallationID, pemData string) (string, error) { +func GenerateOAuthTokenFromApp(apiURL *url.URL, appID, appInstallationID, pemData string) (string, error) { appJWT, err := generateAppJWT(appID, time.Now(), []byte(pemData)) if err != nil { return "", err } - token, err := getInstallationAccessToken(baseURL, appJWT, appInstallationID) + token, err := getInstallationAccessToken(apiURL, appJWT, appInstallationID) if err != nil { return "", err } @@ -32,15 +31,8 @@ func GenerateOAuthTokenFromApp(baseURL *url.URL, appID, appInstallationID, pemDa return token, nil } -func getInstallationAccessToken(baseURL *url.URL, jwt, installationID string) (string, error) { - hostname := baseURL.Hostname() - if hostname != DotComHost && !GHECDataResidencyHostMatch.MatchString(hostname) { - baseURL.Path = path.Join(baseURL.Path, "api/v3/") - } - - baseURL.Path = path.Join(baseURL.Path, "app/installations/", installationID, "access_tokens") - - req, err := http.NewRequest(http.MethodPost, baseURL.String(), nil) +func getInstallationAccessToken(apiURL *url.URL, jwt, installationID string) (string, error) { + req, err := http.NewRequest(http.MethodPost, apiURL.JoinPath("app/installations", installationID, "access_tokens").String(), nil) if err != nil { return "", err } diff --git a/github/apps_test.go b/github/apps_test.go index 628b4639f6..769285a767 100644 --- a/github/apps_test.go +++ b/github/apps_test.go @@ -146,7 +146,7 @@ func TestGetInstallationAccessToken(t *testing.T) { ts := githubApiMock([]*mockResponse{ { - ExpectedUri: fmt.Sprintf("/api/v3/app/installations/%s/access_tokens", testGitHubAppInstallationID), + ExpectedUri: fmt.Sprintf("/app/installations/%s/access_tokens", testGitHubAppInstallationID), ExpectedHeaders: map[string]string{ "Accept": "application/vnd.github.v3+json", "Authorization": fmt.Sprintf("Bearer %s", fakeJWT), diff --git a/github/config.go b/github/config.go index 61f214a4aa..8cb5d5990a 100644 --- a/github/config.go +++ b/github/config.go @@ -5,7 +5,6 @@ import ( "fmt" "net/http" "net/url" - "path" "regexp" "strings" "time" @@ -19,7 +18,8 @@ import ( type Config struct { Token string Owner string - BaseURL string + BaseURL *url.URL + IsGHES bool Insecure bool WriteDelay time.Duration ReadDelay time.Duration @@ -38,12 +38,23 @@ type Owner struct { IsOrganization bool } -// DotComHost is the hostname for GitHub.com API. -const DotComHost = "api.github.com" +const ( + // DotComAPIURL is the base API URL for github.com. + DotComAPIURL = "https://api.github.com/" + // DotComHost is the hostname for github.com. + DotComHost = "github.com" + // DotComAPIHost is the API hostname for github.com. + DotComAPIHost = "api.github.com" + // GHESRESTAPISuffix is the rest api suffix for GitHub Enterprise Server. + GHESRESTAPIPath = "api/v3/" +) -// GHECDataResidencyHostMatch is a regex to match a GitHub Enterprise Cloud data residency host: -// https://[hostname].ghe.com/ instances expect paths that behave similar to GitHub.com, not GitHub Enterprise Server. -var GHECDataResidencyHostMatch = regexp.MustCompile(`^[a-zA-Z0-9.\-]+\.ghe\.com\/?$`) +var ( + // GHECHostMatch is a regex to match GitHub Enterprise Cloud hosts. + GHECHostMatch = regexp.MustCompile(`\.ghe\.com$`) + // GHECAPIHostMatch is a regex to match GitHub Enterprise Cloud API hosts. + GHECAPIHostMatch = regexp.MustCompile(`^api\.[a-zA-Z0-9-]+\.ghe\.com$`) +) func RateLimitedHTTPClient(client *http.Client, writeDelay, readDelay, retryDelay time.Duration, parallelRequests bool, retryableErrors map[int]bool, maxRetries int) *http.Client { client.Transport = NewEtagTransport(client.Transport) @@ -81,38 +92,24 @@ func (c *Config) AnonymousHTTPClient() *http.Client { } func (c *Config) NewGraphQLClient(client *http.Client) (*githubv4.Client, error) { - uv4, err := url.Parse(c.BaseURL) - if err != nil { - return nil, err - } - - hostname := uv4.Hostname() - if hostname != DotComHost && !GHECDataResidencyHostMatch.MatchString(hostname) { - uv4.Path = path.Join(uv4.Path, "api/graphql/") + var path string + if c.IsGHES { + path = "api/graphql" } else { - uv4.Path = path.Join(uv4.Path, "graphql") + path = "graphql" } - return githubv4.NewEnterpriseClient(uv4.String(), client), nil + return githubv4.NewEnterpriseClient(c.BaseURL.JoinPath(path).String(), client), nil } func (c *Config) NewRESTClient(client *http.Client) (*github.Client, error) { - uv3, err := url.Parse(c.BaseURL) - if err != nil { - return nil, err + path := "" + if c.IsGHES { + path = GHESRESTAPIPath } - hostname := uv3.Hostname() - if hostname != DotComHost && !GHECDataResidencyHostMatch.MatchString(hostname) { - uv3.Path = fmt.Sprintf("%s/", path.Join(uv3.Path, "api/v3")) - } - - v3client, err := github.NewClient(client).WithEnterpriseURLs(uv3.String(), "") - if err != nil { - return nil, err - } - - v3client.BaseURL = uv3 + v3client := github.NewClient(client) + v3client.BaseURL = c.BaseURL.JoinPath(path) return v3client, nil } @@ -199,3 +196,45 @@ func (injector *previewHeaderInjectorTransport) RoundTrip(req *http.Request) (*h } return injector.rt.RoundTrip(req) } + +// getBaseURL returns a correctly configured base URL and a bool as to if this is GitHub Enterprise Server. +func getBaseURL(s string) (*url.URL, bool, error) { + if len(s) == 0 { + s = DotComAPIURL + } + + u, err := url.Parse(s) + if err != nil { + return nil, false, err + } + + if !u.IsAbs() { + return nil, false, fmt.Errorf("base url must be absolute") + } + + u = u.JoinPath("/") + + switch { + case u.Host == DotComAPIHost: + case u.Host == DotComHost: + u.Host = DotComAPIHost + case GHECAPIHostMatch.MatchString(u.Host): + case GHECHostMatch.MatchString(u.Host): + u.Host = fmt.Sprintf("api.%s", u.Host) + default: + u.Path = strings.TrimSuffix(u.Path, GHESRESTAPIPath) + return u, true, nil + } + + if u.Scheme != "https" { + return nil, false, fmt.Errorf("base url for github.com or ghe.com must use the https scheme") + } + + if len(u.Path) > 1 { + return nil, false, fmt.Errorf("base url for github.com or ghe.com must not contain a path, got %s", u.Path) + } + + u.Path = "/" + + return u, false, nil +} diff --git a/github/config_test.go b/github/config_test.go index 857a0031bf..e72bc35e60 100644 --- a/github/config_test.go +++ b/github/config_test.go @@ -2,64 +2,155 @@ package github import ( "context" - "net/url" "testing" "github.com/shurcooL/githubv4" ) -func TestGHECDataResidencyHostMatch(t *testing.T) { +func Test_getBaseURL(t *testing.T) { testCases := []struct { + name string url string - matches bool - description string + expectedURL string + isGHES bool + errors bool }{ { - url: "https://customer.ghe.com/", - matches: true, - description: "GHEC data residency URL with customer name", + name: "dotcom", + url: "https://api.github.com/", + expectedURL: "https://api.github.com/", + isGHES: false, + errors: false, }, { - url: "https://customer-name.ghe.com/", - matches: true, - description: "GHEC data residency URL with hyphenated name", + name: "dotcom no trailing slash", + url: "https://api.github.com", + expectedURL: "https://api.github.com/", + isGHES: false, + errors: false, }, { - url: "https://customer.ghe.com", - matches: true, - description: "GHEC data residency URL without a trailing slash", + name: "dotcom ui", + url: "https://github.com/", + expectedURL: "https://api.github.com/", + isGHES: false, + errors: false, }, { - url: "https://ghe.com/", - matches: false, - description: "GHEC domain without subdomain", + name: "dotcom http errors", + url: "http://api.github.com/", + expectedURL: "", + isGHES: false, + errors: true, }, { - url: "https://github.com/", - matches: false, - description: "GitHub.com URL", + name: "dotcom with path errors", + url: "https://api.github.com/xxx/", + expectedURL: "", + isGHES: false, + errors: true, }, { - url: "https://api.github.com/", - matches: false, - description: "GitHub.com API URL", + name: "ghec", + url: "https://api.customer.ghe.com/", + expectedURL: "https://api.customer.ghe.com/", + isGHES: false, + errors: false, }, { + name: "ghec no trailing slash", + url: "https://api.customer.ghe.com", + expectedURL: "https://api.customer.ghe.com/", + isGHES: false, + errors: false, + }, + { + name: "ghec ui", + url: "https://customer.ghe.com/", + expectedURL: "https://api.customer.ghe.com/", + isGHES: false, + errors: false, + }, + { + name: "ghec http errors", + url: "http://api.customer.ghe.com/", + expectedURL: "", + isGHES: false, + errors: true, + }, + { + name: "ghec with path errors", + url: "https://api.customer.ghe.com/xxx/", + expectedURL: "", + isGHES: false, + errors: true, + }, + { + name: "ghes", url: "https://example.com/", - matches: false, - description: "Generic URL", + expectedURL: "https://example.com/", + isGHES: true, + errors: false, + }, + { + name: "ghes no trailing slash", + url: "https://example.com", + expectedURL: "https://example.com/", + isGHES: true, + errors: false, + }, + { + name: "ghes with path prefix", + url: "https://example.com/test/", + expectedURL: "https://example.com/test/", + isGHES: true, + errors: false, + }, + { + name: "empty url returns dotcom", + url: "", + expectedURL: "https://api.github.com/", + isGHES: false, + errors: false, + }, + { + name: "not absolute url errors", + url: "example.com/", + expectedURL: "", + isGHES: false, + errors: true, + }, + { + name: "invalid url errors", + url: "xxx", + expectedURL: "", + isGHES: false, + errors: true, }, } for _, tc := range testCases { - t.Run(tc.description, func(t *testing.T) { - u, err := url.Parse(tc.url) + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + baseURL, isGHES, err := getBaseURL(tc.url) if err != nil { - t.Fatalf("failed to parse URL %q: %s", tc.url, err) + if tc.errors { + return + } + t.Fatalf("expected no error, got: %v", err) } - matches := GHECDataResidencyHostMatch.MatchString(u.Hostname()) - if matches != tc.matches { - t.Errorf("URL %q: expected match=%v, got %v", tc.url, tc.matches, matches) + + if tc.errors { + t.Fatalf("expected error, got none") + } + + if baseURL.String() != tc.expectedURL { + t.Errorf("expected base URL %q, got %q", tc.expectedURL, baseURL.String()) + } + + if isGHES != tc.isGHES { + t.Errorf("expected isGHES to be %v, got %v", tc.isGHES, isGHES) } }) } @@ -71,8 +162,13 @@ func TestAccConfigMeta(t *testing.T) { return } + baseURL, _, err := getBaseURL(DotComAPIURL) + if err != nil { + t.Fatalf("failed to parse test base URL: %s", err.Error()) + } + t.Run("returns an anonymous client for the v3 REST API", func(t *testing.T) { - config := Config{BaseURL: "https://api.github.com/"} + config := Config{BaseURL: baseURL} meta, err := config.Meta() if err != nil { t.Fatalf("failed to return meta without error: %s", err.Error()) @@ -86,15 +182,10 @@ func TestAccConfigMeta(t *testing.T) { } }) - t.Run("returns an anonymous client for the v4 GraphQL API", func(t *testing.T) { - // https://developer.github.com/v4/guides/forming-calls/#authenticating-with-graphql - t.Skip("anonymous client for the v4 GraphQL API is unsupported") - }) - t.Run("returns a v3 REST API client to manage individual resources", func(t *testing.T) { config := Config{ Token: testToken, - BaseURL: "https://api.github.com/", + BaseURL: baseURL, } meta, err := config.Meta() if err != nil { @@ -112,7 +203,7 @@ func TestAccConfigMeta(t *testing.T) { t.Run("returns a v3 REST API client with max retries", func(t *testing.T) { config := Config{ Token: testToken, - BaseURL: "https://api.github.com/", + BaseURL: baseURL, RetryableErrors: map[int]bool{ 500: true, 502: true, @@ -135,7 +226,7 @@ func TestAccConfigMeta(t *testing.T) { t.Run("returns a v4 GraphQL API client to manage individual resources", func(t *testing.T) { config := Config{ Token: testToken, - BaseURL: "https://api.github.com/", + BaseURL: baseURL, } meta, err := config.Meta() if err != nil { @@ -157,7 +248,7 @@ func TestAccConfigMeta(t *testing.T) { t.Run("returns a v3 REST API client to manage organization resources", func(t *testing.T) { config := Config{ Token: testToken, - BaseURL: "https://api.github.com/", + BaseURL: baseURL, Owner: testOrganization, } meta, err := config.Meta() @@ -176,7 +267,7 @@ func TestAccConfigMeta(t *testing.T) { t.Run("returns a v4 GraphQL API client to manage organization resources", func(t *testing.T) { config := Config{ Token: testToken, - BaseURL: "https://api.github.com/", + BaseURL: baseURL, Owner: testOrganization, } meta, err := config.Meta() diff --git a/github/data_source_github_app_token.go b/github/data_source_github_app_token.go index 1379630345..69861e046b 100644 --- a/github/data_source_github_app_token.go +++ b/github/data_source_github_app_token.go @@ -41,8 +41,6 @@ func dataSourceGithubAppTokenRead(d *schema.ResourceData, meta any) error { installationID := d.Get("installation_id").(string) pemFile := d.Get("pem_file").(string) - baseURL := meta.(*Owner).v3client.BaseURL - // The Go encoding/pem package only decodes PEM formatted blocks // that contain new lines. Some platforms, like Terraform Cloud, // do not support new lines within Environment Variables. @@ -52,7 +50,7 @@ func dataSourceGithubAppTokenRead(d *schema.ResourceData, meta any) error { // actual new line character before decoding. pemFile = strings.ReplaceAll(pemFile, `\n`, "\n") - token, err := GenerateOAuthTokenFromApp(baseURL, appID, installationID, pemFile) + token, err := GenerateOAuthTokenFromApp(meta.(*Owner).v3client.BaseURL, appID, installationID, pemFile) if err != nil { return err } diff --git a/github/data_source_github_app_token_test.go b/github/data_source_github_app_token_test.go index 6fa7e17da8..2c47f3b4c1 100644 --- a/github/data_source_github_app_token_test.go +++ b/github/data_source_github_app_token_test.go @@ -23,7 +23,7 @@ func TestAccGithubAppTokenDataSource(t *testing.T) { t.Run("creates a application token without error", func(t *testing.T) { ts := githubApiMock([]*mockResponse{ { - ExpectedUri: fmt.Sprintf("/api/v3/app/installations/%s/access_tokens", testGitHubAppInstallationID), + ExpectedUri: fmt.Sprintf("/app/installations/%s/access_tokens", testGitHubAppInstallationID), ExpectedHeaders: map[string]string{ "Accept": "application/vnd.github.v3+json", }, diff --git a/github/provider.go b/github/provider.go index 57f1da449e..a10d84c027 100644 --- a/github/provider.go +++ b/github/provider.go @@ -340,7 +340,6 @@ func init() { func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc { return func(ctx context.Context, d *schema.ResourceData) (any, diag.Diagnostics) { owner := d.Get("owner").(string) - baseURL := d.Get("base_url").(string) token := d.Get("token").(string) insecure := d.Get("insecure").(bool) @@ -360,17 +359,17 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc { } // END backwards compatibility + baseURL, isGHES, err := getBaseURL(d.Get("base_url").(string)) + if err != nil { + return nil, diag.FromErr(err) + } + org := d.Get("organization").(string) if org != "" { log.Printf("[INFO] Selecting organization attribute as owner: %s", org) owner = org } - bu, err := validateBaseURL(baseURL) - if err != nil { - return nil, diag.FromErr(err) - } - if appAuth, ok := d.Get("app_auth").([]any); ok && len(appAuth) > 0 && appAuth[0] != nil { appAuthAttr := appAuth[0].(map[string]any) @@ -401,7 +400,12 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc { return nil, wrapErrors([]error{fmt.Errorf("app_auth.pem_file must be set and contain a non-empty value")}) } - appToken, err := GenerateOAuthTokenFromApp(bu, appID, appInstallationID, appPemFile) + apiPath := "" + if isGHES { + apiPath = GHESRESTAPIPath + } + + appToken, err := GenerateOAuthTokenFromApp(baseURL.JoinPath(apiPath), appID, appInstallationID, appPemFile) if err != nil { return nil, wrapErrors([]error{err}) } @@ -410,7 +414,7 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc { } if token == "" { - token = tokenFromGHCLI(bu) + token = tokenFromGHCLI(baseURL) } writeDelay := d.Get("write_delay_ms").(int) @@ -472,6 +476,7 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc { RetryableErrors: retryableErrors, MaxRetries: maxRetries, ParallelRequests: parallelRequests, + IsGHES: isGHES, } meta, err := config.Meta() @@ -483,30 +488,6 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc { } } -// validateBaseURL checks that the provided base URL is valid and can be used. -func validateBaseURL(b string) (*url.URL, error) { - u, err := url.Parse(b) - if err != nil { - return nil, err - } - - if !u.IsAbs() { - return nil, fmt.Errorf("base_url must be absolute") - } - - hostname := u.Hostname() - if hostname == DotComHost || GHECDataResidencyHostMatch.MatchString(hostname) { - if u.Scheme != "https" { - return nil, fmt.Errorf("base_url for github.com or ghe.com must use the https scheme") - } - if len(u.Path) > 1 { - return nil, fmt.Errorf("base_url for github.com or ghe.com must not contain a path, got %s", u.Path) - } - } - - return u, err -} - // See https://github.com/integrations/terraform-provider-github/issues/1822 func tokenFromGHCLI(u *url.URL) string { ghCliPath := os.Getenv("GH_PATH") @@ -515,8 +496,8 @@ func tokenFromGHCLI(u *url.URL) string { } host := u.Host - if u.Hostname() == DotComHost { - host = "github.com" + if host == DotComAPIHost { + host = DotComHost } out, err := exec.Command(ghCliPath, "auth", "token", "--hostname", host).Output() diff --git a/github/provider_test.go b/github/provider_test.go index f5692810ad..3b4e53c328 100644 --- a/github/provider_test.go +++ b/github/provider_test.go @@ -202,98 +202,3 @@ func TestAccProviderConfigure(t *testing.T) { }) }) } - -func Test_validateBaseURL(t *testing.T) { - testCases := []struct { - name string - url string - valid bool - }{ - { - name: "dotcom", - url: "https://api.github.com/", - valid: true, - }, - { - name: "dotcom_no_slash", - url: "https://api.github.com", - valid: true, - }, - { - name: "dotcom_with_path", - url: "http://api.github.com/test/", - valid: false, - }, - { - name: "dotcom_http", - url: "http://api.github.com/", - valid: false, - }, - { - name: "dotcom_no_scheme", - url: "api.github.com/", - valid: false, - }, - { - name: "ghec", - url: "https://customer.ghe.com/", - valid: true, - }, - { - name: "ghec_no_slash", - url: "https://customer.ghe.com", - valid: true, - }, - { - name: "ghec_with_path", - url: "https://customer.ghe.com/test/", - valid: false, - }, - { - name: "ghec_http", - url: "http://customer.ghe.com/", - valid: false, - }, - { - name: "ghec_no_scheme", - url: "customer.ghe.com/", - valid: false, - }, - { - name: "ghes", - url: "https://example.com/", - valid: true, - }, - { - name: "ghes_no_slash", - url: "https://example.com", - valid: true, - }, - { - name: "ghes_with_path", - url: "https://example.com/test/", - valid: true, - }, - { - name: "ghes_http", - url: "http://example.com/", - valid: true, - }, - { - name: "ghes_no_scheme/", - url: "example.com", - valid: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - _, err := validateBaseURL(tc.url) - if err != nil && tc.valid { - t.Errorf("URL %q: expected valid URL, got error: %s", tc.url, err) - } else if err == nil && !tc.valid { - t.Errorf("URL %q: expected invalid URL, got no error", tc.url) - } - }) - } -} diff --git a/github/provider_utils.go b/github/provider_utils.go index 386a4e4407..47d7c43b17 100644 --- a/github/provider_utils.go +++ b/github/provider_utils.go @@ -78,7 +78,11 @@ func skipUnlessMode(t *testing.T, providerMode string) { } func testAccCheckOrganization() error { - baseURL := os.Getenv("GITHUB_BASE_URL") + baseURL, isGHES, err := getBaseURL(os.Getenv("GITHUB_BASE_URL")) + if err != nil { + return err + } + token := os.Getenv("GITHUB_TOKEN") owner := os.Getenv("GITHUB_OWNER") @@ -94,6 +98,7 @@ func testAccCheckOrganization() error { BaseURL: baseURL, Token: token, Owner: owner, + IsGHES: isGHES, } meta, err := config.Meta() diff --git a/github/resource_github_repository_collaborators_test.go b/github/resource_github_repository_collaborators_test.go index 61d52aa548..3e9e1f010c 100644 --- a/github/resource_github_repository_collaborators_test.go +++ b/github/resource_github_repository_collaborators_test.go @@ -17,7 +17,12 @@ func TestAccGithubRepositoryCollaborators(t *testing.T) { inOrgUser := os.Getenv("GITHUB_IN_ORG_USER") inOrgUser2 := os.Getenv("GITHUB_IN_ORG_USER2") - config := Config{BaseURL: "https://api.github.com/", Owner: testOwnerFunc(), Token: testToken} + baseURL, isGHES, err := getBaseURL(os.Getenv("GITHUB_BASE_URL")) + if err != nil { + t.Fatalf("failed to parse base URL: %s", err.Error()) + } + + config := Config{BaseURL: baseURL, IsGHES: isGHES, Owner: testOwnerFunc(), Token: testToken} meta, err := config.Meta() if err != nil { t.Fatalf("failed to return meta without error: %s", err.Error()) diff --git a/github/resource_github_repository_test.go b/github/resource_github_repository_test.go index c4fab6f9d8..403935e061 100644 --- a/github/resource_github_repository_test.go +++ b/github/resource_github_repository_test.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log" + "os" "regexp" "strings" "testing" @@ -2026,7 +2027,12 @@ func TestAccGithubRepository_fork(t *testing.T) { } func createForkedRepository(repositoryName string) error { - config := Config{BaseURL: "https://api.github.com/", Owner: testOrganizationFunc(), Token: testToken} + baseURL, isGHES, err := getBaseURL(os.Getenv("GITHUB_BASE_URL")) + if err != nil { + return err + } + + config := Config{BaseURL: baseURL, IsGHES: isGHES, Owner: testOrganizationFunc(), Token: testToken} meta, err := config.Meta() if err != nil { return fmt.Errorf("failed to create client: %w", err) diff --git a/github/sweeper_test.go b/github/sweeper_test.go index 9aa05379c1..8cab431d90 100644 --- a/github/sweeper_test.go +++ b/github/sweeper_test.go @@ -21,10 +21,16 @@ func sharedConfigForRegion() (any, error) { return nil, fmt.Errorf("empty GITHUB_OWNER") } + baseURL, isGHES, err := getBaseURL(os.Getenv("GITHUB_BASE_URL")) + if err != nil { + return nil, err + } + config := Config{ Token: os.Getenv("GITHUB_TOKEN"), Owner: os.Getenv("GITHUB_OWNER"), - BaseURL: "", + BaseURL: baseURL, + IsGHES: isGHES, } meta, err := config.Meta()