diff --git a/packages/grafana-data/src/types/datasource.ts b/packages/grafana-data/src/types/datasource.ts index d4970c3d750..eea3a841a70 100644 --- a/packages/grafana-data/src/types/datasource.ts +++ b/packages/grafana-data/src/types/datasource.ts @@ -647,6 +647,7 @@ export interface DataSourceSettings ({ + getBackendSrv: jest.fn().mockReturnValue({ + get: jest.fn(), + }), + config: {}, +})); + +describe('DatasourceAPIVersions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('get', async () => { + const getMock = jest.fn().mockResolvedValue({ + groups: [ + { name: 'testdata.datasource.grafana.app', preferredVersion: { version: 'v1' } }, + { name: 'prometheus.datasource.grafana.app', preferredVersion: { version: 'v2' } }, + { name: 'myorg-myplugin.datasource.grafana.app', preferredVersion: { version: 'v3' } }, + ], + }); + getBackendSrv().get = getMock; + const apiVersions = new DatasourceAPIVersions(); + expect(await apiVersions.get('testdata')).toBe('v1'); + expect(await apiVersions.get('grafana-testdata-datasource')).toBe('v1'); + expect(await apiVersions.get('prometheus')).toBe('v2'); + expect(await apiVersions.get('graphite')).toBeUndefined(); + expect(await apiVersions.get('myorg-myplugin-datasource')).toBe('v3'); + expect(getMock).toHaveBeenCalledTimes(1); + expect(getMock).toHaveBeenCalledWith('/apis'); + }); +}); diff --git a/public/app/features/apiserver/client.ts b/public/app/features/apiserver/client.ts index 6a23c3dce50..609d6a4c9ec 100644 --- a/public/app/features/apiserver/client.ts +++ b/public/app/features/apiserver/client.ts @@ -14,6 +14,7 @@ import { AnnoKeyOriginPath, AnnoKeyOriginHash, AnnoKeyOriginName, + K8sAPIGroupList, } from './types'; export interface GroupVersionResource { @@ -110,3 +111,34 @@ function setOriginAsUI(meta: Partial) { meta.annotations[AnnoKeyOriginPath] = window.location.pathname; meta.annotations[AnnoKeyOriginHash] = config.buildInfo.versionString; } + +export class DatasourceAPIVersions { + private apiVersions?: { [pluginID: string]: string }; + + async get(pluginID: string): Promise { + if (this.apiVersions) { + return this.apiVersions[pluginID]; + } + const apis = await getBackendSrv().get('/apis'); + const apiVersions: { [pluginID: string]: string } = {}; + apis.groups.forEach((group) => { + if (group.name.includes('datasource.grafana.app')) { + const id = group.name.split('.')[0]; + apiVersions[id] = group.preferredVersion.version; + // workaround for plugins that don't append '-datasource' for the group name + // e.g. org-plugin-datasource uses org-plugin.datasource.grafana.app + if (!id.endsWith('-datasource')) { + if (!id.includes('-')) { + // workaroud for Grafana plugins that don't include the org either + // e.g. testdata uses testdata.datasource.grafana.app + apiVersions[`grafana-${id}-datasource`] = group.preferredVersion.version; + } else { + apiVersions[`${id}-datasource`] = group.preferredVersion.version; + } + } + } + }); + this.apiVersions = apiVersions; + return apiVersions[pluginID]; + } +} diff --git a/public/app/features/apiserver/types.ts b/public/app/features/apiserver/types.ts index 9065f67f668..e3ee1ded77c 100644 --- a/public/app/features/apiserver/types.ts +++ b/public/app/features/apiserver/types.ts @@ -150,3 +150,13 @@ export interface ResourceClient { update(obj: ResourceForCreate): Promise>; delete(name: string): Promise; } + +export interface K8sAPIGroup { + name: string; + versions: Array<{ groupVersion: string; version: string }>; + preferredVersion: { groupVersion: string; version: string }; +} +export interface K8sAPIGroupList { + kind: 'APIGroupList'; + groups: K8sAPIGroup[]; +} diff --git a/public/app/features/datasources/state/actions.ts b/public/app/features/datasources/state/actions.ts index 10574a29bf5..24ab46c15b7 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -18,6 +18,7 @@ import { import { updateNavIndex } from 'app/core/actions'; import { appEvents, contextSrv } from 'app/core/core'; import { getBackendSrv } from 'app/core/services/backend_srv'; +import { DatasourceAPIVersions } from 'app/features/apiserver/client'; import { ROUTES as CONNECTIONS_ROUTES } from 'app/features/connections/constants'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getPluginSettings } from 'app/features/plugins/pluginSettings'; @@ -264,6 +265,8 @@ export function loadDataSourcePlugins(): ThunkResult { }; } +const dsApiVersions = new DatasourceAPIVersions(); + export function updateDataSource(dataSource: DataSourceSettings) { return async ( dispatch: ( @@ -271,6 +274,9 @@ export function updateDataSource(dataSource: DataSourceSettings) { ) => DataSourceSettings ) => { try { + if (config.featureToggles.grafanaAPIServerWithExperimentalAPIs) { + dataSource.apiVersion = await dsApiVersions.get(dataSource.type); + } await api.updateDataSource(dataSource); } catch (err) { const formattedError = parseHealthCheckError(err);