55 "context"
66 "errors"
77 "fmt"
8+ "net/http"
89
910 "github.com/prometheus/client_golang/prometheus"
1011 "k8s.io/apimachinery/pkg/runtime"
@@ -14,9 +15,7 @@ import (
1415 "github.com/grafana/grafana-app-sdk/resource"
1516)
1617
17- var (
18- _ resource.Client = & Client {}
19- )
18+ var _ resource.Client = & Client {}
2019
2120// Client is a kubernetes-specific implementation of resource.Client, using custom resource definitions.
2221// A Client is specific to the Schema it was created with.
@@ -116,10 +115,67 @@ func NewClientWithRESTInterface(
116115 }, nil
117116}
118117
118+ type RemoteRestConfig struct {
119+ Host string
120+ TLSClientConfig rest.TLSClientConfig
121+ WrapTransport func (rt http.RoundTripper ) http.RoundTripper
122+ // OverrideAuth, when set to true, will cause the kubeConfig fields related to authentication (BearerToken, BearerTokenFile, CertData, CertFile, KeyData, KeyFile) to be cleared when overlaying the RemoteRestConfig onto the base kubeConfig. This is useful in cases where the remote API server uses a different authentication mechanism than the local kubeConfig.
123+ OverrideAuth bool
124+ }
125+
126+ // NewClientConfigWithExternalClients creates a ClientConfig that will use the RemoteRestConfig map to route
127+ // requests to different API servers based on the group of the resource.Kind being requested.
128+ func NewClientConfigWithExternalClients (remoteRestConfigsByGroup map [string ]* RemoteRestConfig ) ClientConfig {
129+ config := DefaultClientConfig ()
130+ config .KubeConfigProvider = func (kind resource.Kind , kubeConfig rest.Config ) rest.Config {
131+ if kubeConfig .APIPath != "" {
132+ return kubeConfig // Don't modify the kubeConfig if the APIPath is already configured
133+ }
134+ // If it isn't configured, set the APIPath based on the kind's group
135+ if kind .Group () == "" {
136+ kubeConfig .APIPath = "/api"
137+ } else {
138+ kubeConfig .APIPath = "/apis"
139+ }
140+
141+ if remoteCfg , ok := remoteRestConfigsByGroup [kind .Group ()]; ok && remoteCfg != nil {
142+ kubeConfig = overlayRemoteRestConfig (kubeConfig , remoteCfg )
143+ }
144+
145+ return kubeConfig
146+ }
147+
148+ return config
149+ }
150+
151+ // overlayRemoteRestConfig takes the provided kubeConfig and overlays the Host, TLSClientConfig, and WrapTransport
152+ // In standalone apiserver mode, the default KubeConfig points to the
153+ // local loopback which doesn't serve remote resources.
154+ // We overlay the remote connection details (host, TLS, auth) onto the
155+ // base kubeConfig to preserve SDK-set fields like ContentConfig.GroupVersion.
156+ func overlayRemoteRestConfig (kubeConfig rest.Config , remoteCfg * RemoteRestConfig ) rest.Config {
157+ kubeConfig .Host = remoteCfg .Host
158+ kubeConfig .TLSClientConfig = remoteCfg .TLSClientConfig
159+ kubeConfig .WrapTransport = remoteCfg .WrapTransport
160+
161+ // Clear inherited auth that doesn't apply to the remote target.
162+ if remoteCfg .OverrideAuth {
163+ kubeConfig .BearerToken = ""
164+ kubeConfig .BearerTokenFile = ""
165+ kubeConfig .CertData = nil
166+ kubeConfig .CertFile = ""
167+ kubeConfig .KeyData = nil
168+ kubeConfig .KeyFile = ""
169+ }
170+
171+ return kubeConfig
172+ }
173+
119174// List lists resources in the provided namespace.
120175// For resources with a schema.Scope() of ClusterScope, `namespace` must be resource.NamespaceAll
121176func (c * Client ) List (ctx context.Context , namespace string , options resource.ListOptions ) (
122- resource.ListObject , error ) {
177+ resource.ListObject , error ,
178+ ) {
123179 into := c .schema .ZeroListValue ()
124180 err := c .client .list (ctx , namespace , c .schema .Plural (), into , options , func (raw []byte ) (resource.Object , error ) {
125181 into := c .schema .ZeroValue ()
@@ -134,7 +190,8 @@ func (c *Client) List(ctx context.Context, namespace string, options resource.Li
134190
135191// ListInto lists resources in the provided namespace, and unmarshals the response into the provided resource.ListObject
136192func (c * Client ) ListInto (ctx context.Context , namespace string , options resource.ListOptions ,
137- into resource.ListObject ) error {
193+ into resource.ListObject ,
194+ ) error {
138195 if c .schema .Scope () == resource .ClusterScope && namespace != resource .NamespaceAll {
139196 return fmt .Errorf ("cannot list resources with schema scope \" %s\" in namespace \" %s\" , must be NamespaceAll (\" %s\" )" ,
140197 resource .ClusterScope , namespace , resource .NamespaceAll )
@@ -212,7 +269,8 @@ func (c *Client) CreateInto(
212269
213270// Update updates the provided resource, and returns the updated resource from kubernetes
214271func (c * Client ) Update (ctx context.Context , identifier resource.Identifier , obj resource.Object ,
215- options resource.UpdateOptions ) (resource.Object , error ) {
272+ options resource.UpdateOptions ,
273+ ) (resource.Object , error ) {
216274 if obj == nil {
217275 return nil , errors .New ("obj cannot be nil" )
218276 }
@@ -226,7 +284,8 @@ func (c *Client) Update(ctx context.Context, identifier resource.Identifier, obj
226284
227285// UpdateInto updates the provided resource, and marshals the updated resource from kubernetes into `into`
228286func (c * Client ) UpdateInto (ctx context.Context , identifier resource.Identifier , obj resource.Object ,
229- options resource.UpdateOptions , into resource.Object ) error {
287+ options resource.UpdateOptions , into resource.Object ,
288+ ) error {
230289 if obj == nil {
231290 return errors .New ("obj cannot be nil" )
232291 }
@@ -260,7 +319,8 @@ func (c *Client) UpdateInto(ctx context.Context, identifier resource.Identifier,
260319
261320// Patch performs a JSON Patch on the provided resource, and returns the updated object
262321func (c * Client ) Patch (ctx context.Context , identifier resource.Identifier , patch resource.PatchRequest ,
263- options resource.PatchOptions ) (resource.Object , error ) {
322+ options resource.PatchOptions ,
323+ ) (resource.Object , error ) {
264324 into := c .schema .ZeroValue ()
265325 err := c .PatchInto (ctx , identifier , patch , options , into )
266326 if err != nil {
@@ -271,7 +331,8 @@ func (c *Client) Patch(ctx context.Context, identifier resource.Identifier, patc
271331
272332// PatchInto performs a JSON Patch on the provided resource, and marshals the updated version into the `into` field
273333func (c * Client ) PatchInto (ctx context.Context , identifier resource.Identifier , patch resource.PatchRequest ,
274- options resource.PatchOptions , into resource.Object ) error {
334+ options resource.PatchOptions , into resource.Object ,
335+ ) error {
275336 return c .client .patch (ctx , identifier , c .schema .Plural (), patch , into , options , c .codec )
276337}
277338
@@ -283,7 +344,8 @@ func (c *Client) Delete(ctx context.Context, identifier resource.Identifier, opt
283344// Watch makes a watch request for the namespace, and returns a WatchResponse which wraps a kubernetes
284345// watch.Interface. The underlying watch.Interface can be accessed using KubernetesWatch()
285346func (c * Client ) Watch (ctx context.Context , namespace string , options resource.WatchOptions ) (
286- resource.WatchResponse , error ) {
347+ resource.WatchResponse , error ,
348+ ) {
287349 if c .schema .Scope () == resource .ClusterScope && namespace != resource .NamespaceAll {
288350 return nil , fmt .Errorf ("cannot watch resources with schema scope \" %s\" in namespace \" %s\" , must be NamespaceAll (\" %s\" )" ,
289351 resource .ClusterScope , namespace , resource .NamespaceAll )
0 commit comments