mirror of
https://github.com/grafana/grafana.git
synced 2026-08-19 01:34:54 -05:00
Provisioning: Fix customServerURL resolution for GHE (#127113)
* Provisioning: Fix customServerURL resolution for GHE
This commit is contained in:
@@ -31,7 +31,7 @@ type ClientOptions struct {
|
||||
// ClientOption customizes how a GitHub client is created.
|
||||
type ClientOption func(*ClientOptions)
|
||||
|
||||
// WithCustomServerURL targets a GitHub Enterprise Server instance at the given
|
||||
// WithCustomServerURL targets a GitHub Enterprise instance at the given REST API
|
||||
// base URL. An empty url is ignored, keeping the default github.com client.
|
||||
func WithCustomServerURL(url string) ClientOption {
|
||||
return func(o *ClientOptions) {
|
||||
@@ -46,7 +46,15 @@ func (r *Factory) New(ctx context.Context, ghToken common.RawSecureValue, opts .
|
||||
}
|
||||
|
||||
if r.Client != nil {
|
||||
return NewClient(github.NewClient(r.Client)), nil
|
||||
ghClient := github.NewClient(r.Client)
|
||||
if options.customServerURL != "" {
|
||||
enterprise, err := ghClient.WithEnterpriseURLs(options.customServerURL, options.customServerURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to configure GitHub Enterprise URLs for %q: %w", options.customServerURL, err)
|
||||
}
|
||||
ghClient = enterprise
|
||||
}
|
||||
return NewClient(ghClient), nil
|
||||
}
|
||||
|
||||
httpClient := &http.Client{}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
)
|
||||
|
||||
// TestFactoryNew_EnterpriseBaseURL verifies that a resolved REST API base URL (as produced by
|
||||
// the githubenterprise config's CustomServerURL) maps to the expected go-github BaseURL. The
|
||||
// web-URL -> API-URL resolution per deployment type is tested where it lives, in that config.
|
||||
func TestFactoryNew_EnterpriseBaseURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
serverURL string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no custom server url uses default api.github.com",
|
||||
serverURL: "",
|
||||
expected: "https://api.github.com/",
|
||||
},
|
||||
{
|
||||
name: "GHES self-hosted appends /api/v3",
|
||||
serverURL: "https://custom-ghe-url.com",
|
||||
expected: "https://custom-ghe-url.com/api/v3/",
|
||||
},
|
||||
{
|
||||
name: "GHEC data residency api host stays without /api/v3",
|
||||
serverURL: "https://api.acme.ghe.com",
|
||||
expected: "https://api.acme.ghe.com/",
|
||||
},
|
||||
}
|
||||
|
||||
factory := &Factory{}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c, err := factory.New(context.Background(), common.RawSecureValue(""), WithCustomServerURL(tt.serverURL))
|
||||
require.NoError(t, err)
|
||||
|
||||
gc, ok := c.(*githubClient)
|
||||
require.True(t, ok, "expected *githubClient")
|
||||
require.Equal(t, tt.expected, gc.gh.BaseURL.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-github/v82/github"
|
||||
"golang.org/x/oauth2"
|
||||
@@ -39,20 +40,34 @@ type ClientOptions struct {
|
||||
// ClientOption customizes how a GitHub client is created.
|
||||
type ClientOption func(*ClientOptions)
|
||||
|
||||
// WithCustomServerURL targets a GitHub Enterprise Server instance at the given
|
||||
// base URL. An empty url or the default `https://github.com` is ignored, keeping the default github.com client.
|
||||
// WithCustomServerURL resolves a GitHub repo/web URL into the REST API base URL for its
|
||||
// deployment type and targets the client at it. An empty url or github.com keeps the default
|
||||
// github.com client.
|
||||
//
|
||||
// GHES (self-hosted): https://custom-ghe-url.com/owner/repo -> https://custom-ghe-url.com (go-github appends /api/v3)
|
||||
// GHEC data residency: https://acme.ghe.com/owner/repo -> https://api.acme.ghe.com (REST API has no /api/v3 prefix)
|
||||
// GHEC standard / EMU: https://github.com/owner/repo -> "" (default api.github.com client)
|
||||
//
|
||||
// The api. prefix on data-residency hosts is also what tells go-github's WithEnterpriseURLs to
|
||||
// skip appending the /api/v3 path that self-hosted servers require, so the two cases diverge.
|
||||
func WithCustomServerURL(serverURL string) ClientOption {
|
||||
return func(o *ClientOptions) {
|
||||
u, err := url.Parse(serverURL)
|
||||
if err != nil {
|
||||
if err != nil || u.Host == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if u.Host == "" || u.Host == "github.com" {
|
||||
host := u.Hostname()
|
||||
if host == "github.com" || host == "www.github.com" {
|
||||
return
|
||||
}
|
||||
|
||||
o.customServerURL = fmt.Sprintf("%s://%s", u.Scheme, u.Host)
|
||||
if strings.HasSuffix(host, ".ghe.com") && !strings.HasPrefix(host, "api.") {
|
||||
o.customServerURL = u.Scheme + "://api." + u.Host
|
||||
return
|
||||
}
|
||||
|
||||
o.customServerURL = u.Scheme + "://" + u.Host
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +1,54 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/apps/provisioning/pkg/repository/git"
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
)
|
||||
|
||||
func TestWithCustomServerURL(t *testing.T) {
|
||||
// TestNewRepository_ServerURL verifies the REST API base URL on the underlying client is resolved
|
||||
// from the repo URL's host per GitHub deployment type.
|
||||
func TestNewRepository_ServerURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
serverURL string
|
||||
expected string
|
||||
name string
|
||||
repoURL string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "default github.com is ignored",
|
||||
serverURL: "https://github.com",
|
||||
expected: "",
|
||||
name: "plain github.com uses default api.github.com",
|
||||
repoURL: "https://github.com/grafana/grafana",
|
||||
expected: "https://api.github.com/",
|
||||
},
|
||||
{
|
||||
name: "github.com with path is ignored",
|
||||
serverURL: "https://github.com/example/test",
|
||||
expected: "",
|
||||
name: "GHES appends /api/v3",
|
||||
repoURL: "https://custom-ghe-url.com/owner/repo",
|
||||
expected: "https://custom-ghe-url.com/api/v3/",
|
||||
},
|
||||
{
|
||||
name: "empty url is ignored",
|
||||
serverURL: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "GitHub Enterprise Server url is kept",
|
||||
serverURL: "https://ghes.example.com/example/test",
|
||||
expected: "https://ghes.example.com",
|
||||
name: "data residency rewrites to api host without /api/v3",
|
||||
repoURL: "https://acme.ghe.com/owner/repo",
|
||||
expected: "https://api.acme.ghe.com/",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var options ClientOptions
|
||||
WithCustomServerURL(tt.serverURL)(&options)
|
||||
assert.Equal(t, tt.expected, options.customServerURL)
|
||||
gitRepo := git.NewMockGitRepository(t)
|
||||
gitRepo.EXPECT().URL().Return(tt.repoURL).Maybe()
|
||||
|
||||
repo, err := NewRepository(context.Background(), &provisioning.Repository{}, gitRepo, ProvideFactory(), common.RawSecureValue(""))
|
||||
require.NoError(t, err)
|
||||
|
||||
gr, ok := repo.(*githubRepository)
|
||||
require.True(t, ok, "expected *githubRepository")
|
||||
gc, ok := gr.gh.(*githubClient)
|
||||
require.True(t, ok, "expected *githubClient")
|
||||
require.Equal(t, tt.expected, gc.gh.BaseURL.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package extensions
|
||||
|
||||
import (
|
||||
_ "github.com/alicebob/miniredis/v2"
|
||||
_ "github.com/google/go-github/v82/github"
|
||||
_ "github.com/grafana/authlib/authz/proto/v1"
|
||||
_ "github.com/grafana/dataplane/examples"
|
||||
_ "github.com/grafana/grafana-azure-sdk-go/v2/azsettings"
|
||||
@@ -48,6 +49,7 @@ import (
|
||||
_ "github.com/grafana/grafana/pkg/tests/testsuite"
|
||||
_ "github.com/grafana/grafana/pkg/util/testutil"
|
||||
_ "github.com/grafana/grafana/pkg/web/webtest"
|
||||
_ "github.com/migueleliasweb/go-github-mock/src/mock"
|
||||
_ "github.com/open-feature/go-sdk/openfeature/testing"
|
||||
_ "github.com/openfga/openfga/pkg/server/test"
|
||||
_ "github.com/openfga/openfga/pkg/storage/test"
|
||||
|
||||
Reference in New Issue
Block a user