Provisioning: correctly use resource clients in controllers (#110737)
* Provisioning: correctly use resource clients in controllers * better names on fields * fix struct initialisation * updating roundtripper tests
This commit is contained in:
@@ -6,7 +6,6 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/authlib/authn"
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
)
|
||||
|
||||
@@ -19,20 +18,22 @@ type tokenExchanger interface {
|
||||
type RoundTripper struct {
|
||||
client tokenExchanger
|
||||
transport http.RoundTripper
|
||||
audience string
|
||||
}
|
||||
|
||||
// NewRoundTripper constructs a RoundTripper that exchanges the provided token per request
|
||||
// and forwards the request to the provided base transport.
|
||||
func NewRoundTripper(tokenExchangeClient tokenExchanger, base http.RoundTripper) *RoundTripper {
|
||||
func NewRoundTripper(tokenExchangeClient tokenExchanger, base http.RoundTripper, audience string) *RoundTripper {
|
||||
return &RoundTripper{
|
||||
client: tokenExchangeClient,
|
||||
transport: base,
|
||||
audience: audience,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
tokenResponse, err := t.client.Exchange(req.Context(), authn.TokenExchangeRequest{
|
||||
Audiences: []string{provisioning.GROUP},
|
||||
Audiences: []string{t.audience},
|
||||
Namespace: "*",
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestRoundTripper_SetsAccessTokenHeader(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
rr.WriteHeader(http.StatusOK)
|
||||
return rr.Result(), nil
|
||||
}))
|
||||
}), "example-audience")
|
||||
|
||||
req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://example", nil)
|
||||
resp, err := tr.RoundTrip(req)
|
||||
@@ -50,7 +50,7 @@ func TestRoundTripper_PropagatesExchangeError(t *testing.T) {
|
||||
tr := NewRoundTripper(&fakeExchanger{err: io.EOF}, roundTripperFunc(func(_ *http.Request) (*http.Response, error) {
|
||||
t.Fatal("transport should not be called on exchange error")
|
||||
return nil, nil
|
||||
}))
|
||||
}), "example-audience")
|
||||
|
||||
req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://example", nil)
|
||||
resp, err := tr.RoundTrip(req)
|
||||
|
||||
@@ -111,7 +111,7 @@ func setupFromConfig(cfg *setting.Cfg) (controllerCfg *provisioningControllerCon
|
||||
APIPath: "/apis",
|
||||
Host: provisioningServerURL,
|
||||
WrapTransport: transport.WrapperFunc(func(rt http.RoundTripper) http.RoundTripper {
|
||||
return authrt.NewRoundTripper(tokenExchangeClient, rt)
|
||||
return authrt.NewRoundTripper(tokenExchangeClient, rt, provisioning.GROUP)
|
||||
}),
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
@@ -153,19 +153,23 @@ func setupFromConfig(cfg *setting.Cfg) (controllerCfg *provisioningControllerCon
|
||||
return nil, fmt.Errorf("folders_server_url is required in [operator] section")
|
||||
}
|
||||
|
||||
apiServerURLs := []string{dashboardsServerURL, foldersServerURL, provisioningServerURL}
|
||||
configProviders := make([]apiserver.RestConfigProvider, len(apiServerURLs))
|
||||
apiServerURLs := map[string]string{
|
||||
resources.DashboardResource.Group: dashboardsServerURL,
|
||||
resources.FolderResource.Group: foldersServerURL,
|
||||
provisioning.GROUP: provisioningServerURL,
|
||||
}
|
||||
configProviders := make(map[string]apiserver.RestConfigProvider)
|
||||
|
||||
for i, url := range apiServerURLs {
|
||||
for group, url := range apiServerURLs {
|
||||
config := &rest.Config{
|
||||
APIPath: "/apis",
|
||||
Host: url,
|
||||
WrapTransport: transport.WrapperFunc(func(rt http.RoundTripper) http.RoundTripper {
|
||||
return authrt.NewRoundTripper(tokenExchangeClient, rt)
|
||||
return authrt.NewRoundTripper(tokenExchangeClient, rt, group)
|
||||
}),
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
configProviders[i] = NewDirectConfigProvider(config)
|
||||
configProviders[group] = NewDirectConfigProvider(config)
|
||||
}
|
||||
|
||||
clients := resources.NewClientFactoryForMultipleAPIServers(configProviders)
|
||||
|
||||
@@ -120,26 +120,39 @@ func NewClientFactory(configProvider apiserver.RestConfigProvider) ClientFactory
|
||||
}
|
||||
|
||||
// NewClientFactoryForMultipleAPIServers creates a ClientFactory for multiple API servers
|
||||
func NewClientFactoryForMultipleAPIServers(configProviders []apiserver.RestConfigProvider) ClientFactory {
|
||||
clientFactories := make([]ClientFactory, len(configProviders))
|
||||
func NewClientFactoryForMultipleAPIServers(configProviders map[string]apiserver.RestConfigProvider) ClientFactory {
|
||||
clientFactories := make(map[string]ClientFactory)
|
||||
|
||||
for i, configProvider := range configProviders {
|
||||
for api, configProvider := range configProviders {
|
||||
clientFactory := NewClientFactory(configProvider)
|
||||
clientFactories[i] = clientFactory
|
||||
clientFactories[api] = clientFactory
|
||||
}
|
||||
|
||||
return &multiClientFactory{clientFactories: clientFactories}
|
||||
}
|
||||
|
||||
type multiClientFactory struct {
|
||||
clientFactories []ClientFactory
|
||||
clientFactories map[string]ClientFactory
|
||||
}
|
||||
|
||||
func (m *multiClientFactory) Clients(ctx context.Context, namespace string) (ResourceClients, error) {
|
||||
for _, clientFactory := range m.clientFactories {
|
||||
return clientFactory.Clients(ctx, namespace)
|
||||
clients := make(map[string]ResourceClients)
|
||||
for group, clientFactory := range m.clientFactories {
|
||||
c, err := clientFactory.Clients(ctx, namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clients[group] = c
|
||||
}
|
||||
return nil, fmt.Errorf("no client factories available")
|
||||
if len(clients) == 0 {
|
||||
return nil, fmt.Errorf("no client factories available")
|
||||
}
|
||||
|
||||
return &multiResourceClients{
|
||||
namespace: namespace,
|
||||
resourceClientsByAPIGroup: clients,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *clientFactory) Clients(ctx context.Context, namespace string) (ResourceClients, error) {
|
||||
@@ -283,6 +296,52 @@ func (c *resourceClients) User(ctx context.Context) (dynamic.ResourceInterface,
|
||||
return v, err
|
||||
}
|
||||
|
||||
type multiResourceClients struct {
|
||||
namespace string
|
||||
mutex sync.Mutex
|
||||
resourceClientsByAPIGroup map[string]ResourceClients
|
||||
}
|
||||
|
||||
// ForKind returns a client for a kind.
|
||||
// If the kind has a version, it will be used.
|
||||
// If the kind does not have a version, the preferred version will be used.
|
||||
func (c *multiResourceClients) ForKind(ctx context.Context, gvk schema.GroupVersionKind) (dynamic.ResourceInterface, schema.GroupVersionResource, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
resourceClients, ok := c.resourceClientsByAPIGroup[gvk.Group]
|
||||
if !ok {
|
||||
return nil, schema.GroupVersionResource{}, fmt.Errorf("no clients provider for group %s", gvk.Group)
|
||||
}
|
||||
|
||||
return resourceClients.ForKind(ctx, gvk)
|
||||
}
|
||||
|
||||
// ForResource returns a client for a resource.
|
||||
// If the resource has a version, it will be used.
|
||||
// If the resource does not have a version, the preferred version will be used.
|
||||
func (c *multiResourceClients) ForResource(ctx context.Context, gvr schema.GroupVersionResource) (dynamic.ResourceInterface, schema.GroupVersionKind, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
resourceClients, ok := c.resourceClientsByAPIGroup[gvr.Group]
|
||||
if !ok {
|
||||
return nil, schema.GroupVersionKind{}, fmt.Errorf("no clients provider for group %s", gvr.Group)
|
||||
}
|
||||
|
||||
return resourceClients.ForResource(ctx, gvr)
|
||||
}
|
||||
|
||||
func (c *multiResourceClients) Folder(ctx context.Context) (dynamic.ResourceInterface, error) {
|
||||
client, _, err := c.ForResource(ctx, FolderResource)
|
||||
return client, err
|
||||
}
|
||||
|
||||
func (c *multiResourceClients) User(ctx context.Context) (dynamic.ResourceInterface, error) {
|
||||
v, _, err := c.ForResource(ctx, UserResource)
|
||||
return v, err
|
||||
}
|
||||
|
||||
// ForEach applies the function to each resource returned from the list operation
|
||||
func ForEach(ctx context.Context, client dynamic.ResourceInterface, fn func(item *unstructured.Unstructured) error) error {
|
||||
var continueToken string
|
||||
|
||||
Reference in New Issue
Block a user