diff --git a/.changeset/lazy-planes-reply.md b/.changeset/lazy-planes-reply.md new file mode 100644 index 00000000..d12bb6c8 --- /dev/null +++ b/.changeset/lazy-planes-reply.md @@ -0,0 +1,5 @@ +--- +'grafana-github-datasource': minor +--- + +Add support for PDC diff --git a/README.md b/README.md index f5324cc2..57169e7a 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ Watch this video to learn more about setting up the Grafana GitHub data source p This data source uses the [`githubv4` package](https://github.com/shurcooL/githubv4), which is under active development. +## Private data source connect - Only for Grafana Cloud users. + +Establishes a private, secured connection between a Grafana Cloud stack and data sources within a private network. Use the drop-down to locate the PDC URL. For setup instructions, refer to [Private data source connect (PDC)](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/) and [Configure PDC](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/configure-pdc/#configure-grafana-private-data-source-connect-pdc). Click Manage private data source connect to open your PDC connection page and view your configuration details. + ## Frequently Asked Questions - **Why does it sometimes take up to 5 minutes for my new pull request / new issue / new commit to show up?** diff --git a/pkg/github/client/client.go b/pkg/github/client/client.go index 926ede72..46265315 100644 --- a/pkg/github/client/client.go +++ b/pkg/github/client/client.go @@ -13,6 +13,7 @@ import ( googlegithub "github.com/google/go-github/v72/github" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + "github.com/grafana/grafana-plugin-sdk-go/backend/proxy" "github.com/influxdata/tdigest" "github.com/shurcooL/githubv4" "golang.org/x/oauth2" @@ -54,27 +55,33 @@ var runnerPerMinuteRate = map[string]float64{ // New instantiates a new GitHub API client. func New(ctx context.Context, settings models.Settings) (*Client, error) { + plugin := backend.PluginConfigFromContext(ctx) + opts, err := plugin.DataSourceInstanceSettings.HTTPClientOptions(ctx) + if err != nil { + return nil, err + } + if settings.SelectedAuthType == models.AuthTypeGithubApp { - return createAppClient(settings) + return createAppClient(settings, opts) } if settings.SelectedAuthType == models.AuthTypePAT { - return createAccessTokenClient(ctx, settings) + return createAccessTokenClient(ctx, settings, opts) } return nil, backend.DownstreamError(errors.New("access token or app token are required")) } -func createAppClient(settings models.Settings) (*Client, error) { - transport, err := httpclient.GetDefaultTransport() +func createAppClient(settings models.Settings, opts httpclient.Options) (*Client, error) { + httpClient, err := httpclient.New(opts) if err != nil { - return nil, backend.DownstreamError(errors.New("error: http.DefaultTransport is not of type *http.Transport")) + return nil, backend.DownstreamErrorf("error creating http client: %w", err) } - itr, err := ghinstallation.New(transport, settings.AppIdInt64, settings.InstallationIdInt64, []byte(settings.PrivateKey)) + + itr, err := ghinstallation.New(httpClient.Transport, settings.AppIdInt64, settings.InstallationIdInt64, []byte(settings.PrivateKey)) if err != nil { return nil, backend.DownstreamError(errors.New("error creating token source")) } - httpClient := &http.Client{Transport: itr} - + httpClient.Transport = itr if settings.GitHubURL == "" { return &Client{ restClient: googlegithub.NewClient(httpClient), @@ -87,13 +94,26 @@ func createAppClient(settings models.Settings) (*Client, error) { return useGitHubEnterprise(httpClient, settings) } -func createAccessTokenClient(ctx context.Context, settings models.Settings) (*Client, error) { +func createAccessTokenClient(ctx context.Context, settings models.Settings, opts httpclient.Options) (*Client, error) { src := oauth2.StaticTokenSource( &oauth2.Token{AccessToken: settings.AccessToken}, ) httpClient := oauth2.NewClient(ctx, src) + cli := proxy.New(opts.ProxyOptions) + if cli.SecureSocksProxyEnabled() { + // only override the Transport if Secure Proxy is enabled. + transport, err := httpclient.GetTransport(opts) + if err != nil { + return nil, backend.DownstreamErrorf("error getting the transport: %w", err) + } + + httpClient.Transport = &oauth2.Transport{ + Base: transport, + Source: oauth2.ReuseTokenSource(nil, src), + } + } if settings.GitHubURL == "" { return &Client{ restClient: googlegithub.NewClient(httpClient), diff --git a/pkg/plugin/instance.go b/pkg/plugin/instance.go index 753defdf..036459f7 100644 --- a/pkg/plugin/instance.go +++ b/pkg/plugin/instance.go @@ -28,15 +28,15 @@ func NewGitHubInstance(ctx context.Context, settings models.Settings) (instancem } // NewDataSourceInstance creates a new instance -func NewDataSourceInstance(_ context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { +func NewDataSourceInstance(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) { datasourceSettings, err := models.LoadSettings(settings) if err != nil { return nil, err } - + datasourceSettings.CachingEnabled = true - instance, err := NewGitHubInstance(context.Background(), datasourceSettings) + instance, err := NewGitHubInstance(ctx, datasourceSettings) if err != nil { return instance, fmt.Errorf("instantiating github instance: %w", err) } diff --git a/src/views/ConfigEditor.tsx b/src/views/ConfigEditor.tsx index a8eda606..c52f9fab 100644 --- a/src/views/ConfigEditor.tsx +++ b/src/views/ConfigEditor.tsx @@ -1,9 +1,5 @@ import React, { ChangeEvent, useCallback, useEffect, useState } from 'react'; import { css } from '@emotion/css'; -import { Collapse, Field, Input, Label, RadioButtonGroup, SecretInput, SecretTextArea, useStyles2 } from '@grafana/ui'; -import { ConfigSection, DataSourceDescription } from '@grafana/plugin-ui'; -import { Divider } from 'components/Divider'; -import { components as selectors } from '../components/selectors'; import { onUpdateDatasourceJsonDataOption, onUpdateDatasourceSecureJsonDataOption, @@ -11,7 +7,22 @@ import { type GrafanaTheme2, type SelectableValue, } from '@grafana/data'; +import { ConfigSection, DataSourceDescription } from '@grafana/plugin-ui'; +import { config } from '@grafana/runtime'; +import { + Collapse, + Field, + Input, + Label, + RadioButtonGroup, + SecretInput, + SecretTextArea, + SecureSocksProxySettings, + useStyles2, +} from '@grafana/ui'; +import { Divider } from 'components/Divider'; import type { GitHubAuthType, GitHubLicenseType, GitHubDataSourceOptions, GitHubSecureJsonData } from 'types/config'; +import { components as selectors } from '../components/selectors'; export type ConfigEditorProps = DataSourcePluginOptionsEditorProps; @@ -196,7 +207,9 @@ const ConfigEditor = (props: ConfigEditorProps) => { )} - + {config.secureSocksDSProxyEnabled && ( + + )}