mirror of
https://github.com/grafana/grafana.git
synced 2026-02-03 20:49:50 -05:00
When `config.featureToggles.queryServiceWithConnections` is true, calls to `getDataSourceByUid` are routed through a new frontend function, `getDataSourceFromK8sAPI`, which exercises the new app platform kubernetes style APIs.
264 lines
8.6 KiB
Go
264 lines
8.6 KiB
Go
package datasource
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"maps"
|
|
"net/http"
|
|
"slices"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
|
datasourceV0alpha1 "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
|
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/datasources"
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"github.com/grafana/grafana/pkg/tests/apis"
|
|
"github.com/grafana/grafana/pkg/tests/testinfra"
|
|
"github.com/grafana/grafana/pkg/tests/testsuite"
|
|
"github.com/grafana/grafana/pkg/util/testutil"
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
testsuite.Run(m)
|
|
}
|
|
|
|
func TestIntegrationTestDatasource(t *testing.T) {
|
|
testutil.SkipIntegrationTestInShortMode(t)
|
|
expectedAPIVersion := "grafana-testdata-datasource.datasource.grafana.app/v0alpha1"
|
|
|
|
ctx := context.Background()
|
|
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
|
DisableAnonymous: true,
|
|
EnableFeatureToggles: []string{
|
|
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // Required to start the datasource api servers
|
|
featuremgmt.FlagQueryServiceWithConnections, // enables CRUD endpoints
|
|
},
|
|
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
|
|
"datasources.grafana-testdata-datasource.datasource.grafana.app": {
|
|
DualWriterMode: grafanarest.Mode0,
|
|
},
|
|
},
|
|
})
|
|
|
|
client := helper.Org1.Admin.ResourceClient(t, schema.GroupVersionResource{
|
|
Group: "grafana-testdata-datasource.datasource.grafana.app",
|
|
Version: "v0alpha1",
|
|
Resource: "datasources",
|
|
}).Namespace("default")
|
|
|
|
t.Run("create", func(t *testing.T) {
|
|
out, err := client.Create(ctx, &unstructured.Unstructured{
|
|
Object: map[string]any{
|
|
"apiVersion": "grafana-testdata-datasource.datasource.grafana.app/v0alpha1",
|
|
"kind": "DataSource",
|
|
"metadata": map[string]any{
|
|
"name": "test",
|
|
},
|
|
"spec": map[string]any{
|
|
"title": "test",
|
|
},
|
|
"secure": map[string]any{
|
|
"aaa": map[string]any{
|
|
"create": "AAA",
|
|
},
|
|
"bbb": map[string]any{
|
|
"create": "BBB",
|
|
},
|
|
},
|
|
},
|
|
}, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, "test", out.GetName())
|
|
require.Equal(t, expectedAPIVersion, out.GetAPIVersion())
|
|
|
|
obj, err := utils.MetaAccessor(out)
|
|
require.NoError(t, err)
|
|
|
|
secure, err := obj.GetSecureValues()
|
|
require.NoError(t, err)
|
|
|
|
keys := slices.Collect(maps.Keys(secure))
|
|
require.ElementsMatch(t, []string{"aaa", "bbb"}, keys)
|
|
})
|
|
|
|
t.Run("update", func(t *testing.T) {
|
|
out, err := client.Update(ctx, &unstructured.Unstructured{
|
|
Object: map[string]any{
|
|
"apiVersion": "grafana-testdata-datasource.datasource.grafana.app/v0alpha1",
|
|
"metadata": map[string]any{
|
|
"name": "test",
|
|
},
|
|
"spec": map[string]any{
|
|
"title": "test",
|
|
"database": "testdb",
|
|
"url": "http://fake.url",
|
|
"access": datasources.DS_ACCESS_PROXY,
|
|
"user": "example",
|
|
"isDefault": true,
|
|
"readOnly": true,
|
|
"jsonData": map[string]any{
|
|
"hello": "world",
|
|
},
|
|
},
|
|
"secure": map[string]any{
|
|
// "aaa": map[string]any{
|
|
// "remove": true, // remove does not really remove in legacy!
|
|
// },
|
|
"ccc": map[string]any{
|
|
"create": "CCC", // add a third value
|
|
},
|
|
},
|
|
},
|
|
}, metav1.UpdateOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, "test", out.GetName())
|
|
require.Equal(t, expectedAPIVersion, out.GetAPIVersion())
|
|
|
|
obj, err := utils.MetaAccessor(out)
|
|
require.NoError(t, err)
|
|
|
|
secure, err := obj.GetSecureValues()
|
|
require.NoError(t, err)
|
|
|
|
keys := slices.Collect(maps.Keys(secure))
|
|
require.ElementsMatch(t, []string{"aaa", "bbb", "ccc"}, keys)
|
|
})
|
|
|
|
t.Run("list", func(t *testing.T) {
|
|
list, err := client.List(ctx, metav1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, expectedAPIVersion, list.GetAPIVersion())
|
|
require.Len(t, list.Items, 1, "expected a single datasource")
|
|
require.Equal(t, "test", list.Items[0].GetName(), "with the test uid")
|
|
|
|
spec, _, _ := unstructured.NestedMap(list.Items[0].Object, "spec")
|
|
jj, _ := json.MarshalIndent(spec, "", " ")
|
|
// fmt.Printf("%s\n", string(jj))
|
|
require.JSONEq(t, `{
|
|
"access": "proxy",
|
|
"database": "testdb",
|
|
"isDefault": true,
|
|
"jsonData": {
|
|
"hello": "world"
|
|
},
|
|
"readOnly": true,
|
|
"title": "test",
|
|
"url": "http://fake.url",
|
|
"user": "example"
|
|
}`, string(jj))
|
|
})
|
|
|
|
t.Run("execute", func(t *testing.T) {
|
|
client := helper.Org1.Admin.ResourceClient(t, schema.GroupVersionResource{
|
|
Group: "grafana-testdata-datasource.datasource.grafana.app",
|
|
Version: "v0alpha1",
|
|
Resource: "datasources",
|
|
}).Namespace("default")
|
|
ctx := context.Background()
|
|
|
|
list, err := client.List(ctx, metav1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Len(t, list.Items, 1, "expected a single connection")
|
|
require.Equal(t, "test", list.Items[0].GetName(), "with the test uid")
|
|
|
|
_, err = client.Get(ctx, "test", metav1.GetOptions{}, "health")
|
|
// endpoint is disabled currently because it has not been
|
|
// sufficiently tested.
|
|
// for more info see pkg/registry/apis/datasource/sub_health.go
|
|
require.Error(t, err)
|
|
var statusErr *apierrors.StatusError
|
|
require.True(t, errors.As(err, &statusErr))
|
|
require.Equal(t, int32(501), statusErr.ErrStatus.Code)
|
|
// require.NoError(t, err)
|
|
// body, err := rsp.MarshalJSON()
|
|
// require.NoError(t, err)
|
|
// //fmt.Printf("GOT: %v\n", string(body))
|
|
// require.JSONEq(t, `{
|
|
// "apiVersion": "grafana-testdata-datasource.datasource.grafana.app/v0alpha1",
|
|
// "code": 1,
|
|
// "kind": "HealthCheckResult",
|
|
// "message": "Data source is working",
|
|
// "status": "OK"
|
|
// }
|
|
// `, string(body))
|
|
|
|
// Test connecting to non-JSON marshaled data
|
|
raw := apis.DoRequest[any](helper, apis.RequestParams{
|
|
User: helper.Org1.Admin,
|
|
Method: "GET",
|
|
Path: "/apis/grafana-testdata-datasource.datasource.grafana.app/v0alpha1/namespaces/default/datasources/test/resource",
|
|
}, nil)
|
|
// endpoint is disabled currently because it has not been
|
|
// sufficiently tested.
|
|
// for more info see pkg/registry/apis/datasource/sub_resource.go
|
|
require.Equal(t, int32(501), raw.Status.Code)
|
|
// require.Equal(t, `Hello world from test datasource!`, string(raw.Body))
|
|
})
|
|
|
|
t.Run("delete", func(t *testing.T) {
|
|
err := client.Delete(ctx, "test", metav1.DeleteOptions{})
|
|
require.NoError(t, err)
|
|
|
|
list, err := client.List(ctx, metav1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Empty(t, list.Items)
|
|
})
|
|
}
|
|
|
|
func TestIntegrationTestDatasourceAccess(t *testing.T) {
|
|
testutil.SkipIntegrationTestInShortMode(t)
|
|
|
|
t.Run("get DatasourceAccessInfo", func(t *testing.T) {
|
|
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
|
DisableAnonymous: true,
|
|
EnableFeatureToggles: []string{
|
|
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // Required to start the datasource api servers
|
|
featuremgmt.FlagQueryServiceWithConnections, // enables CRUD endpoints
|
|
},
|
|
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
|
|
"datasources.grafana-testdata-datasource.datasource.grafana.app": {
|
|
DualWriterMode: grafanarest.Mode0,
|
|
},
|
|
},
|
|
})
|
|
|
|
var datasourceAccessInfo datasourceV0alpha1.DatasourceAccessInfo
|
|
raw := apis.DoRequest(helper, apis.RequestParams{
|
|
User: helper.Org1.Admin,
|
|
Method: "GET",
|
|
Path: "/apis/grafana-testdata-datasource.datasource.grafana.app/v0alpha1/namespaces/default/datasources/test/access",
|
|
}, &datasourceAccessInfo)
|
|
require.Equal(t, http.StatusOK, raw.Response.StatusCode)
|
|
|
|
expectedDatasourceAccessInfo := datasourceV0alpha1.DatasourceAccessInfo{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "DatasourceAccessInfo",
|
|
APIVersion: "grafana-testdata-datasource.datasource.grafana.app/v0alpha1",
|
|
},
|
|
Permissions: accesscontrol.Metadata{
|
|
"alert.instances.external:read": true,
|
|
"alert.instances.external:write": true,
|
|
"alert.notifications.external:read": true,
|
|
"alert.notifications.external:write": true,
|
|
"alert.rules.external:read": true,
|
|
"alert.rules.external:write": true,
|
|
"datasources.id:read": true,
|
|
"datasources:delete": true,
|
|
"datasources:query": true,
|
|
"datasources:read": true,
|
|
"datasources:write": true,
|
|
},
|
|
}
|
|
require.Equal(t, datasourceAccessInfo, expectedDatasourceAccessInfo)
|
|
})
|
|
}
|